背景
随着微服务的流行,服务之间的稳定性变得越发重要,往往我们会花很多经历在维护服务的稳定性上,限流和熔断降级是我们最常用的两个手段。前段时间在群里有些小伙伴对限流的使用些疑问,再加上最近公司大促也做了限流相关的事,所以在这里总结一下写写自己对限流的一些看法。
刚才说了限流是我们保证服务稳定性的手段之一,但是他并不是所有场景的稳定性都能保证,和他名字一样他只能在大流量或者突发流量的场景下才能发挥出自己的作用。比如我们的系统最高支持100QPS,但是突然有1000QPS请求打了进来,可能这个时候系统就会直接挂掉,导致后面一个请求都处理不了,但是如果我们有限流的手段,无论他有多大的QPS,我们都只处理100QPS的请求,其他请求都直接拒绝掉,虽然有900的QPS的请求我们拒绝掉了,但是我们的系统没有挂掉,我们系统仍然可以不断的处理后续的请求,这个是我们所期望的。有同学可能会说,现在都上的云了,服务的动态伸缩应该是特别简单的吧,如果我们发现流量特别大的时候,自动扩容机器到可以支撑目标QPS那不就不需要限流了吗?其实有这个想法的同学应该还挺多的,有些同学可能被一些吹牛的文章给唬到了,所以才会这么想,这个想法在特别理想化的时候是可以实现的,但是在现实中其实有下面几个问题:
扩容是需要时间。扩容简单来说就是搞一个新的机器,然后重新发布代码,做java的同学应该是知道发布成功一个代码的时间一般不是以秒级计算,而是以分钟级别计算,有时候你扩容完成,说不定流量尖峰都过去了。
扩容到多少是个特别复杂的问题。扩容几台机器这个是比较复杂的,需要大量的压测计算,以及整条链路上的一个扩容,如果扩容了你这边的机器之后,其他团队的机器没有扩容可能最后还是有瓶颈这个也是一个问题。
所以单纯的扩容是解决不了这个问题的,限流仍然是我们必须掌握的技能!
基本原理
想要掌握好限流,就需要先掌握他的一些基本算法,限流的算法基本上分为三种,计数器,漏斗,令牌桶,其他的一些都是在这些基础上进行演变而来。
计数器算法
首先我们来说一下计数器算法,这个算法比较简单粗暴,我们只需要一个累加变量,然后每隔一秒钟去刷新这个累加变量,然后再判断这个累加变量是否大于我们的最大QPS。
int curQps = 0;
long lastTime = System.currentTimeMillis();
int maxQps = 100;
Object lock = new Object();
boolean check(){
synchronized (lock){
long now = System.currentTimeMillis();
if (now - lastTime > 1000){
lastTime = now;
curQps = 0;
}
curQps++;
if (curQps > maxQps){
return false;
}
}
return true;
}
这个代码比较简单,我们定义了当前的qps,以及上一次刷新累加变量的时间,还有我们的最大qps和我们的lock锁,我们每次检查的时候,都需要判断是否需要刷新,如果需要刷新那么需要把时间和qps都进行重置,然后再进行qps的累加判断。
这个算法因为太简单了所以带来的问题也是特别明显,如果我们最大的qps是100,在0.99秒的时候来了100个请求,然后在1.01秒的时候