常见限流算法

79 阅读2分钟

一.限流场景

通过限流,可以控制服务请求速率,从而提高系统应对突发大流量的能力,让系统更具弹性。限流的常见应用场景有商品秒杀活动,12306抢票等。限流对象可以是接口请求频率或者某个IP用户的请求速度等,使其拒绝服务或者排队等待,服务降级等。

二.限流算法

1. 固定窗口

限制指定窗口内(指定时间段内)的请求数不会超过最大值
如10分钟内限制1000个请求
效果有限,无法限制前一个时间段末和后一个时间段始的大量请求

2.滑动窗口

将大窗口划分为粒度更细的小窗口,每次向后移动一个小窗口,并限制大窗口内的请求数不会超过最大值
如10分钟内限制1000个请求,小窗口为1分钟,每次向后移动一分钟,并限制10分钟内请求不会超过1000
相比固定窗口更加平滑(小窗口越小越平滑,性能要求更高),不过没有根本解决突发流量问题

image.png

3. 漏桶算法

水流入桶的速度不固定,水以固定速率从桶中流出,如果桶里的水(容器里的请求)已满,则触发限流策略
如设置桶的大小为10000,请求以固定速度为10个/s进行处理
不适合突发流量问题,因为处理速度是固定的
ps:水(请求), 桶(容器),流出(处理)

image.png

4. 令牌桶算法

令牌以固定速率加入桶中(不能超过桶最大值),请求处理前需要从桶中获取令牌(如果没有令牌触发限流策略)
如桶大小为10000,令牌以固定速度为10个/s加入桶
能够很好的应对突发流量,因为在此之前桶内存储了大量令牌

image.png

// 令牌桶经典实现
public class TokenBucket {
    // 可用令牌
    private int availableToken;
    // 总令牌
    private int totality;
    // 令牌添加速度 个/毫秒
    private double tokenSpeed;
    // 最后一次添加时间
    private long lastTime;

    public TokenBucket(int totality, double tokenSpeed) {
        this.totality = totality;
        this.tokenSpeed = tokenSpeed;
        this.lastTime = System.currentTimeMillis();
    }

    public synchronized boolean getToken(int num) {
        // 每次填充前先把这段时间内的令牌添加进去
        fill();
        if (num <= availableToken) {
            availableToken-=num;
            System.out.println(Thread.currentThread().getName() + ": " +"availableToken is :" + availableToken);
            return true;
        } else {
            System.out.println(Thread.currentThread().getName() + ": " +"请等待 : " + availableToken);
            return false;
        }
    }

    public void fill() {
        long now = System.currentTimeMillis();
        if (now > this.lastTime) {
            long time = now - this.lastTime;
            int newTokens = (int) (time * this.tokenSpeed);
            System.out.println(Thread.currentThread().getName() + ": " + newTokens + ", time :" + time);
            this.availableToken = Math.min(this.totality, this.availableToken + newTokens);
            this.lastTime = now;
        }
    }
}