[服务或接口限流算法1/2]-漏桶算法及令牌桶算法解析

同字面意思一样,限流即为限制流量,已知服务并发上限,但是在某个时刻涌入了大量流量,服务器无法及时处理,则进入排队等待,从而导致服务超时或者响应时间过长,此时就需要我们的限流方案为这一现象进行一些处理, 目前主流的限流方案有以下几种,我们一起来实战演练一番

1.漏桶算法

原理及概念

漏桶限流算法是一种流量控制算法,它通过一个固定容量的桶来控制流出的速率。当流量到来时,先放入到漏桶中,如果漏桶已满则丢弃多余的流量,否则按照固定的速率流出。这样可以限制流量的突发性,平滑流量的输出。

漏桶限流算法的实现思路如下:

  1. 设定一个固定容量的漏桶,以及漏水的速率。
  2. 当有请求到来时,将请求放入漏桶中。
  3. 如果漏桶已满,则拒绝请求或者丢弃多余的请求。
  4. 按照固定速率处理漏桶中的请求。

在实际应用中,漏桶限流算法可以用于限制网络请求的速率,防止流量突发增大导致系统崩溃,也可以用于平滑流量的输出,保护系统资源。

如图所示

优缺点

优点

  1. 平滑输出:漏桶算法可以平滑地处理流量,防止突发流量对系统造成冲击,有助于保护系统稳定性。
  2. 控制速率:漏桶算法可以限制输出的速率,确保系统资源不被过度占用,有助于进行流量控制和保护。

缺点

  1. 丢弃请求:当漏桶已满时,漏桶算法会丢弃多余的请求,可能会导致部分请求被拒绝,对于某些应用场景可能不太友好。
  2. 无法应对突发流量:漏桶算法无法应对突发性的大流量,因为漏桶容量是固定的,超出容量的流量会被丢弃,无法灵活应对突发情况。

总的来说,漏桶算法适合用于平滑输出和控制速率,但在对于突发流量的处理上有一定局限性。在实际应用中需要根据具体场景和需求进行选择。

代码实现

java 复制代码
/**
 * 漏桶限流算法实现
 *
 * @author AnkerEvans
 */
public class LeakyBucketCurrentLimitHandler implements CurrentLimitHandler {
    /**
     * 漏桶容量
     */
    private int capacity;
    /**
     * 漏水速率n/s, 每秒允许多少个请求
     */
    private int rate;
    /**
     * 当前水量
     */
    private int water;
    /**
     * 上次漏水时间
     */
    private long lastLeakTime;

    /**
     * 构建handler
     *
     * @param capacity      桶容量
     * @param rate          流出速率(n/s)
     */
    public LeakyBucketCurrentLimitHandler(int capacity, int rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.water = 0;
    }

    /**
     * 尝试访问
     *
     * @param tokens    需要获取令牌的数量
     * @return 是否允许访问
     */
    @Override
    public synchronized boolean tryAccess(Integer tokens) {
        // 当前服务器时间戳
        long now = System.currentTimeMillis();
        // 求当前数量
        water = Math.max(0, (int) (water - (now - lastLeakTime) / 1000 * rate));
        // 更新上次流出时间
        lastLeakTime = now;
        // 如果数量 + 需要获取令牌的数量 小于等于 桶容量大小, 则允许访问
        if (water + tokens <= capacity) {
            water += tokens;
            // 漏桶未满,允许通过
            return true;
        }
        // 漏桶已满,拒绝通过
        return false;

    }
}

2.令牌桶算法

原理及概念

令牌桶算法基于令牌桶的概念来实现流量控制。在令牌桶限流算法中,令牌桶以固定的速率往里面添加令牌,每个请求需要消耗一个令牌,如果令牌桶中的令牌数量不足,则请求将被限制或延迟处理。

令牌桶限流算法的基本原理如下:

  1. 令牌桶以固定的速率往里面添加令牌,直到达到最大容量为止。
  2. 每个请求需要消耗一个令牌,如果令牌桶中有足够的令牌,则请求可以被处理,同时从令牌桶中消耗一个令牌;如果令牌桶中没有足够的令牌,则请求被限制或延迟处理。
  3. 令牌桶中的令牌数量是有限的,当请求频率超过令牌桶的填充速率时,令牌桶将会耗尽,无法处理更多的请求。

令牌桶限流算法的优点包括:

  1. 平滑的流量控制:令牌桶算法可以以固定的速率向令牌桶中添加令牌,从而限制请求的处理速率,实现平滑的流量控制。
  2. 灵活性:令牌桶算法可以根据实际需求动态调整令牌的填充速率和容量,适应不同的场景。

然而,令牌桶限流算法也存在一些缺点:

  1. 突发流量处理:令牌桶算法对于突发流量的处理能力较差,因为令牌桶中的令牌数量是有限的,无法应对瞬时的大流量。
  2. 复杂性:相对于一些其他流量控制算法,令牌桶算法的实现可能相对复杂一些,需要考虑令牌的填充速率、容量等参数。

如图所示

优缺点

优点

  1. 平滑的流量控制:令牌桶算法可以以固定的速率向令牌桶中添加令牌,从而限制请求的处理速率,实现平滑的流量控制。
  2. 灵活性:令牌桶算法可以根据实际需求动态调整令牌的填充速率和容量,适应不同的场景。

缺点

  1. 突发流量处理:令牌桶算法对于突发流量的处理能力较差,因为令牌桶中的令牌数量是有限的,无法应对瞬时的大流量。
  2. 复杂性:相对于一些其他流量控制算法,令牌桶算法的实现可能相对复杂一些,需要考虑令牌的填充速率、容量等参数。

代码实现

