也谈限流

时间:2022-07-24
本文章向大家介绍也谈限流,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

限流的技术现在用的比较普遍了,网上一搜应该有大把的文章,为什么还来凑这个热闹呢,因为最近我们公司也在做限流,限流参考是以并发请求数作为限流参考的,即来一个请求计数器加1,请求结束对应计数器减1,如果计数器超过限流值则拒绝请求。

但有的同学不太理解,为什么以并发请求数,而不是TPS作为限流参考呢?

我们先看我们为什么要限流:

保护系统,防止系统雪崩;

限并发请求数和TPS都可以让达到目的。

我们再看下常用限流算法有哪些:

1、计数器算法

计数器算法是使用计数器在周期内累加访问次数,当达到设定的限流值时,触发限流策略。下一个周期开始时,进行清零,重新计数。

打个比方,我们设的周期为1秒,设置1秒内最多能进来100个请求,如果在1秒内有第101个请求,则被限流。

2、滑动窗口算法

滑动窗口算法是将时间周期分为N个小周期,分别记录每个小周期内访问次数,并且根据时间滑动删除过期的小周期。

这种算法的特点是 :当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

3、漏桶算法

漏桶算法是访问请求到达时直接放入漏桶,如当前容量已达到上限(限流值),则进行丢弃。漏桶以固定的速率进行释放访问请求(即请求通过),直到漏桶为空。

4、令牌桶算法 令牌桶算法是程序以r(r=时间周期/限流值)的速度向令牌桶中增加令牌,直到令牌桶满,请求到达时向令牌桶请求令牌,如获取到令牌则通过请求,否则触发限流策略

其中1和2实现起来比较简单,3和4实现起来比较复杂,因为要动态做一些计算,时效性要求也比较高。

我们上面说的问题就是算法1,这个算法有个问题,即只统计请求的开始计数,这样会导致一个问题,即只能保证每个周期内只进来这么多请求,但无法保证服务器同时只处理这么多请求,什么意思呢,举个例子:

假设限流值设置的是1000,即1秒内最多只能处理1000个请求,我们假设每个请求处理的时间为50ms,假如在第一个周期的前960ms只进来100个请求,最后40ms内瞬间进入900个请求,而在第2个周期头5ms内进来800个请求,那在第2个周期的头10ms内服务器同时处理的请求有1700个请求,超出1000。

导致这个问题的关键是实现上只在请求进来时候进行了计数,而没有在请求结束的时候进行减掉计数器,导致计数有重叠。所以在实现上我们只要做到后面减数的这一点,则计数器大体上是比较准确的。

在实现上还要注意一个问题,如果用redis进行计数的话,伪代码如下:

 incr url的对应的计数器;
 if 计数器 == 1 then
   设置计数器的过期时间为1s
 else
 end

可以看到这里可能有下面的因素导致不准:

1、程序在执行incr后挂了,那么过期时间就没有设置了,导致后面一直过期;解决方案是可以在服务端将incr和设置过期时间改造为原子命令,有兴趣的话后面可以单独一节讲解如何复合redis命令。

再回到上面的问题,并发和TPS哪个好呢,还是看实际的场景:

1、如果你的响应时间非常快,一般来说小于5ms,限流为并发请求数就没有太大意义了,我们在压测环境试过,用Lua写的接口,单接口在并发数限为1,TPS都能上千,这种场景下反而起不到限流的作用;

2、并发请求数实现会比较简单,它是基于瞬间压力来计算的,如果系统响应时间有很大变化的,如上100个请求的平均响应时间为50ms,而下100个的平均响应时间为100ms则比较适合这种情况;

而TPS是限制一个时间段的请求,效果会比较顺滑一些,如果系统响应时间比较固定,并且每个周期的请求数没有太多变化可以用这个;

另外TPS需要统计每秒的请求数,一般来说得用定时器实现,定时器在CPU压力比较大的情况下可能有延迟,会导致系统计数不准。