简介
上一篇文章 限流实现1 已经介绍三种限流方案
- 随机拒流
- 计数器方式
- 基于滑动时间窗口限流
剩下的几种本来打算能立即写完,没想到一下三个月过去了,很是尴尬。本次主要实现如下两种算法
- 令牌桶算法
- 漏斗算法
算法的具体实现可以在github上查看 https://github.com/shidawuhen/asap/tree/master/controller/limit
下面先来介绍一下这两种算法
令牌桶算法
算法说明
令牌桶算法(Token Bucket):是网络流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一种算法。令牌桶算法示意图如下所示:
一般的流程为:
a. 按特定的速率向令牌桶投放令牌
b. 根据预设的匹配规则先对报文进行分类,不符合匹配规则的报文不需要经过令牌桶的处理,直接发送;
c. 符合匹配规则的报文,则需要令牌桶进行处理。当桶中有足够的令牌则报文可以被继续发送下去,同时令牌桶中的令牌量按报文的长度做相应的减少;
d. 当令牌桶中的令牌不足时,报文将不能被发送,只有等到桶中生成了新的令牌,报文才可以发送。这就可以限制报文的流量只能是小于等于令牌生成的速度,达到限制流量的目的。
大小固定的令牌桶可自行以恒定的速率源源不断地产生令牌。如果令牌不被消耗,或者被消耗的速度小于产生的速度,令牌就会不断地增多,直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小。
令牌桶能允许突发,是因为令牌桶一般有两个值,一个是桶容量,一个是单位时间投放令牌量。如果桶容量大于令牌单位时间投放量,而且单位时间消耗量比投放量少,则令牌数量最终会达到桶容量最大值。此时如果大量请求到达,会将所有令牌消耗,实现了允许突发的效果。
算法实现
该算法有几个核心点
- 因为要更新令牌数量,所以需要加锁
- 定时向桶中放入令牌,有两种方式,一种是起goroutine,定时投放,另一种是在判断是否还有足够令牌的时候,根据投放情况进行投放。这次实现使用的是第二种方法,整个架构会更简单一些。
1 | package limit |
漏桶算法
算法说明
漏桶作为计量工具(The Leaky Bucket Algorithm as a Meter)时,可以用于流量整形(Traffic Shaping)和流量控制(TrafficPolicing),漏桶算法的描述如下:
- 一个固定容量的漏桶,按照常量固定速率流出水滴;
- 如果桶是空的,则不需流出水滴;
- 可以以任意速率流入水滴到漏桶;
- 如果流入水滴超出了桶的容量,则流入的水滴溢出了(被丢弃),而漏桶容量是不变的。
漏桶法限流很好理解,假设我们有一个水桶按固定的速率向下方滴落一滴水,无论有多少请求,请求的速率有多大,都按照固定的速率流出,对应到系统中就是按照固定的速率处理请求。
示意图如下:
算法实现
查阅了一下相关资料,主要的算法实现有三种。学习完这几种实现后,我怀疑我是不是理解错了,感觉还没有计数拒流方便好用。如果我理解的有误,大家也可以告诉我。
这三种方式为:
令牌桶算法变种
桶的大小就是单位时间内能流出的最大量,这种就不写了。
计数拒流变种
该方法在指定时间将可用空间置为初始值。
1 | package limit |
真固定速率
这个算法的的实现,根据uber团队开源的 github.com/uber-go/ratelimit 而来。
该实现保证如果有大量请求,每一个请求会按照规定的时间间隔执行。如设置1s处理100个请求,则每10ms会处理一个。
如果长时间没有请求,仍会产生请求在短时间内被处理完毕的情况。当然对于这种情况可以很容易修复,大家可以思考一下如何修改。
1 | package limit |
演示结果如下:
总结
学习了令牌桶和漏桶算法,结合这几年工作中的具体场景,感觉令牌桶算法的实用价值更大一些。
下面是令牌桶和漏桶对比:
- 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数减为零时则拒绝新的请求;
- 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数累积到漏桶容量时,则新流入的请求被拒绝;
- 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿3个令牌,4个令牌),并允许一定程度突发流量;
- 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次又是2),从而平滑突发流入速率;
- 令牌桶允许一定程度的突发,而漏桶主要目的是平滑流入速率;
- 两个算法实现可以一样,但是方向是相反的,对于相同的参数得到的限流效果是一样的。
最后,给大家展示一下各种限流算法的比较