tokenlimit

[!TIP] This document is machine-translated by Google. If you find grammatical and semantic errors, and the document description is not clear, please PR

This section will introduce its basic usage through token limit (tokenlimit).

Usage

  1. const (
  2. burst = 100
  3. rate = 100
  4. seconds = 5
  5. )
  6. store := redis.NewRedis("localhost:6379", "node", "")
  7. fmt.Println(store.Ping())
  8. // New tokenLimiter
  9. limiter := limit.NewTokenLimiter(rate, burst, store, "rate-test")
  10. timer := time.NewTimer(time.Second * seconds)
  11. quit := make(chan struct{})
  12. defer timer.Stop()
  13. go func() {
  14. <-timer.C
  15. close(quit)
  16. }()
  17. var allowed, denied int32
  18. var wait sync.WaitGroup
  19. for i := 0; i < runtime.NumCPU(); i++ {
  20. wait.Add(1)
  21. go func() {
  22. for {
  23. select {
  24. case <-quit:
  25. wait.Done()
  26. return
  27. default:
  28. if limiter.Allow() {
  29. atomic.AddInt32(&allowed, 1)
  30. } else {
  31. atomic.AddInt32(&denied, 1)
  32. }
  33. }
  34. }
  35. }()
  36. }
  37. wait.Wait()
  38. fmt.Printf("allowed: %d, denied: %d, qps: %d\n", allowed, denied, (allowed+denied)/seconds)

tokenlimit

On the whole, the token bucket production logic is as follows:

  • The average sending rate configured by the user is r, then a token is added to the bucket every 1/r second;
  • Assume that at most b tokens can be stored in the bucket. If the token bucket is full when the token arrives, then the token will be discarded;
  • When the traffic enters at the rate v, the token is taken from the bucket at the rate v, the traffic that gets the token passes, and the traffic that does not get the token does not pass, and the fuse logic is executed;

go-zero adopts the method of lua script under both types of current limiters, relying on redis to achieve distributed current limiting, and lua script can also achieve atomicity of token production and read operations.

Let’s take a look at several key attributes controlled by lua script:

argument mean
ARGV[1] rate 「How many tokens are generated per second」
ARGV[2] burst 「Maximum token bucket」
ARGV[3] now_time「Current timestamp」
ARGV[4] get token nums 「The number of tokens that the developer needs to obtain」
KEYS[1] Tokenkey representing the resource
KEYS[2] The key that represents the refresh time
  1. -- Return whether the expected token can be obtained alive
  2. local rate = tonumber(ARGV[1])
  3. local capacity = tonumber(ARGV[2])
  4. local now = tonumber(ARGV[3])
  5. local requested = tonumber(ARGV[4])
  6. -- fill_timeHow long does it take to fill the token_bucket
  7. local fill_time = capacity/rate
  8. -- Round down the fill time
  9. local ttl = math.floor(fill_time*2)
  10. -- Get the number of remaining tokens in the current token_bucket
  11. -- If it is the first time to enter, set the number of token_bucket to the maximum value of the token bucket
  12. local last_tokens = tonumber(redis.call("get", KEYS[1]))
  13. if last_tokens == nil then
  14. last_tokens = capacity
  15. end
  16. -- The time when the token_bucket was last updated
  17. local last_refreshed = tonumber(redis.call("get", KEYS[2]))
  18. if last_refreshed == nil then
  19. last_refreshed = 0
  20. end
  21. local delta = math.max(0, now-last_refreshed)
  22. -- Calculate the number of new tokens based on the span between the current time and the last update time, and the rate of token production
  23. -- If it exceeds max_burst, excess tokens produced will be discarded
  24. local filled_tokens = math.min(capacity, last_tokens+(delta*rate))
  25. local allowed = filled_tokens >= requested
  26. local new_tokens = filled_tokens
  27. if allowed then
  28. new_tokens = filled_tokens - requested
  29. end
  30. -- Update the new token number and update time
  31. redis.call("setex", KEYS[1], ttl, new_tokens)
  32. redis.call("setex", KEYS[2], ttl, now)
  33. return allowed

It can be seen from the above that the lua script: only involves the operation of the token, ensuring that the token is produced and read reasonably.

Function analysis

tokenlimit - 图1

Seen from the above flow:

  1. There are multiple guarantee mechanisms to ensure that the current limit will be completed.
  2. If the redis limiter fails, at least in the process rate limiter will cover it.
  3. Retry the redis limiter mechanism to ensure that it runs as normally as possible.

Summary

The tokenlimit current limiting scheme in go-zero is suitable for instantaneous traffic shocks, and the actual request scenario is not at a constant rate. The token bucket is quite pre-request, and when the real request arrives, it won’t be destroyed instantly. When the traffic hits a certain level, consumption will be carried out at a predetermined rate.

However, in the production of token, dynamic adjustment cannot be made according to the current flow situation, and it is not flexible enough, and further optimization can be carried out. In addition, you can refer to Token bucket WIKI which mentioned hierarchical token buckets, which are divided into different queues according to different traffic bandwidths.

Reference