常见的限流算法有:令牌桶、漏桶、计数器。
令牌桶
令牌桶算法:
- 一个存放固定容量令牌的桶(假设容量为n),
- 按照固定速率往桶里添加令牌(假设限速为10r/s,则按照100毫秒的固定速率往桶中添加令牌,当桶满时,新添加的令牌会被丢弃),
- 请求过滤之后,需要先从桶里获取一个令牌,如果获取到令牌,则进行业务处理;如果获取不到则拒绝服务。
例子:Google开源项目Guava中的RateLimiter使用的就是令牌桶算法
漏桶
- 一个固定容量的漏桶,按照固定速率流出水滴,如果桶是空的,则不需要流出水滴
- 以任意速率流入水滴到漏桶,如果流入水滴超出了桶的容量,则流入的水滴溢出(被丢弃)
例子:nginx的limit_req_zone 用来限制单位时间内的请求数,即速率限制,采用的漏桶算法 "leaky bucket"
令牌桶和漏桶算法比较:
- 令牌桶是按照固定速率往桶中添加令牌,请求是否被处理需要看桶中令牌是否足够,当令牌数量为0时,则拒绝新的请求。
- 漏桶则是按照常量固定速率流出请求,流入请求速率任意,当流入的请求数量累计到漏桶容量时,则新流入的请求被拒绝。
- 令牌桶限制的是平均流入速率(允许突发请求,只要有令牌就可以处理,支持一次拿n个令牌),并允许一定程度的突发流量。
- 漏桶限制的是常量流出速率(即流出速率是一个固定常量值,比如都是1的速率流出,而不能一次是1,下次是2),从而平滑突发流入速率。
计数器
采用计数器实现限流有点简单粗暴,一般我们会限制一秒钟的能够通过的请求数,比如限流qps为100,算法的实现思路就是从第一个请求进来开始计时,在接下去的1s内,每来一个请求,就把计数加1,如果累加的数字达到了100,那么后续的请求就会被全部拒绝。等到1s结束后,把计数恢复成0,重新开始计数。
具体的实现可以是这样的:对于每次服务调用,可以通过 AtomicLong#incrementAndGet()方法来给计数器加1并返回最新值,通过这个最新值和阈值进行比较。
这种实现方式,相信大家都知道有一个弊端:如果我在单位时间1s内的前10ms,已经通过了100个请求,那后面的990ms,只能眼巴巴的把请求拒绝,我们把这种现象称为“突刺现象”。
参考https://blog.csdn.net/weixin_41846320/article/details/95941361