限流算法介绍

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了限流算法介绍相关的知识,希望对你有一定的参考价值。

参考技术A

高并发的处理有三个比较常用的手段,缓存,限流和降级。缓存的使用相信很多开发者都很了解了,诸如redis,memcache等工具都会活跃在我们的系统当中。但是假如在某一时间段内出现了远超预想的流量访问到系统,例如在搞秒杀活动之类的,这样一来我们应该如何保护业务系统呢?

在参加一些秒杀活动的时候,我们可以看到,有时候会有 系统繁忙,请稍后再试
或者 请稍等 的提示,那这个系统就很可能是使用了限流的手段。

限流是限制系统的输入和输出流量,以达到保护系统的目的。而限流的实现主要是依靠限流算法,限流算法主要有4种:
1、固定时间窗口(计数器);
2、滑动时间窗口;
3、令牌桶算法;
4、漏桶算法;

计数器就是统计记录单位时间内进入系统或者某一接口的请求次数,在限定的次数内的请求则正常接收处理,超过次数的请求则拒绝掉,或者改为异步处理。

假设我们设定单位时间内进入系统的的最大请求数为100,如果有超过100个请求集中在刷新计数器的临界点前后进入系统,而且单位时间的粒度比较粗的话,那就容易误伤很多正常请求。

这个名称要跟TCP的窗口滑动区分开来,但是理解之后会发现其实也是有点相似。
计数器算法对流量的限制比较粗放,而滑动时间窗口的算法则是对流量进行更加平稳的控制。上面的计数器的单位时间是1分钟,而在使用滑动时间窗口,可以把1分钟分成6格,每格时间长度是10s,每一格又各自管理一个计数器,单位时间用一个长度为60s的窗口描述。一个请求进入系统,对应的时间格子的计数器便会+1,而每过10s,这个窗口便会向右滑动一格。只要窗口包括的所有格子的计数器总和超过限流上限,便会拒绝后面的请求。

漏桶算法,又称leaky bucket。下图是wiki上的漏桶图解:

一个系统处理请求,就像一个固定容量的水桶去溜进来的水,同时也让水流出去,但是它无法预见有多少水流进来和水流进来的速度,它只能够控制从桶底水流出去的速度,多出来的水,就只好让它从桶边流出去了。这个从桶底流出去的水就是系统正常处理的请求,从旁边流出去的水就是系统拒绝掉的请求。如此一来,我们只要监控系统单位时间内处理请求的速率就可以了,速率超过上限后的请求都给拒绝掉就可以了。

继续wiki图解。

令牌桶即是以一定速率生成token并放入桶中,请求进入系统需要先拿到token才能进行业务处理,无token的请求则拒绝掉。令牌桶算法实际上跟漏桶算法很相似,而实际使用中其实也不需要另起线程生成token,只需要把握好token生成速率和当前应该剩余的token数量即可。

在时间点刷新的临界点上,只要剩余token足够,令牌桶算法会允许对应数量的请求通过,而后刷新时间因为token不足,流量也会被限制在外,这样就比较好的控制了瞬时流量。因此,令牌桶算法也被广泛使用。

三种常见的限流算法

在开发高并发系统时,有三把利器用来保护系统:缓存、降级和限流。那么何为限流呢?顾名思义,限流就是限制流量,就像你宽带包了1个G的流量,用完了就没了。通过限流,我们可以很好地控制系统的qps,从而达到保护系统的目的。本篇文章将会介绍一下常用的限流算法以及他们各自的特点。

1、计数器算法
计数器算法是限流算法里最简单也是最容易实现的一种算法。比如我们规定,对于A接口来说,我们1分钟的访问次数不能超过100个。那么我们可以这么做:在一开 始的时候,我们可以设置一个计数器counter,每当一个请求过来的时候,counter就加1,如果counter的值大于100并且该请求与第一个 请求的间隔时间还在1分钟之内,那么说明请求数过多;如果该请求与第一个请求的间隔时间大于1分钟,且counter的值还在限流范围内,那么就重置 counter,具体算法的示意图如下:
技术分享图片
具体的伪代码如下:

