golang 标准库 time/rate 介绍
标准库限流器 time/rate 使用介绍
golang官方库中有一个rate包,实现了令牌桶算法。仓库地址:https://github.com/golang/time
- 生成一个Limiter对象
func NewLimiter(r Limit, b int) *Limiter {
return &Limiter{
limit: r,
burst: b,
}
}
如上所示,其中r是往令牌桶中放令牌的速率,b是令牌桶的大小。
- 尝试获取token
例如:
func (lim *Limiter) Allow() bool {
return lim.AllowN(time.Now(), 1)
}
API很多,最终都是依靠 reserveN 来确定
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
lim.mu.Lock()
if lim.limit == Inf {
lim.mu.Unlock()
return Reservation{
ok: true,
lim: lim,
tokens: n,
timeToAct: now,
}
}
now, last, tokens := lim.advance(now)
// Calculate the remaining number of tokens resulting from the request.
tokens -= float64(n)
// Calculate the wait duration
var waitDuration time.Duration
if tokens < 0 {
waitDuration = lim.limit.durationFromTokens(-tokens)
}
// Decide result
ok := n <= lim.burst && waitDuration <= maxFutureReserve
// Prepare reservation
r := Reservation{
ok: ok,
lim: lim,
limit: lim.limit,
}
if ok {
r.tokens = n
r.timeToAct = now.Add(waitDuration)
} else {
panic(10)
}
// Update state
if ok {
lim.last = now
lim.tokens = tokens
lim.lastEvent = r.timeToAct
} else {
lim.last = last
}
lim.mu.Unlock()
return r
}
``
首先用一个常量来判断Limiter是否是无限大,上限是 math.MaxFloat64。
然后就是计算当前时间、上一次的时间和桶里剩余的token数量,这里在一种极端情况下,存在超出预期的情况,后面会介绍。然后就是计算是否是当前token不够,需要等待一段时间来获取令牌。
其中最值得关注的是 advance 方法
func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) {
last := lim.last
if now.Before(last) {
last = now
}
// Avoid making delta overflow below when last is very old.
maxElapsed := lim.limit.durationFromTokens(float64(lim.burst) - lim.tokens)
elapsed := now.Sub(last)
if elapsed > maxElapsed {
elapsed = maxElapsed
}
// Calculate the new number of tokens, due to time that passed.
delta := lim.limit.tokensFromDuration(elapsed)
tokens := lim.tokens + delta
if burst := float64(lim.burst); tokens > burst {
tokens = burst
}
return now, last, tokens
}
实现也很清晰,根据令牌桶里保存的上一次取令牌的时间,计算出两次时间的时间差,这里有个点是使用 durationFromTokens 方法来计算此时填满令牌桶需要的时间。取二者最小,避免delta过大,变量tokens溢出。
durationFromTokens是用来转换token生成的时间的工具方法,比如输入n个token 返回值是生成这n个token需要多少时间。
tokensFromDuration也是工具方法,用来转换一段时间能生成多少个token。
极端情况
当使用这个令牌桶时,如果burst为1,Limite为int32的最大值(2147483647)的时候,并发情况,会出现预期之外的情况。
在这个时候,上面两个工具函数的输入输出对应情况:
生成的token数量 | 需要的时间 |
---|---|
0 | 0 |
1 | 0 |
2 | 0 |
3 | 1ns |
4 | 1ns |
5 | 2ns |
6 | 2ns |
7 | 3ns |
耗时 | 生成的token数量 |
---|---|
0ns | 0 |
1ns | 2.147483647 |
2ns | 4.294967294 |
3ns | 6.442450941 |
可以留意到,如果是limit比较大,但是不是足够打,而burst足够小,会有一种情况,导致桶中的令牌会被迅速取完。
因为在rate包中burst代表的其实是令牌桶的大小。
当burst也就是桶的size很小,假设burst为1,在请求并发的时候,假设很多个请求拿到的time的纳秒时间都是相同,就会导致桶里的令牌被迅速取走,而时间戳相同,所以不会往桶里加令牌,这样后续的请求,即使在limit设置很高的情况下,也会返回false,需要等待至少1ns的时间。
这时候,可以增大burst,减少这种请求取令牌的时候time相同时迅速取完桶里的令牌的情况。