限流
系统面临高并发,大流量请求的情况下,限制新的流量对系统的访问,保证系统服务的安全性。
为什么限流
日常的业务上有类似秒杀活动、双十一大促或者突发新闻等场景,用户的流量突增,后端服务的处理能力是有限的,如果不能处理好突发流量,后端服务很容易就被打垮,导致整个系统崩溃!
亦或是爬虫等不正常流量,我们对外暴露的服务都要以最大恶意去防备我们的调用者。我们不清楚调用者会如何调用我们的服务。假设某个调用者开几十个线程一天二十四小时疯狂调用你的服务,不做啥处理咱服务也算完了。更胜的还有DDos攻击。
常见限流算法
1.计数限流
假设系统能同时处理100个请求,保存一个计数器,处理了一个请求,计数器加一,一个请求处理完毕之后计数器减一。每次请求来的时候看看计数器的值,如果超过阈值要么拒绝。
计数器的值要是存在内存中就是单机限流算法,存在中心存储中如Redis,集群机器访问就是分布式限流算法。
优点:使用简单,单机可以在Java中使用Atomic原子类,分布式就Redis incr
缺点:如果阈值设为一万,此时计数器的值为0,当一万个请求在前1s内同时进入,这时顶不住突发的流量。
2.固定窗口限流算法
维护一个计数器,将单位时间段当做一个窗口,计数器记录这个窗口接收请求的次数。
- 当次数少于限流阀值,就允许访问,并且计数器+1
- 当次数大于限流阀值,就拒绝访问。
- 当前的时间窗口过去之后,计数器清零。
假设单位时间是1秒,限流阀值为3。在单位时间1秒内,每来一个请求,计数器就加1,如果计数器累加的次数超过限流阀值3,后续的请求全部拒绝。等到1s结束后,计数器清0,重新开始计数。
但固定窗口限流算法也存在问题:
-
一段时间内(不超过时间窗口)系统服务不可用。比如窗口大小为1s,限流大小为100,然后恰好在某个窗口的第1ms来了100个请求,然后第2ms-999ms的请求就都会被拒绝,这段时间用户会感觉系统服务不可用。
-
窗口切换时可能会产生两倍于阈值流量的请求。假设限流阀值为5个请求,单位时间窗口是1s,如果我们在单位时间内的前0.8-1s和1-1.2s,分别并发5个请求。虽然都没有超过阀值,但是如果算0.8-1.2s,则并发数高达10,已经超过单位时间1s不超过5阀值的定义,通过的请求达到了阈值的两倍。
为了解决上述问题,引入滑动窗口限流:
滑动窗口限流 滑动窗口限流解决固定窗口临界值的问题,可以保证在任意时间窗口内都不会超过阈值。
相对于固定窗口,滑动窗口除了需要引入计数器之外还需要记录时间窗口内每个请求到达的时间点,因此对内存的占用会比较多。
规则如下,假设时间窗口为 1 秒:
1.记录每次请求的时间 2.统计每次请求的时间 至 往前推1秒这个时间窗口内请求数,并且 1 秒前的数据可以删除。 3.统计的请求数小于阈值就记录这个请求的时间,并允许通过,反之拒绝。
滑动窗口就是固定窗口的优化版,将计时窗口划分成一个小窗口,滑动窗口算法就退化成了固定窗口算法。而滑动窗口算法其实就是对请求数进行了更细粒度的限流,窗口划分的越多,则限流越精准。 但是滑动窗口和固定窗口都无法解决短时间之内集中流量的突击。
我们所想的限流场景,例如每秒限制 100 个请求。希望请求每 10ms 来一个,这样我们的流量处理就很平滑,但是真实场景很难控制请求的频率。因此可能存在 5ms 内就打满了阈值的情况。
当然对于这种情况还是有变型处理的,例如设置多条限流规则。不仅限制每秒 100 个请求,再设置每 10ms 不超过 2 个。
接下来再说说漏桶,它可以解决时间窗口类的痛点,使得流量更加的平滑。
限流结论及选择:
-
固定窗口算法实现简单,性能高,但是会有临界突发流量问题,瞬时流量最大可以达到阈值的2倍。为了解决临界突发流量,可以将窗口划分为多个更细粒度的单元,每次窗口向右移动一个单元,于是便有了滑动窗口算法。
-
滑动窗口当流量到达阈值时会瞬间掐断流量,所以导致流量不够平滑。
-
想要达到限流的目的,又不会掐断流量,使得流量更加平滑?可以考虑漏桶算法!需要注意的是,漏桶算法通常配置一个FIFO的队列使用以达到允许限流的作用。
-
由于速率固定,即使在某个时刻下游处理能力过剩,也不能得到很好的利用,这是漏桶算法的一个短板。
-
限流和瞬时流量其实并不矛盾,在大多数场景中,短时间突发流量系统是完全可以接受的。令牌桶算法就是不二之选了,令牌桶以固定的速率v产生令牌放入一个固定容量为n的桶中,当请求到达时尝试从桶中获取令牌。
-
当桶满时,允许最大瞬时流量为n;当桶中没有剩余流量时则限流速率最低,为令牌生成的速率。
-
如何实现更加灵活的多级限流呢?使用滑动日志限流算法,这里的日志则是请求的时间戳,通过计算制定时间段内请求总数来实现灵活的限流。
-
当然,由于需要存储时间戳信息,其占用的存储空间要比其他限流算法要大得多。
参考了billgates_wanbin的博客