// redMutexForTask vendors a configured redlock w/ randomness builtin for the expiration // of the lock func (r *RateLimiter) redMutexForTask(factor float64, delay int64) *redsync.Mutex { // Grab the pool redisPool := r.pool nodes := []redsync.Pool{redisPool} // Generate the mutex w/ token redSyncToken := r.redlockToken() redMutex, err := redsync.NewMutexWithGenericPool(redSyncToken, nodes) if err != nil { meshLog.Fatalf("Error creating RedMutex in limiter: %+v", err) return nil } // Configure the mutex to have add sleep time randomness to its waiting. It was // found in testing that w/ out this, the system locks in step w/ itself when not using // a local pmutex. This is a danger for dist systems redMutex.Tries = 10000 sleepTime := (rand.Float64() * factor * float64(delay)) + float64(delay) redMutex.Delay = time.Duration(sleepTime) * time.Millisecond redMutex.Expiry = 15 * time.Second return redMutex }
// Enter attempts to enter the request into the current pool func (r *RateLimiter) Enter() error { // Set expiration timeInterval := r.timeInterval if timeInterval == 0 { timeInterval = defaultTimeInterval } // Set retries retries := r.retries if retries == 0 { retries = defaultRetries } // Set delay delay := r.delay if delay == 0 { delay = delay / 10.0 } // Set factor factor := r.factor if factor == 0 { factor = defaultFactor } // Local token ref token := r.rateLimiterToken() // Begin process of trying to enter into the // current window on this process. Lock across this // process to avoid rushing redis r.mutex.Lock() defer r.mutex.Unlock() // Lock this job across processes too, but only after a // sequential local lock redMutex := r.redMutexForTask(factor, delay) err := redMutex.Lock() if err != nil { meshLog.Fatalf("Error acquiring local redlock on ratelimiter with error: %+v", token) return err } defer redMutex.Unlock() // Create and close the redis connection we were handed redisSession := meshRedis.NewSessionWithExistingPool(r.pool) defer redisSession.CloseSession() // Enter a loop to begin the tries to enter the limiter group for i := 0; i < retries; i++ { // First try to resolve the list and get a count count, err := redisSession.GetListCount(token) if err != nil { meshLog.Fatal(err) } if err != nil || count >= r.maxRequestsForTimeInterval { // Sleep w/ a randomness factor sleepTime := (rand.Float64() * factor * float64(delay)) + float64(delay) time.Sleep(time.Duration(sleepTime) * time.Millisecond) } else { // The key doesnt exists, or we're below our limit // // Check for the key existence exists, err := redisSession.KeyExists(token) if err != nil { meshLog.Fatal(err) continue } // If key doesn't exist, push it w/ expiration if !exists { // Multi cmd err = redisSession.AtomicPushOnListWithMsExpiration(token, token, timeInterval) if err != nil { meshLog.Fatalf("Block Creation Error In Rate Limiter: %+v", err) continue } } else { // RPush _, err = redisSession.RPushX(token, token) if err != nil { meshLog.Fatal(err) continue } } // Success! Let's return w/ no error return nil } } return errors.New("Unable to process request. Max attempts hit in the Rate Limiter") }