java 复制代码
/**
 * 令牌桶限流算法实现
 *
 * @author AnkerEvans
 */
public class TokenBucketCurrentLimitHandler implements CurrentLimitHandler {

    /**
     * 令牌桶容量
     */
    private int capacity;
    /**
     * 当前令牌数量
     */
    private int tokens;
    /**
     * 上次填充令牌的时间
     */
    private long lastRefillTime;
    /**
     * 令牌填充速率
     */
    private int refillRate;

    /**
     * 构建令牌桶算法实现
     *
     * @param capacity      令牌容量
     * @param refillRate    填充速率
     */
    public TokenBucketCurrentLimitHandler(int capacity, int refillRate) {
        this.capacity = capacity;
        this.tokens = capacity;
        this.refillRate = refillRate;
    }

    /**
     * 补充令牌
     */
    private void refill() {
        long now = System.currentTimeMillis();
        if (now > lastRefillTime) {
            int timePassed = (int) (now - lastRefillTime);
            /*
             计算新token数量, 这段代码很好理解, 假设距离上次填充时间过去了 1000 ms, 填充速率每秒2个
             那么需要填充的数量即为 1000 * 2 / 1000 = 2, 如果距离上次填充为500 ms, 则 500 * 2 / 1000 = 1, 间隔更小的话则不填充
             通俗易懂的来讲就是, 按每秒生成令牌的速率填充令牌桶
             */
            int newTokens = timePassed * refillRate / 1000;
            // 令牌数量为 当前桶令牌数量 + 新生成的令牌数量, 与最大令牌容量取最小值即为当前令牌数量
            this.tokens = Math.min(this.tokens + newTokens, capacity);
            this.lastRefillTime = now;
        }
    }


    @Override
    public boolean tryAccess(Integer token) {
        /*
            相当于匀速填充令牌
         */
        refill();
        /*
            如果令牌数量大于需要获取的令牌数量,则通过,并且令牌数量-${token}个
            相当于获取令牌的操作
         */
        if (this.tokens >= token) {
            this.tokens -= token;
            return true;
        }
        // 否则限制访问
        return false;
    }
}

小结一下:

了解完了漏桶限流和令牌桶限流算法,有没有发现他俩的区别

  1. 漏桶算法是限定单位时间内可以接受的最大请求次数.

    想想这样的方式是不是有一些缺陷,例如

    1. 该接口允许1秒内最大接收10个请求, 但是我在第100ms时已经放行了10个请求, 那么后面的请求将被拒绝

    2. 该接口允许1秒内最大接收1000个请求,但是恰好10ms内涌入了1000个请求,这种场景其实没有有效的实现对服务器资源的保护

    所以 漏桶限流算法存在很大的局限性, 读到这里,相信你也理解了漏桶算法的原理,希望在后续工作中,对于频繁访问或者流量不平滑的接口需要进行限流时,能对其一些核心参数进行优化, 避免出现上述的两种极端情况

  2. 令牌桶限流算法基于其实现原理,侧面的也解决了一些上文中漏桶算法的缺陷. 因为其相较于漏斗算法, 它对令牌的填充数量是匀速进行的

它们有一个共同的缺点: 无法应对流量激增这种情况,

我为两个限流算法各举一个简单的例子:

  1. 漏桶算法:

    1. 你开了一家早餐店, 每日早上限量100份供应,早上8:00开门9:00关门, 8:30的时候你早餐卖完然后关门走了, 慕名而来买早餐的人买不到早餐走了. 这种对应漏桶算法的缺陷a
    2. 你开了一家早餐店, 你每40分钟能做100份早餐, 你为了保险起见,预估了一下每小时卖100份, 结果刚开门2分钟就有100个人排队, 你拼老命10分钟做了80份然后累垮了, 这就是刚才提到的漏桶算法的缺陷b, 你的早餐店相当于服务器, 你40分钟做100份相当于你实际的处理速率, 然后那排队的100个人就是100个请求, 你累垮了则标识服务器宕机了
  2. 令牌桶限流算法:

    1. 你还是开了一家早餐店,你每10分钟只能做两份早餐, 来了十个人买早餐, 两个人买完早餐走了, 剩下的8个人等下一个10分钟的两份早餐,或者走人

以上两个贴近生活的小例子应该能帮助你对两种限流算法的理解了(文笔不太好,勿Cue) 感兴趣的小伙伴可以关注下我的博客,将在下周更新 [服务或接口限流算法1/3]-固定时间窗口及滑动时间窗口算法解析

相关推荐
今天背单词了吗98019 分钟前
算法学习笔记:11.冒泡排序——从原理到实战,涵盖 LeetCode 与考研 408 例题
java·学习·算法·排序算法·冒泡排序
Brookty27 分钟前
【操作系统】进程(二)内存管理、通信
java·linux·服务器·网络·学习·java-ee·操作系统
风象南27 分钟前
SpringBoot 与 HTMX:现代 Web 开发的高效组合
java·spring boot·后端
倔强的小石头_3 小时前
【C语言指南】函数指针深度解析
java·c语言·算法
kangkang-7 小时前
PC端基于SpringBoot架构控制无人机(三):系统架构设计
java·架构·无人机
界面开发小八哥9 小时前
「Java EE开发指南」如何用MyEclipse创建一个WEB项目?(三)
java·ide·java-ee·myeclipse
idolyXyz9 小时前
[java: Cleaner]-一文述之
java
一碗谦谦粉9 小时前
Maven 依赖调解的两大原则
java·maven
netyeaxi10 小时前
Java:使用spring-boot + mybatis如何打印SQL日志?
java·spring·mybatis
收破烂的小熊猫~10 小时前
《Java修仙传:从凡胎到码帝》第四章:设计模式破万法
java·开发语言·设计模式