如何限流

限制单位时间的调用量

AtomicLong#incrementAndGet()

分布式限流

很多时候我需要有一个全局的限速,例如用户注册时,让用户输入手机验证码,为了防止短信接口不被恶意频繁调用,一般会限制用户每分钟获取验证码频率,例如一分钟不能超过5次。

此时,我们可以通过Redis的来实现,伪代码如下:

phoneNum = "186xxxxxx";
key = "verifyCode:limit:"+phoneNum 
// SET key value EX 60 NX
isExists = redis.set(key, 1, "EX 60", "NX");
if( isExists !=null || redis.incr(key) <=5) {
    //通过
} else {
    //限速
}
上述,就是通过Redis实现了限速功能,例如一些网站限制一个IP地址不能在一秒钟内访问超过n次也可以采用类似的思路来实现。

限制并发调用程度

Semaphore


从上图可以看出上游的A、B服务直接依赖了下游的基础服务C、D和E,对于A,B服务都依赖的基础服务D这种场景,服务A和B其实处于某种竞争关系,当我们考量服务A的并发阈值时,不可忽略的是服务B对A的影响,所以,大多情况并发阈值的设置需要保守一点,如果服务A的并发阈值设置过大,当流量高峰期来临,有可能直接拖垮基础服务D并影响服务B,即雪崩效应来了。从表面上看并发量限流似乎很有用,但也不可否认,它仍然可以造成流量尖刺,即每台服务器上该服务的并发量从0上升到阈值是没有任何“阻力”的,这是因为并发量考虑的只是服务能力边界的问题。

使用令牌桶

第3种是通过漏桶算法来进行限流,漏桶算法是网络中流量整形的常用算法之一,它有点像我们生活中用到的漏斗,液体倒进去以后,总是从下端的小口中以固定速率流出,漏桶算法也类似,不管突然流量有多大,漏桶都保证了流量的常速率输出,也可以类比于调用量,比如,不管服务调用多么不稳定,我们只固定进行服务输出,比如每10毫秒接受一次服务调用。既然是一个桶,那就肯定有容量,由于调用的消费速率已经固定,那么当桶的容量堆满了,则只能丢弃了,漏桶算法如下图:

漏桶算法其实是悲观的,因为它严格限制了系统的吞吐量,从某种角度上来说,它的效果和并发量限流很类似。漏桶算法也可以用于大多数场景,但由于它对服务吞吐量有着严格固定的限制,如果在某个大的服务网络中只对某些服务进行漏桶算法限流,这些服务可能会成为瓶颈。其实对于可扩展的大型服务网络,上游的服务压力可以经过多重下游服务进行扩散,过多的漏桶限流似乎意义不大。
实现方面,可以先准备一个队列,当做桶的容量,另外通过一个计划线程池(ScheduledExecutorService)来定期从队列中获取并执行请求调用,当然,我们没有限定一次性只能从队里中拿取一个请求,比如可以一次性拿100个请求,然后并发执行。

第4种是令牌桶算法限流,令牌桶算法从某种程度上来说是漏桶算法的一种改进,漏桶算法能够强行限制请求调用的速率,而令牌桶算法能够在限制调用的平均速率的同时还允许某种程度的突发调用。在令牌桶算法中,桶中会有一定数量的令牌,每次请求调用需要去桶中拿取一个令牌,拿到令牌后才有资格执行请求调用,否则只能等待能拿到足够的令牌数,读者看到这里,可能就认为是不是可以把令牌比喻成信号量,那和前面说的并发量限流不是没什么区别嘛?其实不然,令牌桶算法的精髓就在于“拿令牌”和“放令牌”的方式,这和单纯的并发量限流有明显区别,采用并发量限流时,当一个调用到来时,会先获取一个信号量,当调用结束时,会释放一个信号量,但令牌桶算法不同,因为每次请求获取的令牌数不是固定的,比如当桶中的令牌数还比较多时,每次调用只需要获取一个令牌,随着桶中的令牌数逐渐减少,当到令牌的使用率(即使用中的令牌数/令牌总数)达某个比例,可能一次请求需要获取两个令牌,当令牌使用率到了一个更高的比例,可能一次请求调用需要获取更多的令牌数。同时,当调用使用完令牌后,有两种令牌生成方法,第一种就是直接往桶中放回使用的令牌数,第二种就是不做任何操作,有另一个额外的令牌生成步骤来将令牌匀速放回桶中。如下图:

在并发量限制时,在达到并发阈值之前,并发量和前来调用的线程数可以说是成严格正比关系的,但在令牌桶中可能就并不是这样,下面给出在某种特定场景和特定参数下四种限流方式对服务并发能力影响的折线图,其中X轴表示当前并发调用数,而Y轴表示某服务在不同并发调用程度下采取限流后的实际并发调用数:

其实,不同场景不同参数下,服务采用所述四种限流方式都会有不同的效果,甚至较大差异,上图也并不能说明实际并发度越高就吞吐量越高,因为还必须把稳定性等因素考虑进去,这就好比插入排序、堆排序、归并排序和快速排序的对比一样,没有任何限流算法可以说自己在任何场景下都是最优限流算法,这需要从服务资源特性、限流策略(参数)配置难度、开发难度和效果检测难度等多方面因素来考虑。

咱们再回到令牌桶算法,和其他三种降级方式来说,令牌桶算法限流无疑是最灵活的,因为它有众多可配置的参数来直接影响限流的效果。幸运的是,谷歌的Guava包中RateLimiter提供了令牌桶算法的实现.
 

发布了41 篇原创文章 · 获赞 6 · 访问量 5万+

猜你喜欢

转载自blog.csdn.net/bujidexinq/article/details/104986903
今日推荐