github.com/golang/time
Golang官方的事件频率限制器,可以用来限制事件发生的频率,平时项目中可以用来做网关层的限流中间件,也可以用来做请求后台资源限制的中间件。
令牌桶频率限制算法
令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)、根据限流大小,设置按照一定的速率往桶里添加令牌;
3)、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流。
使用示例
// 初始化一个限制器,一般在主协程初始化,这样所有协程共享总的事件限制
limiter := NewLimiter(1000, 1000)
// 具体的每一个协程触发,保证协程进行有效消耗token
if !limiter.Allow() {
// 如果被限制,则返回403,或者其他业务状态
}
复制代码
源码分析
数据结构
type Limiter struct {
mu sync.Mutex
limit Limit
burst int
tokens float64
// last is the last time the limiter's tokens field was updated
last time.Time
// lastEvent is the latest time of a rate-limited event (past or future)
lastEvent time.Time
}
复制代码
mu:主要用来保证并发安全,防止出现竞争访问同一模块数据;
limit:速率,可以用来表示我们常见的TPS、QPS;在限制器是表示生成token的速率;
burst:桶的容量,可以表示最大的并发数量;
tokens:当前桶中token的数量;
last:则代表最早的请求消耗token的时间,每次请求会修改,每次计算token容量都会进行修正;
核心算法函数
整个令牌桶事件频率控制算法就在以下函数实现的;
下面保留了主要的计算逻辑,如何计算当前桶中token的容量?如何计算token消耗的数量?以及相关的临界值校验。
func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation {
lim.mu.Lock()
...
// 当前时间、开始时间、token数量
now, last, tokens := lim.advance(now)
// 计算请求带来的消耗
// Calculate the remaining number of tokens resulting from the request.
tokens -= float64(n)
...
// 当前请求是否可以通过
ok := n <= lim.burst && waitDuration <= maxFutureReserve
...
if ok {
lim.last = now
lim.tokens = tokens
...
} else {
lim.last = last
}
}
复制代码
主要流程:
1、计算当前时间,限流器有效的最早时间,当前桶中token的容量
2、计算当前的消耗后的token数量
3、最后更新限流器相关的数据
注意事项
1、首先是限制器最早有效时间,如果当前时间更早,则进行更新
2、计算时间消耗,如果时间大于允许最大消耗的时间(通过当前桶中的token数量计算),则进行重置
3、计算当前桶中token的数量=已经过去的时间*速率;修正数据,如果大于桶的容量,则重置为桶的最大容量
总结
golang/time包还有其他Reserve(),Wait(ctx context.Context)等函数;Wait()主要是通过上下文和时间计数器实现可以等待的功能,一般如果仅是用来做限流功能,使用Allow()函数即可满需求。