go-zero 如何扛住流量冲击(二)

wx607fee5b9d0e6 · · 318 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

本篇文章承接上一篇go-zero 如何扛住流量冲击(一)

上一篇介绍的是 go-zero 中滑动窗口限流,本篇介绍另外一个 tokenlimit ,令牌桶限流。

使用

const (
	burst   = 100
	rate    = 100
	seconds = 5)

store := redis.NewRedis("localhost:6379", "node", "")
fmt.Println(store.Ping())// New tokenLimiterlimiter := limit.NewTokenLimiter(rate, burst, store, "rate-test")
timer := time.NewTimer(time.Second * seconds)
quit := make(chan struct{})defer timer.Stop()go func() {
  <-timer.C  close(quit)
}()var allowed, denied int32var wait sync.WaitGroupfor i := 0; i < runtime.NumCPU(); i++ {
  wait.Add(1)  go func() {for {      select {case <-quit:
          wait.Done()          returndefault:          if limiter.Allow() {
            atomic.AddInt32(&allowed, 1)
          } else {
            atomic.AddInt32(&denied, 1)
          }
      }
    }
  }()
}

wait.Wait()
fmt.Printf("allowed: %d, denied: %d, qps: %d\n", allowed, denied, (allowed+denied)/seconds)复制代码

tokenlimit

从整体上令牌桶生产token逻辑如下:

  • 用户配置的平均发送速率为r,则每隔1/r秒一个令牌被加入到桶中;
  • 假设桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌会被丢弃;
  • 当流量以速率v进入,从桶中以速率v取令牌,拿到令牌的流量通过,拿不到令牌流量不通过,执行熔断逻辑;

go-zero 在两类限流器下都采取 lua script 的方式,依赖redis可以做到分布式限流,lua script同时可以做到对 token 生产读取操作的原子性。

下面来看看 lua script 控制的几个关键属性:

argumentmean
ARGV[1]rate 「每秒生成几个令牌」
ARGV[2]burst 「令牌桶最大值」
ARGV[3]now_time「当前时间戳」
ARGV[4]get token nums 「开发者需要获取的token数」
KEYS[1]表示资源的tokenkey
KEYS[2]表示刷新时间的key
-- 返回是否可以活获得预期的tokenlocal rate = tonumber(ARGV[1])local capacity = tonumber(ARGV[2])local now = tonumber(ARGV[3])local requested = tonumber(ARGV[4])-- fill_time:需要填满 token_bucket 需要多久local fill_time = capacity/rate-- 将填充时间向下取整local ttl = math.floor(fill_time*2)-- 获取目前 token_bucket 中剩余 token 数-- 如果是第一次进入,则设置 token_bucket 数量为 令牌桶最大值local last_tokens = tonumber(redis.call("get", KEYS[1]))if last_tokens == nil thenlast_tokens = capacityend-- 上一次更新 token_bucket 的时间local last_refreshed = tonumber(redis.call("get", KEYS[2]))if last_refreshed == nil thenlast_refreshed = 0endlocal delta = math.max(0, now-last_refreshed)-- 通过当前时间与上一次更新时间的跨度,以及生产token的速率,计算出新的token数-- 如果超过 max_burst,多余生产的token会被丢弃local filled_tokens = math.min(capacity, last_tokens+(delta*rate))local allowed = filled_tokens >= requestedlocal new_tokens = filled_tokensif allowed thennew_tokens = filled_tokens - requestedend-- 更新新的token数,以及更新时间redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)return allowed复制代码

上述可以看出 lua script :只涉及对 token 操作,保证 token 生产合理和读取合理。

函数分析

从上述流程中看出:

  1. 有多重保障机制,保证限流一定会完成。
  2. 如果redis limiter失效,至少在进程内rate limiter兜底。
  3. 重试 redis limiter 机制保证尽可能地正常运行。

总结

go-zero 中的 tokenlimit 限流方案适用于瞬时流量冲击,现实请求场景并不以恒定的速率。令牌桶相当预请求,当真实的请求到达不至于瞬间被打垮。当流量冲击到一定程度,则才会按照预定速率进行消费。

但是生产token上,不能按照当时的流量情况作出动态调整,不够灵活,还可以进行进一步优化。此外可以参考Token bucket WIKI中提到分层令牌桶,根据不同的流量带宽,分至不同排队中。

参考

如果觉得文章不错,欢迎 github 点个star ???? 。同时欢迎大家使用 go-zerogithub.com/tal-tech/go…


有疑问加站长微信联系(非本文作者)

本文来自:51CTO博客

感谢作者:wx607fee5b9d0e6

查看原文:go-zero 如何扛住流量冲击(二)

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

318 次点击  ∙  1 赞  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传