常见的限流算法
常用的限流算法有:漏桶算法(Leaky Bucket)和令牌桶算法(Token Bucket)。
漏桶算法
漏桶(Leaky Bucket)算法思路很简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水(接口有响应速率),当水流入速度过大会直接溢出(访问频率超过接口响应速率),然后就拒绝请求,可以看出漏桶算法能强行限制数据的传输速率。
令牌桶
随着时间流逝,系统会按恒定1/QPS时间间隔(如果QPS=100,则间隔是10ms)往桶里加入Token(想象和漏洞漏水相反,有个水龙头在不断的加水),如果桶已经满了就不再加了。新请求来临时,会各自拿走一个Token,如果没有Token可拿了就阻塞或者拒绝服务。
漏桶漏出的速率是固定参数的,流出速率是多少,流入的速率也是多少,对突发特性的流量处理缺乏效率。而令牌桶只要桶中有令牌就可以拿,允许一定程度的并发,比如同一个时刻,有100个用户请求,只要令牌桶中有100个令牌,那么这100个请求全都会放过去。令牌桶在桶中没有令牌的情况下也会退化为漏桶模型。另外,令牌桶还可以根据需求,提高放入令牌存放速率。
Golang实现
Leaky Bucket
实际应用中令牌桶应用较为广泛,漏桶使用较少。uber开源实现过一个基于请求之间的时间,而不是单纯的间隔时间来填充的漏桶:https://github.com/uber-go/ratelimit
1 | import ( |
每次请求的时间间隔为10ms。其中Sub()方法是时间减,即now-prev,所以主要实现在rl.Take(),如下:
1 | type limiter struct { |
记录每次请求的时间,若当前时间减上一次请求时间小于设置的间隔时间t.sleepFor += t.perRequest - now.Sub(t.last),则休眠t.sleepFor的时间,若大于则记录当前时间。
Token Bucket
令牌桶模型实际上就是对全局计数的加减法操作过程,但使用计数需要我们自己加读写锁,很容易想到可以用buffered channel来完成简单的加令牌取令牌操作:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29package main
import (
"fmt"
"time"
)
func main() {
var fillInterval = time.Millisecond * 10
var capacity = 100
var tokenBucket = make(chan struct{}, capacity)
fillToken := func() {
ticker := time.NewTicker(fillInterval)
for {
select {
case <-ticker.C:
select {
case tokenBucket <- struct{}{}:
default:
}
fmt.Println("current token cnt:", len(tokenBucket), time.Now())
}
}
}
go fillToken()
time.Sleep(time.Hour)
}
每10ms放入1个token,若token已满,则直接放弃。
请求进来时,获取token:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18func TakeAvailable(block bool) bool{
var takenResult bool
if block {
select {
case <-tokenBucket:
takenResult = true
}
} else {
select {
case <-tokenBucket:
takenResult = true
default:
takenResult = false
}
}
return takenResult
}
若桶未满,则取出一个token,返回true,若桶已满,取到令牌则返回true,没取到令牌则返回false。
除了以上实现方法外,github.com/juju/ratelimit还提供了一种高效的实现方式。
想象以下,每隔一段固定时间向令牌桶中放令牌,如果我们记下上一次放令牌的时间t1,和当时的令牌数k1,放令牌的时间间隔未ti,每次向令牌桶中放x个令牌,桶的容量为cap。现有人来取n个令牌,记此时的时间为t2,在t2时刻,令牌桶中的令牌数为多少呢:1
2cur = k1 + ((t2-t1)/ti) * x
cur = cur > cap ? cap : cur
使用两个时间的时间差,就可以计算出桶中有多少令牌了。不需要填充token的操作,只需要每个Take的时候进行简单的计算就能得到正确的令牌数。
参考:
https://github.com/uber-go/ratelimit
https://en.wikipedia.org/wiki/Token_bucket
https://chai2010.cn/advanced-go-programming-book/ch5-web/ch5-06-ratelimit.html