在高并发系统中有三个常见的利器:缓存、限流、降级
- 缓存:提升系统访问的速度和增大处理的容量
- 降级:当服务器的压力剧增时,可以根据业务策略进行降级、以此释放服务资源保证业务正常
- 限流:通过对并发降速以达到拒绝服务、排队或等待、降级等处理
限流器,从字面上理解就是用来限制流量,有时候流量突增比如秒杀活动,会将后端服务压垮,甚至直接宕机,使用限流器能限制访问后端的流量,起到一个保护作用,被限制的流量,可以根据具体的业务逻辑去处理,直接返回错误或者返回默认值等等
限流方法
目前主流的限流方法有两个:
漏桶限流:
每次请求的时候计算桶的流量,当超过桶的流量时就降级请求
可以由上图看到,水龙头的速率就是我们的服务器接收到的请求,当请求速率过大的时候,漏桶会在短时间内迅速装满,然后就会拒绝其他的请求,水就会漫出桶外,对应的请求就会被丢弃,漏桶算法可以强行限制请求的访问以及数据的传输速率。
令牌桶限流:
每次请求的时候从令牌桶里面拿令牌,取不到令牌就降级请求
令牌通算法以一定恒定的速度往桶里面放入令牌,比如放入1000个令牌,每来一次请求,就从令牌通里面拿一个令牌,当令牌被拿完之后,其他服务就拒绝或者等待服务。令牌桶会不断地生成令牌直到把桶填满。后面再产生的令牌就会从桶中溢出。最后桶中可以保存的最大令牌数永远不会超过桶的大小
time/rate限速器
golang内部有一个非常好用的限流器time/rate位于golang.org/x/time/rate
下面主要讲解一下他的一些函数:
rate.NewLimiter(limit,burst)
limit表示每秒产生的token数,burst表示最多存储的token数,这个函数就可以与上方的令牌桶限流的图形成对照。
//例如: 每秒产生200*cpu个数个令牌,最多存储200*cpu个数个令牌。
limiter = rate.NewLimiter(rate.Limit(200.NumCPU()), 200*runtime.NumCPU())
对应的也有对应的三个使用方法:
- Allow: 返回是否有 token,没有 token 返回 false,如果有token就会消耗 1 个 token 返回 true
- Wait: 阻塞等待,直到取到 1 个 token
- Reserve: 返回 token 信息,此外还会返回需要等待多久才有新的 token
其实这三种方法最终都是调用了ReserveN()方法
Wait()方法的调用过程如下:
Wait()会调用WaitN(),WaitN()也会调用ReserveN()
而Allow()方法最终也是调用了ReserveN().ok
// Allow is shorthand for AllowN(time.Now(), 1).
func (lim *Limiter) Allow() bool {
return lim.AllowN(time.Now(), 1)
}
func (lim *Limiter) AllowN(now time.Time, n int) bool {
return lim.reserveN(now, n, 0).ok
}
应用demo:
我们创建一个路由的方法,逻辑就是创建一个限流器,当请求不被allow的时候就会打印出当前的限流器的信息,否则会执行对应的服务器方法
func RateLimiter() func(c *SliceRouterContext) {
l := rate.NewLimiter(1, 2)
return func(c *SliceRouterContext) {
if !l.Allow() {
c.Rw.Write([]byte(fmt.Sprintf("rate limit:%v,%v", l.Limit(), l.Burst())))
c.Abort()
return
}
c.Next()
}
}
以下代码是创建一个反向代理服务器,把请求转发到2003和2004端口,并在转发的过程中添加限流的方法
var addr = "127.0.0.1:2002"
// 熔断方案
func main() {
coreFunc := func(c *middleware.SliceRouterContext) http.Handler {
rs1 := "http://127.0.0.1:2003/base"
url1, err1 := url.Parse(rs1)
if err1 != nil {
log.Println(err1)
}
rs2 := "http://127.0.0.1:2004/base"
url2, err2 := url.Parse(rs2)
if err2 != nil {
log.Println(err2)
}
urls := []*url.URL{url1, url2}
return proxy.NewMultipleHostsReverseProxy(c, urls)
}
log.Println("Starting httpserver at " + addr)
sliceRouter := middleware.NewSliceRouter()
sliceRouter.Group("/").Use(middleware.RateLimiter())
routerHandler := middleware.NewSliceRouterHandler(coreFunc, sliceRouter)
log.Fatal(http.ListenAndServe(addr, routerHandler))
}
我们把2003、2004端口的服务器以及反向代理服务器运行起来,然后把请求发送给反向代理服务器
我们使用curl模拟请求,可以看到如果我们请求的速度过快就会打印**rate limit:1,2%**的信息
➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2004/base/
RemoteAddr=127.0.0.1:53291,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2004/base/
RemoteAddr=127.0.0.1:53291,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2004/base/
RemoteAddr=127.0.0.1:53291,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
➜ cool-boy-klay curl '127.0.0.1:2002/'
rate limit:1,2% ➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2004/base/
RemoteAddr=127.0.0.1:53291,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
➜ cool-boy-klay curl '127.0.0.1:2002/'
rate limit:1,2% ➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2004/base/
RemoteAddr=127.0.0.1:53291,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
➜ cool-boy-klay curl '127.0.0.1:2002/'
http://127.0.0.1:2003/base/
RemoteAddr=127.0.0.1:53317,X-Forwarded-For=127.0.0.1,X-Real-Ip=
headers =map[Accept:[*/*] Accept-Encoding:[gzip] User-Agent:[curl/7.54.0] X-Forwarded-For:[127.0.0.1]]
参考链接:
常见限流算法之漏桶算法、令牌桶算法