令牌桶算法与Golang官方包的实现
概念
什么叫令牌桶算法?它有点像疫情期间你去公园玩,为了保证在公园里的人不超过一定的数量,你得在门口大爷那取号子,拿到号子才能进去玩,没号子你就不能进去。当然这例子有点不是太准确,感兴趣的话可以看下维基百科下的解释:https://en.wikipedia.org/wiki/Token_bucket
下面一幅图简单解释了令牌桶的工作流程:

为了统一,下面均将令牌称之为token。
作用
限流。是指限制到达系统的并发请求数,当达到限制条件则可以拒绝请求,可以起到保护下游服务,防止服务过载等作用。
使用
新建限流器方法:
1 | func NewLimiter(r Limit, b int) *Limiter { |
实例化一个限流器:
1 | package main |
上述初始化的意思是:构造了一个容量为50的的桶,并且以每秒10个token的速率往桶里放。
此外,官方还提供了Every方法来设置向桶里放token的时间间隔:
1 | // 每100ms放一个token,也就是每秒10个 |
Limiter主要用到这几个方法:
Wait(ctx context.Context) (err error)WaitN(ctx context.Context, n int) (err error)Allow() boolAllowN(now time.Time, n int) boolReserve() *ReservationReserveN(now time.Time, n int) *Reservation
其中,Wait/Allow/Reserve分别是WaitN(ctx, 1)/AllowN(time.Now(), 1)/ReserveN(time.Now(), 1)的简化形式
WaitN方法代表当桶内的token数量小于N时,则等待一段时间(超时时间可以通过context.withTimeout设置)。如果token数量充足,则从桶中消费掉这N个token,则直接返回。AllowN方法代表截止到某一时刻,判断桶中的token数量至少为N个,如果满足,则从桶中消费掉这N个token,返回true;否则直接返回false。ReserveN方法调用完成后,会返回一个*Reservation对象,你可以继续使用该对象的Delay方法或Cancel方法。
实际使用中,可以根据不同的场景使用不同的方法。比如:AllowN方法可以用在频繁访问场景中,超过一定的速率则直接拒绝访问。
当然你也可以动态调整限流器的速率和桶大小,使用如下方法:
SetLimit(newLimit Limit)SetBurst(newBurst int)
源码分析
官方限流器并没有通过队列来实现桶的逻辑,下面我们通过源码来看一下。
限流器的定义:
1 | type Limiter struct { |
这里有几个字段解释一下:
limit:其实就是float64的别名。它代表token入桶的频率,即每秒可以塞几个token到桶里面。burst:token桶的大小tokens:桶中剩余的token数量last: 上一次取走token的时间lastEvent: 上一次发生限流时间的时间
WaitN、AllowN、ReserveN这三个方法最终都调用了reserveN和advance方法,下面我们来看下这两个方法,我已将主要的注释标注上去了。
1 | func (lim *Limiter) advance(now time.Time) (newNow time.Time, newLast time.Time, newTokens float64) { |
可以看到,advance方法的作用是: 计算出当一个请求进来的时刻,当前可用的token数量,并返回请求时间和上一次token取走的时间
1 | func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { |
reserveN方法的作用是:判断这个请求能否获得想要数量的token(n),并更新这次请求后Limiter实例的状态。
从上面的分析可以看到,官方的限流器设计还是很精巧的,结合官方库下的测试用例看得话效果更好。(^o^)/~