限流算法-令牌桶 Go语言实现

283 阅读2分钟

算法

原理:

(图片来自网络)

  1. 有一个存放令牌的桶,桶的容量一般即为qps大小
  1. 有单独生成令牌的线程,以一定速率(和qps有关)往桶里生产令牌,如果桶满则停止生产
  1. 当有请求过来,首先从桶里获取令牌,如果能获得则继续执行后续业务逻辑,如果无法获取则超时等待或直接返回限流错误

特点:

  1. 令牌桶本身没有丢弃和优先级策略。
  1. 桶的容量有限制,所以每个时间段内的流量可控
  1. 请求处理非匀速
  1. 实现相对简单

适用场景:

  1. 因为桶本身会预留一定的空闲令牌,适合流量有突增的场景

代码实现

通过令牌桶的思想可知,我们大概要做以下逻辑

  1. 有一个桶的实体,可以设置桶的容量,当前剩余的令牌数量等
  1. 会有一个单独的线程匀速生成令牌
  1. 提供一个获取令牌的接口,返回获取成功或者失败

以下代码参考kitex qps限流实现

  • 令牌桶
type tokenBucketRateLimiter struct {
   limit    int32       // 容量
   tokens   int32       // 当前token数
   interval time.Duration // 多长时间生成一次token
   once     int32         // 每次生成多少token
}
  • 初始化
func NewTokenBucket(limit int, opts ...Option) RateLimiter {
   l := &tokenBucketRateLimiter{
      limit: int32(limit),
   }
   for _, opt := range opts {
      opt.apply(l)
   }
   l.calcOnce()
   go l.createTokens()
   return l
}

// 设置生成token的间隔时间
func WithInterval(interval time.Duration) Option {
   return Option{apply: func(limiter RateLimiter) {
      l := limiter.(*tokenBucketRateLimiter)
      l.interval = interval
   }}
}

间隔时间的设置,主要根据系统每次请求的耗时来决定,如果qps耗时比较低,间隔时间可以适当 变端

  • 根据生产token的间隔时间和qps数来计算出每次生成多少token
func (l *tokenBucketRateLimiter) calcOnce() {
   if l.interval > time.Second || l.interval == 0 {
      l.interval = 100 * time.Millisecond
}
   once := int32(float64(l.limit) / (fixedWindowTime.Seconds() / l.interval.Seconds()))
   if once < 1 {
      once = 1
   }
   l.once = once
   l.tokens = once
}
  • 新起一个协程生成token
func (l *tokenBucketRateLimiter) _createTokens() {
   if atomic.LoadInt32(&l.tokens) > l.limit {
      return
   }

   cur := atomic.LoadInt32(&l.tokens)
   if cur+l.once > l.limit {
      atomic.StoreInt32(&l.tokens, l.limit)
   } else {
      atomic.StoreInt32(&l.tokens, l.once)
   }
}
  • 获取token
func (l *tokenBucketRateLimiter) Take() bool {
   if atomic.LoadInt32(&l.tokens) <= 0 {
      return false
   }
   return atomic.AddInt32(&l.tokens, -1) >= 0
}

完整代码:

github.com/dqing0/rate…

总结

  • 令牌桶相对来说实现简单,且能比较好的应付突发流量
  • 令牌桶方式同样适用于分布式限流,可以部署专门负责生产、发放令牌桶的服务