常用的限流方式和场景有:
我们常说的限流,其实更多的都是指时间窗口内的平均速率,所以往往这种限流方式成了限流的代名词了。这里主要说明的也是这种时间窗口内的平均速率的限制,guava的RateLimiter解决的也是这个场景的限流。
这个最简单也最好理解的一个窗口平均速率限流算法,当然实际生产证也没人使用的一个算法。它是将时间划分成一个一个固定的时间段,然后时间段中绑定一个计数器,记录这个时间段内的请求数,当时间段内的请求数到达设定阈值之后,再有请求过来,就直接拒绝;当达到时间段结束点后,即在每个时间段的起始位置,清0计数器。
其基本实现:
所以固定窗口计数法限流需要的记录的几个变量:
固定窗口计数法的优点就是简单,但因为简单也有一些问题,这些所有的问题的本质都是:限流器假设了流量是均匀到来的,但实际上并不是。
滑动窗口计数的思路是
举个例子:
滑动窗口通过将限流窗口系分成更小的计数窗口,更加精细化的来统计请求量,从而避免了固定窗口在窗口移动处可能存在将最大两倍于限流阈值的流量放过的问题。
其实当限流窗口中划分的小的计数窗口的个数为1的时候,滑动窗口就退化成了固定窗口计数了。
Tcp的限流就是基于滑动窗口来做的。
落到具体的实现,滑动窗口计数器需要保存的数据:
为了方便,有的时候还会记录下,当前计数窗口的索引。实现这个的时候一定要注意,限流窗口是一个时间窗口,而时间是会不停的向前流失的。
ps:图片来源于网络
我们常说的使用mq的一个作用就是削峰填谷、平滑流量,其实mq在这个地方充当的其实就是一个漏桶。
漏桶算法在限流方面的作用主要就是流量整形。对于外部进来的流量大小不可预知,但是漏桶的流出速率是一个可控制的恒定的均匀的速率,从而达到流量整形的目的。
所以实现漏桶算法就是一个队列:
ps:对应到具体的实现上,其实也可以记录下一次可出队的时间来实现。比如限流限制成1s只能访问5次,那么当请求到达时间为T1,就记录下T1+200ms,当再次有请求到达的时候,比较当前时间和记录下时间,只有当前时间大于记录下的时间,可直接访问,否则阻塞等待直到记录下的下次可访问时间,最后修改这个下次可访问时间为当前时间+200ms。
漏桶算法可实现限流的目的,达到系统不被压垮的目的,但是对于突发流量来说,漏桶算法是缓存在漏桶中的,超过漏桶的容量的请求就会被丢弃。这对应对突发流量的应对能力相对弱一些
ps:图片来源于网络
以恒定速率往令牌桶中添加令牌,在请求到达的时候,先去令牌桶中获取一个令牌,如果令牌桶为空,获取不到令牌,则说明触发了限流规则,阻塞等,图中的queue就是阻塞等待队列。如果获取到了令牌,就交给业务系统去处理。
令牌桶这里达到限流的目的是通过令牌桶容量和生产令牌的速率来控制的,令牌桶的生产速率就是正常的限流值,比如1s内访问5次,那么令牌产生的速率就是1s生产5个;而令牌桶是用来处理一定的突发流量的,所以桶的容量需要保证极端情况下,不压垮系统就好了。
具体的实现上:相比于漏桶算法的实现,多一个令牌桶的容量。
其实这里记录时间的好处,主要一个好处就是避免了固定窗口算法中记录请求的访问次数。
参考:https://mp.weixin.qq.com/s/GOBmSOvWqpmLp2rijZ6q4w
RateLimiter是基于令牌桶算法实现的一个限流组件,其代码看起来很简单,一共就两个类:抽象父类RateLimiter和实际的SmoothRateLimiter。其中SmoothWarmingUP和SmmothBursty是SmoothRateLimiter的两个内部类。但实际真的要看懂也需要花点时间的,这里其实主要就是算法上的考虑不好看懂。
RateLimiter提供了两种限流模式:
基本使用也非常简单:
当然也提供了非阻塞的tryAcquire()方法
有了上面令牌桶算法的背景,再看RateLimiter就比价容易了,其中保存的属性:
Ps:速率(单位时间生产个数) = 时间段内总个数/时间长度 = 时间内长度/生产两个令牌的时间间隔 可以来表示生效速率。反过来使用两个令牌生产间隔其实也就可以表示速率。
而对于有预热的限流器,预热期间,就是1s/stableIntervalMicros的一半。预热结束就是1s/stableIntervalMicros。
所以这个maxPermits的最大值,其实就是初始化RateLimiter的时候设置的限流阈值做了整数转换。
比如:当前令牌桶中的令牌数storedPermits=2,但是acquire(5)的时候不会立马阻塞,而是将超支的3个令牌的生产时间转义到下次调用acquire()的时候。
即nextFreeTicketMicros += 3*100ms。
RateLimiter的设计哲学:它允许瞬间的流量波峰超过QPS,但瞬间过后的请求将会等待较长的时间来缓解上次的波峰,以使得平均的QPS等于预定值。
这4个参数是SmoothBursty和SmoothWarmUp共有的,且维护逻辑也都是一样的。
SmoothBursty自己的属性
SmoothWarmUp自己的属性:
其值=0.5 * warmupPeriodMicros/stableIntervalMicros
在guava 30.1版本中,这个值还是写死的3.0。
所以,对于限流阈值设置成1s内100个,那么stableIntervalMicros=100ms,但是在预热期令牌生产速率=3*100ms=300ms.
其值=(stableIntervalMicros * coldFactor - stableIntervalMicros) / (maxPermits - thresholdPermits)