public class CounterTest {
    public long timeStamp = getNowTime();
    public int reqCount = 0;
    public final int limit = 100; // 时间窗口内最大请求数
    public final long interval = 1000; // 时间窗口ms

    public boolean grant() {
        long now = getNowTime();
        if (now < timeStamp + interval) {
            // 在时间窗口内
            reqCount++;
            // 判断当前时间窗口内是否超过最大请求控制数
            return reqCount <= limit;
        } else {
            timeStamp = now;
            // 超时后重置
            reqCount = 1;
            return true;
        }
    }

    public long getNowTime() {
        return System.currentTimeMillis();
    }
}


这个算法虽然简单,但是有一个十分致命的问题,那就是临界问题,我们看下图:
技术分享图片
从上图中我们可以看到,假设有一个恶意用户,他在0:59时,瞬间发送了100个请求,并且1:00又瞬间发送了100个请求,那么其实这个用户在 1秒里面,瞬间发送了200个请求。我们刚才规定的是1分钟最多100个请求,也就是每秒钟最多1.7个请求,用户通过在时间窗口的重置节点处突发请求, 可以瞬间超过我们的速率限制。用户有可能通过算法的这个漏洞,瞬间压垮我们的应用。

聪明的朋友可能已经看出来了,刚才的问题其实是因为我们统计的精度太低。那么如何很好地处理这个问题呢?或者说,如何将临界问题的影响降低呢?我们可以看下面的滑动窗口算法。

滑动窗口
滑动窗口,又称rolling window。为了解决这个问题,我们引入了滑动窗口算法。如果学过TCP网络协议的话,那么一定对滑动窗口这个名词不会陌生。下面这张图,很好地解释了滑动窗口算法:
技术分享图片
在上图中,整个红色的矩形框表示一个时间窗口,在我们的例子中,一个时间窗口就是一分钟。然后我们将时间窗口进行划分,比如图中,我们就将滑动窗口 划成了6格,所以每格代表的是10秒钟。每过10秒钟,我们的时间窗口就会往右滑动一格。每一个格子都有自己独立的计数器counter,比如当一个请求 在0:35秒的时候到达,那么0:30~0:39对应的counter就会加1。

那么滑动窗口怎么解决刚才的临界问题的呢?我们可以看上图,0:59到达的100个请求会落在灰色的格子中,而1:00到达的请求会落在橘黄色的格 子中。当时间到达1:00时,我们的窗口会往右移动一格,那么此时时间窗口内的总请求数量一共是200个,超过了限定的100个,所以此时能够检测出来触 发了限流。

我再来回顾一下刚才的计数器算法,我们可以发现,计数器算法其实就是滑动窗口算法。只是它没有对时间窗口做进一步地划分,所以只有1格。

由此可见,当滑动窗口的格子划分的越多,那么滑动窗口的滚动就越平滑,限流的统计就会越精确。

2、令牌桶算法
令牌桶算法是比较常见的限流算法之一,大概描述如下:
1)、所有的请求在处理之前都需要拿到一个可用的令牌才会被处理;
2)、根据限流大小,设置按照一定的速率往桶里添加令牌;
3)、桶设置最大的放置令牌限制,当桶满时、新添加的令牌就被丢弃或者拒绝;
4)、请求达到后首先要获取令牌桶中的令牌,拿着令牌才可以进行其他的业务逻辑,处理完业务逻辑之后,将令牌直接删除;
5)、令牌桶有最低限额,当桶中的令牌达到最低限额的时候,请求处理完之后将不会删除令牌,以此保证足够的限流;
111111


3、漏桶算法
漏桶算法其实很简单,可以粗略的认为就是注水漏水过程,往桶中以一定速率流出水,以任意速率流入水,当水超过桶流量则丢弃,因为桶容量是不变的,保证了整体的速率。
222222

以上是关于限流算法介绍的主要内容,如果未能解决你的问题,请参考以下文章

接口限流算法总结

常见限流算法分析

常用的接口限流算法

三种常见的限流算法

高并发场景下常见的限流算法及方案介绍

Nginx限流和黑名单配置