限流是啥?
维基百科是这样解释的:
在计算机网络中,频率限制被应用在控制网络接口收到或发送的请求频次,它可以被用来阻止dos攻击或者是网络爬虫。
直白点说,就是限制服务收到或发出的请求频次,保证整体服务可以正常健康的使用。
谈到这里有人会想,只要我服务处理的速度足够快,那么频次高点也没问题,而且我们做的系统不就应该接受各种峰值考验么?
这么想没问题,提高服务的吞吐量确实是没问题,但是有时出于安全角度考虑,比如爬虫,就不应该让他过快的获取到抓取的数据,还可能涉及到洗钱网络安全什么的。
另外一方面,需求的开发是有成本的,不应该想当然,在超出系统本来承受打范围后,还有数量级差异的请求进来,这种需求改进的成本无论是人工还是费用都是巨大的。
因此限流是肯定有存在必要的。下面我们来谈谈经典的限流都有哪些:
假设我们要实现1分钟,100次的限流:
1、计数器限流(固定窗口限流)
这个算法简单实用,基本可以满足很多业务场景的需要
大致原理是这样的,有一个全局的计数器,初始值为0,每次请求进来,计数器+1,然后再处理,如果计数器超过100,则不再处理请求,直接返回。
同时我们启动一个定时的线程,每过1分钟,就重置计数器为0;
流程大概是下边这个样子:
但是这种计数器限流有一个临界的问题,我们初衷其实上是,任意的一分钟范围内,处理的请求不超过100。
但是假若第一分钟最后10s处理100个请求,第二分钟最初10s处理100个请求,也就是说20s内,我们共处理了200请求,这显然是不满足最初我们的诉求的。
2、滑动窗口
为了解决传统计数器限流的弊端,有人提出了滑动窗口的概念。所谓的滑动窗口,是指不存在固定的起止、结束的时间点。而是当前的时间点向前推动单位周期,判定流量是否超限:(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
举个例子,比如1min,100次的限流
我们将时间划分成10s每个单位
请求如下图,在窗口1期间,请求已经到达60次,因此后两个时间单位内就进入限流状态。
但是随着时间推移,窗口2期间的请求次数再次小于了100,又可以处理请求。
继续推移,窗口3的请求次数再次到达100,因此又被限流。
滑动窗口如何实现呢?
我们可以使用一个HashMap或者干脆使用一个数组,然后统计每个时间单位内的请求,当判断是否限流时,只要统计时间窗内的请求次数是否满足即可。
同时map或者是数组我们并不需要无限大,我们可以反复利用其中已经过期的位置。另外滑动窗口的单位时间越小,整体的限流也就会越精准。
3、漏桶算法
滑动窗口的结尾我们有说时间单位越小,那么就越精准平滑,可是我们并不能把时间单位搞到无限小,或者说尽可能小的时候就出现偏差,比如1ns,但是可能你刚计算完限流后,已经到下一个时间单位了
为了解决这种问题,有人提出了漏桶算法。
如图,这个模型和消息队列有点像:
请求进来后,会先放置在漏桶中,漏桶最多保存的请求是有限的。当漏桶满了以后,会丢弃请求,否则会添加到漏桶的队尾。
同时任务处理会恒定的处理漏桶队列中的请求。以此达到平衡。漏桶算法也并不一定需要一个列队,我们也可以使用一个计数器,当请求进来后,计数器+1。计数器满了以后会请求丢弃。同时任务处理完毕后会回调计数器-1;(防盗连接:本文首发自http://www.cnblogs.com/jilodream/ )
4、令牌桶算法
漏桶算法会让请求更加平滑,但是当有突发请求冲击时,并且我们的下游处理速度又比较快(或者是使用固定频率)来处理,这样我们没有办法很好的控制请求的处理速率。
如果你设置固定频率,当某个时段请求冲击比较大,而我们也应该满足时,显然无法满足。当放开请求频率,又可能因为下游处理速度太快,对机器的整体性能造成冲击,而且我们又无法控制。
针对这种情况,有人提供了令牌桶算法:
如图
1、有线程会按照固定频率向令牌桶中添加令牌,当令牌桶满了之后,就不会继续添加。
2、请求来了之后,向令牌桶中请求令牌,成功拿到令牌可以继续处理请求
3、未请求到令牌,则丢弃该请求
不知道大家有没有是否记得有一款腾讯的小游戏叫开心消消乐。其中的防沉迷策略就是通过令牌桶的算法处理的。玩家每次玩游戏会消耗一份闪电(令牌),可以连续玩,直到所有闪电都消耗完毕。
同时随着时间的推移,闪电又会慢慢变满,但是最多不会超过上限(令牌桶满)。在这个增加的过程中,我们也可以同时去玩消消乐消耗令牌