在微服务中,为了防止在高并发场景下被大流量冲击,保护下游服务,防止服务过载,需要有控制并发请求数的手段. 限流器常用的算法有令牌桶/漏桶算法/自适应限流. golang 标准库中自带的限流算法的实现,基于令牌桶算法 rate.
令牌桶可以想象成往一个固定容量大小的桶中放 Token(可以理解成门票),系统会以固定速率往桶中放 Token,桶满了则不放.用户从桶中去 Token,如果有 Token(门票)则可以一直取. 如果没有 Token(门票)则需要等待系统发放. 令牌桶在一段时间的总 Token(门票)数是固定的,但峰值数量可以达到桶的容量.
golang time rate 的使用
初始化
1 | // 第一个参数 r 代表每秒放入的令牌数 |
获取 Token 方法
1 | // WaitN/Wait |
三种方法的使用场景:
- 如果不想 drop event,可以使用 Reserve,根据返回的Reservation等待一定时间后执行
- 如果需要遵守最后期限或取消延迟,使用 Wait
- 如果可以允许丢弃或者忽略超过速率的事件,使用 Allow
实现原理
令牌桶原理图
看起来是有一个goroutine定时放一个队列中放入令牌,我最开始也是这样认为的,这样的好处是比较简单直观.
在 go 中是采用lazyload的方式实现的,通过记录最近一次消费时间跟当前时间进行对比更新Token数目,同时Token数目也不是使用队列,而是通过计数来实现,节省内存.
三种消费方式底层都是通过 reserveN 来实现的.
为什么能通过记录消费时间和计数来实现呢?主要是通过 Token 数可以和时间相互转化.通过limit可以得到以下信息:
- 生成N个Token需要多少时间
- 在一段时间内,可以生成多少个Token
根据以上两个信息和最近获取时间,当前获取时间来计算获取 n个 Token 需要等待的时间.
参考资料: