Example #1
0
// 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
}
Example #2
0
// 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")
}