常用的限流策略简介——以漏桶和令牌桶为例
这部分内容参考并搬运自 q1mi 老师的技术博客,原文的链接为:https://liwenzhou.com/posts/Go/ratelimit/。
限流
限流又称流量控制(流控),通常是指限制到达系统的并发请求数。
互联网上需要限流的业务场景非常多,比如电商系统的秒杀、微博上突发热点新闻、双十一购物节、12306抢票等等。这些场景下的用户请求量通常会激增,远远超过平时正常的请求量,此时如果不加任何限制很容易就会将后端服务打垮,影响服务的稳定性(限制一部分用户的请求总比所有所有用户的服务都宕掉要好,至少保证了一部分用户的体验)。
常用的限流策略
漏桶
漏桶法限流很好理解,假定我们有一个水桶,按照固定的速率向下方滴水,无论有多少请求,无论请求的速率有多大,都按照固定的速率流出,对应到系统当中就是按照固定的速率处理请求。【感觉非常像百度网盘对非会员的限流策略,不论请求有多少、请求的速率有多大,都按照固定的速率流出】
漏桶法的关键点在于漏桶始终按照固定的速率运行,但是它并不能很好的处理有大量突发请求的场景,毕竟在某些场景下我们可能需要提高系统的处理效率,而不是一味的按照固定速率处理请求。
令牌桶
令牌桶与漏桶的原理类似。令牌桶按照固定的速率将令牌放入桶内,并且只要能从桶里取出令牌,就可以进一步对请求进行处理,令牌桶支持突发流量的快速处理。
如果从桶中取不出令牌,那么可以选择等待,也可以直接拒绝请求并返回错误码。
在 GIN 框架中使用限流中间件
现在我们使用令牌桶作为限流策略,编写一个限流中间件,在 GIN 框架当中使用它,并应用到 bluebell 项目上:
限流策略使用的是令牌桶,我们没有重新造轮子,而是使用现成的由其它开发者开发的软件包,使用下述命令安装:
go get -u github.com/juju/ratelimit
之后,我们开始编写限流中间件。GIN 的中间件是一个函数,它以 *gin.Context
为参数:
func RateLimitMiddleware(fillInterval time.Duration, cap int64) func(c *gin.Context) {
bucket := ratelimit.NewBucket(fillInterval, cap)
return func(c *gin.Context) {
// 如果取不到令牌就返回响应
if bucket.TakeAvailable(1) == 0 {
c.String(http.StatusOK, "rate limit...")
c.Abort()
return
}
c.Next()
}
}
我们来看一下ratelimit.NewBucket()
的文档:
NewBucket returns a new token bucket that fills at the rate of one
token every fillInterval, up to the given maximum capacity. Both
arguments must be positive. The bucket is initially full.
翻译过来就是,NewBucket 返回一个新的令牌桶对象,它会在每一个 fillInterval 时间间隔(它是一个time.Duration
类型)向桶中填充一个 token,直到达到令牌桶的上限(即第二个参数 cap)。这两个参数都必须是正的值。在开始时,令牌桶为空。
现在我们在 GIN 框架下使用限流中间件:
r.Use(logger.GinLogger(), logger.GinRecovery(true), middleware.RateLimitMiddleware(2*time.Second, 1))
r.Use
的参数是HandlerFunc...
,因此它理论上支持无限拓展中间件。我们已经使用了 GinLogger 和 GinRecovery 两个中间件,它们的作用是使用 zap 作为默认的日志库对 web 服务进行日志记录,在此基础上,我们为全局添加一个限流中间件,并设置好参数。
此时,我们随意在浏览器访问一个接口,重复访问将会得到"rate limit..."
的提示:
至此,我们根据 q1mi 老师的技术博客,了解了限流的概念以及在 web 服务中限流的必要性。我们了解了漏桶和令牌桶两种常见的限流策略,令牌桶可以处理突发流量的场景,这一点比漏桶机制更优。我们进一步在 GIN 框架下为全局添加了一个限流中间件,并在 bluebell 项目中对 api 进行了限流测试。