共计 2502 个字符,预计需要花费 7 分钟才能阅读完成。
本篇文章承接上一篇 go-zero 如何扛住流量冲击(一)。
上一篇介绍的是 go-zero
中滑动窗口限流,本篇介绍另外一个 tokenlimit
,令牌桶限流。
应用
const (
burst = 100
rate = 100
seconds = 5
)
store := redis.NewRedis("localhost:6379", "node", "")
fmt.Println(store.Ping())
// New tokenLimiter
limiter := 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 int32
var wait sync.WaitGroup
for i := 0; i < runtime.NumCPU(); i++ {wait.Add(1)
go func() {
for {
select {
case <-quit:
wait.Done()
return
default:
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
管制的几个要害属性:
argument | mean |
---|---|
ARGV[1] | rate「每秒生成几个令牌」 |
ARGV[2] | burst「令牌桶最大值」 |
ARGV[3] | now_time「以后工夫戳」 |
ARGV[4] | get token nums「开发者须要获取的 token 数」 |
KEYS[1] | 示意资源的 tokenkey |
KEYS[2] | 示意刷新工夫的 key |
-- 返回是否能够活取得预期的 token
local 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 then
last_tokens = capacity
end
-- 上一次更新 token_bucket 的工夫
local last_refreshed = tonumber(redis.call("get", KEYS[2]))
if last_refreshed == nil then
last_refreshed = 0
end
local 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 >= requested
local new_tokens = filled_tokens
if allowed then
new_tokens = filled_tokens - requested
end
-- 更新新的 token 数,以及更新工夫
redis.call("setex", KEYS[1], ttl, new_tokens)
redis.call("setex", KEYS[2], ttl, now)
return allowed
上述能够看出 lua script
:只波及对 token 操作,保障 token 生产正当和读取正当。
函数剖析
从上述流程中看出:
- 有多重保障机制,保障限流肯定会实现。
- 如果
redis limiter
生效,至多在过程内rate limiter
兜底。 - 重试
redis limiter
机制保障尽可能地失常运行。
总结
go-zero
中的 tokenlimit
限流计划实用于刹时流量冲击,事实申请场景并不以恒定的速率。令牌桶相当预申请,当实在的申请达到不至于霎时被打垮。当流量冲击到肯定水平,则才会依照预约速率进行生产。
然而生产 token
上,不能依照过后的流量状况作出动静调整,不够灵便,还能够进行进一步优化。此外能够参考 Token bucket WIKI 中提到分层令牌桶,依据不同的流量带宽,分至不同排队中。
参考
- go-zero tokenlimit
- Go-Redis 提供的分布式限流库
如果感觉文章不错,欢送 github 点个 star ????
同时欢送大家应用 go-zero
,https://github.com/tal-tech/g…
我的项目地址:
https://github.com/tal-tech/go-zero
正文完