业务中有时需要做一些优惠活动, 经常会带来较大流量,面对突然到来的爆发流量,除了缓存、消息队列、系统扩容、降级之外,也要考虑做限流来保障系统的稳定运行,避免整个系统崩溃。被限流的消息将直接被丢弃,并回复”系统繁忙,请稍后再试”。生活中也能看到相似的场景:家里的电闸一般会安装上保险丝,一旦有大功率用电,超出负载保险丝就会烧断,保护电器不被烧坏。同理我们也需要为我们的系统安装上『保险丝』。
常见的限流算法
常见的限流算法有两种:漏桶算法(Leaky Bucket) 和 令牌桶算法(Token Bucket)。
漏桶算法
漏桶算法的思路比较简单,水(请求)先进入到漏桶里,漏桶以一定的速度出水,当水流入速度过大就会直接溢出,可以看出漏桶算法能强行限制数据的传输速率。
上面为漏桶算法的示意图,漏桶算法可以很好的限制容量池的大小,从而防止流量暴增。
令牌桶算法
令牌桶算法的原理是系统会以一个恒定的速度往桶里放入令牌,如果请求需要被处理,则需要先从桶里获取一个令牌,当桶里没有令牌可取时,则拒绝服务。
令牌桶算法通过发放令牌,根据令牌的频率来做请求频率限制,容量限制等。
两种算法的区别
两者主要区别在于“漏桶算法”能够强行限制数据的传输速率,由于限制了传输速率,当面对突发流量时会大量拒绝。
而“令牌桶算法”在能够限制数据的平均传输速率外,可以允许某种程度的突发传输。在“令牌桶算法”中,只要令牌桶中存在令牌,那么就允许突发地传输数据直到达到用户配置的门限,所以它适合于具有突发特性的流量。
Golang中的令牌桶算法
Java中常用的是Google开源的 guava。在Golang中Google有提供实现:
1
go get golang.org/x/time/rate
实现代码:
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
29
30
package main
import (
"net/http"
"golang.org/x/time/rate"
)
var limiter = rate.NewLimiter(2, 5)
func limit(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !limiter.Allow() {
var httpCode = http.StatusTooManyRequests
http.Error(w, http.StatusText(httpCode), httpCode)
return
}
next.ServeHTTP(w, r)
})
}
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/", helloHandler)
http.ListenAndServe(":3000", limit(mux))
}
func helloHandler(w http.ResponseWriter, r *http.Request) {
w.Write([]byte("hello"))
}
源码实现可以查看 rate.NewLimiter。 参数配置r为发送速率,每秒会有r个令牌放入桶中,桶中最多可以存放b个令牌。如果令牌到达时令牌桶已经满了,那么这个令牌就会被丢弃。
示例代码: golang-token-bucket-ratelimit
这是一个全局的限制,实际应用中也会常见基于IP地址、ApiKey或用户ID标识进行限制的。