首先得知道什么是限流:
在日常生活中限流很常见,例如去有些景区玩,每天售卖的门票数是有限的。
例如 2000 张,即每天最多只有 2000 个人能进去游玩。
因为景区的管理员根据景区的各种参数,比如保安人数,比如景区面积等。
通过这些参数,来推导出这个景区一天最大能容纳多少人,于是就限定一天只卖这么多票。
我们程序也是同样的道理,我们得根据服务器的配置,你程序的性能综合评定,你每秒钟能处理多少请求。
从而对程序起到一定保护作用,不至于被一下涌来的大批量请求压垮我们的服务。
限流算法有很多,主流的有两个分别是 令牌桶算法 和 漏桶算法 。
今天要介绍的库 time/rate 是基于令牌桶算法编写的,所以漏桶算法先不做讲解。
我在百度百科上找了一张图,觉得比较形象,如下:
令牌桶算法的原理是系统以恒定的速率产生令牌,然后把令牌放到令牌桶中。
令牌桶有一个容量,当令牌桶满了的时候,再向其中放令牌时多余的令牌就会被丢弃;
当想要处理一个请求的时候,需要从令牌桶中取出一个令牌,如果此时令牌桶中没有令牌,那么则拒绝该请求。
有点像生产者和消费者的关系,和最开始说的景区卖票很类似。
景区每24小时产生 2000 张票,然后给顾客消费,顾客来买票的时候,还有余票就可以进去,卖完了就不让进。
我们使用的库是 go 官方的库,官方地址:golang.org/x/time/rate
此库只有两个文件,其中一个还是 test 测试文件。
安装非常简单,直接 go get 就好了:
go get golang.org/x/time/rate
根据文档我们首先需要先定义投放速度和桶的容量,代码如下:
var r rate.Limit
var limit *rate.Limiter
func main() {
// 一秒内产生10个令牌 1秒钟会执行10次 每次投放一个token
r = rate.Every(1*time.Second)
// 桶容量20个 预先缓冲20个
limit = rate.NewLimiter(r,20)
}
随后我们就只需要通过 limit.AllowN(time.Now(),1) 去消费验证就可以了。
像这种验证性的代码,我们最好是放到中间件里面,于是我们的完整代码如下:
func Rate() gin.HandlerFunc {
return func(c *gin.Context) {
// AllowN 方法表示,截止到某一时刻,目前桶中数目是否至少为 n 个
// 满足则返回 true,同时从桶中消费 n 个 token
// 反之返回不消费 token,false
// n 表示一次消费多少个
if limit.AllowN(time.Now(),2) {
log.Println("log:event happen")
c.Next()
}else{
log.Println("log:event not allow")
c.AbortWithStatusJSON(http.StatusBadRequest, gin.H{
"code": 400,
})
}
}
}
var r rate.Limit
var limit *rate.Limiter
func main() {
// 一秒内产生10个令牌 1秒钟会执行10次 每次投放一个token
r = rate.Every(100*time.Microsecond)
// 桶容量20个 预先缓冲20个
limit = rate.NewLimiter(r,20)
r := gin.New()
r.Use(Rate())
r.Handle("GET","/", func(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"code": 200,
})
})
if err:=r.Run(":9083");err!=nil {
panic(err)
}
}
对于使用这个库了解这么多基本就可以了。
在测试时,你可以加大 limit.AllowN(time.Now(),2) 里面的 2,让他每次消费的令牌数量多一点,或者调整产生令牌的时间间隔变长一点,也能很快达到出现 400 的情况。
在如今的实际生产中,由程序去限流已经变得很少了,大都是由网关去处理限流,所以了解下原理再去了解网关的限流会更加容易些。