常见的限流算法

前不久在项目中使用到了限流算法,今天就总结一下常见的限流算法,常见的四种限流算法:固定窗口、滑动窗口、漏桶算法和令牌桶算法,并比较它们的优缺点和适用场景。

1、固定窗口算法

固定窗口算法是最简单的限流算法之一,其核心思想是将时间划分为固定大小的窗口,并在每个窗口内限制请求的数量。具体来说,系统维护一个大小为 N 的计数器,每当有请求到来时,计数器加一。当一个窗口结束时,如果计数器的值超过了预设的阈值,则拒绝后续的请求。固定窗口算法的实现简单,但容易受到突发流量的影响,因为请求可能会在同一窗口内集中到达。

假设单位时间1s(窗口大小),限流阈值是4。也就是说每次请求来计数器都会+1,如果单位时间内,计数器的数量超过阈值,那么就会拒绝所有请求,等到单位时间结束后,计数器清0,重新计数

在这里插入图片描述

固定窗口限流算法代码如下:

public class FixedWindowRateLimiter {

    private final AtomicInteger counter = new AtomicInteger(0);
    private volatile long lastRequestTime;
    private long windowUnit = 1000L;
    private int threshold = 4;

    public FixedWindowRateLimiter(){}

	// 这块可以通过配置文件获取
    public FixedWindowRateLimiter(long windowUnit, int threshold) {
        this.windowUnit = windowUnit;
        this.threshold = threshold;
    }

    public synchronized  boolean fixedWindowsTryAcquire() {
        long currentTime = System.currentTimeMillis();
        if (currentTime - lastRequestTime > windowUnit) {
            counter.set(0);
            lastRequestTime = currentTime;
        }
        if (counter.get() < threshold) {
            counter.incrementAndGet();
            return true;
        }
        return false;
    }
}

固定窗口限流算法的优缺点

优点:
1、实现简单易理解
缺点:
1、限流不均匀: 固定窗口算法可能会导致请求的限流不均匀。在某些时刻,请求量可能远远低于限流阈值,而在其他时刻则可能超过限流阈值。如下图所示:
在这里插入图片描述
2、临界问题:假设窗口大小是1s,阈值是4,那么,0.8s-1s来4个请求,在1s-1.2s来4个请求,虽然在每个独立的窗口内都没有超过阈值,但是在连续的窗口中,请求量却超过了阈值。即在窗口边界处出现突发的高并发请求,导致整体的请求量超过了阈值。如图所示。
在这里插入图片描述

2、滑动窗口限流算法

为了解决固定窗口算法的突发流量问题,滑动窗口算法应运而生。滑动窗口算法与固定窗口算法类似,但是它不是按照固定的窗口大小来计数,而是采用一个固定大小的滑动窗口在时间轴上滑动。每当有请求到达时,算法会检查滑动窗口内的请求数是否超过了阈值。通过滑动窗口的机制,滑动窗口算法能够更加平滑地限制流量,减少了对突发流量的敏感度。

在这里插入图片描述

滑动窗口限流算法代码如下:

public class SlidingWindowRateLimiter {
    
    private long unitOfTime = 60000;

    private int subCycle = 10;

    private int thresholdPerMin = 100;

    private final ConcurrentHashMap<Long, Integer> counters = new ConcurrentHashMap<>();


    /**
     * 滑动窗口时间算法实现
     */
    public boolean allowRequest() {
        // 将当前时间戳(以 UTC 时区为准)进行舍去操作,使其对齐到最近的子周期的起始时间
        // 例如,如果子周期长度为 10 秒,并且当前时间戳是 1632345678,那么上述表达式的结果将是 1632345670,即对齐到最近的以 0 结尾的子周期起始时间。
        long currentWindowTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) / subCycle * subCycle; //获取当前时间在哪个小周期窗口
        int currentWindowNum = countCurrentWindow(currentWindowTime); //当前窗口总请求数

        //超过阀值限流
        if (currentWindowNum >= thresholdPerMin) {
            return false;
        }
        //计数器+1
        counters.put(currentWindowTime, counters.getOrDefault(currentWindowTime, 0) + 1);
        return true;
    }

    /**
     * 统计当前窗口的请求数
     */
    private int countCurrentWindow(long currentWindowTime) {
        //计算窗口开始位置
        long startTime = currentWindowTime - subCycle * (unitOfTime / 1000L / subCycle - 1);
        int count = 0;

        //遍历存储的计数器
        Iterator<Map.Entry<Long, Integer>> iterator = counters.entrySet().iterator();
        while (iterator.hasNext()) {
            Map.Entry<Long, Integer> entry = iterator.next();
            // 删除无效过期的子窗口计数器
            if (entry.getKey() < startTime) {
                iterator.remove();
            } else {
                //累加当前窗口的所有计数器之和
                count = count + entry.getValue();
            }
        }
        return count;
    }
}

从代码的角度简简单单举个例子来说明一下滑动窗口的限流,假设单位时间1min(窗口大小),把这个窗口划分为6个小窗口,每个小窗口是10s,每过10s,窗口就会往右滑动,限流阈值是100。也就是说每次请求来都会计算窗口计数器总和,如果总和小于设置的阈值,那么对应的小窗口计数器+1,否则就拒绝请求。

通过下图它是如何解决临界问题的
在这里插入图片描述
在当前窗口结束的位置有100个请求,时间超过了1min后,又来100个请求,此时由于窗口向前滑动了一小格,所以此时窗口内的请求是200,大于100,那么他会把第二个蓝色小框的请求都会拒绝

滑动窗口限流算法的优缺点

优点:简单易懂、统计精度高
缺点:无法处理突发流量

3、漏桶限流算法

漏桶限流算法是一种简单有效的限流算法,通俗点来说,其原理类似于一个漏桶,请求先进入漏桶,然后以固定的速率被处理或丢弃。漏桶限流算法主要包括两个核心参数:漏桶的容量和漏水的速率。
在这里插入图片描述

漏桶限流算法代码如下:

public class LeakyBucketRateLimiter {

    private long bucketSize = 10; // 漏桶容量,默认为 10
    private long leakyRate = 1; // 漏水速率,默认为 1 每毫秒
    private long currentSize = 0; // 漏桶当前水量
    private long lastTime = System.currentTimeMillis(); // 上次处理请求的时间,默认为当前时间

    /**
     * 无参构造函数,使用默认的漏桶容量、漏水速率、当前水量和上次处理请求的时间。
     */
    public LeakyBucketRateLimiter() {
    }

    /**
     * 带参构造函数,使用指定的漏桶容量、漏水速率、当前水量和上次处理请求的时间。
     * @param bucketSize 漏桶容量
     * @param leakyRate 漏水速率
     * @param currentSize 当前水量
     * @param lastTime 上次处理请求的时间
     */
    public LeakyBucketRateLimiter(long bucketSize, long leakyRate, long currentSize, long lastTime) {
        this.bucketSize = bucketSize;
        this.leakyRate = leakyRate;
        this.currentSize = currentSize;
        this.lastTime = lastTime;
    }

    /**
     * 允许处理请求的方法。
     * @return 如果漏桶中有足够的水量处理请求,则返回 true;否则返回 false。
     */
    public synchronized boolean allowRequest() {
        long currentTime = System.currentTimeMillis(); // 获取当前时间
        long deltaTime = currentTime - lastTime; // 计算距离上次处理请求的时间间隔
        currentSize = Math.max(0, currentSize - deltaTime * leakyRate); // 漏水,更新当前水量
        if (currentSize < bucketSize) { // 如果漏桶中有足够的空间
            currentSize++; // 处理请求,放入漏桶
            lastTime = currentTime; // 更新上次处理请求的时间
            return true; // 允许处理请求
        }
        return false; // 漏桶已满,拒绝处理请求
    }
}

漏桶限流算法的优缺点

优点:
1、简单有效:漏桶限流算法简单易懂,易于实现和部署。
2、平滑限流:漏桶算法以固定速率处理请求,可以实现平滑的限流效果,防止突发请求对系统造成影响。
3、控制请求速率:漏桶限流算法可以有效地控制请求的处理速率,保护后端系统免受突发请求的影响。
缺点:
1、不适用于突发流量:漏桶限流算法无法应对突发流量,因为漏桶的处理速率是固定的,无法根据实际流量动态调整。
2、延迟较大:漏桶限流算法会对请求进行排队,可能会增加请求的处理延迟,不适用于对延迟敏感的场景。

4、令牌桶限流算法

令牌桶限流算法是一种常用的限流算法,用于控制单位时间内请求的数量,保护系统免受突发流量的影响。其基本原理是系统维护一个令牌桶,每个令牌表示一个允许通过的请求。在单位时间内,令牌桶以固定速率生成令牌,请求到达时需要从令牌桶中获取令牌,如果桶中有足够的令牌,则允许请求通过;否则拒绝请求。
在这里插入图片描述

令牌桶限流算法代码如下:

public class TokenBucketRateLimiter {

    private long capacity = 10; // 令牌桶容量
    private long rate = 10; // 令牌生成速率,单位:毫秒
    private AtomicLong tokens = new AtomicLong(10); // 当前令牌数量
    private long lastRefillTime; // 上次令牌补充时间

    public TokenBucketRateLimiter() {
    }

    public TokenBucketRateLimiter(long capacity, long rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.tokens = new AtomicLong(capacity); // 初始时,令牌桶满
        this.lastRefillTime = System.currentTimeMillis(); // 初始化时记录上次补充时间
    }

    // 尝试获取令牌,成功返回true,失败返回false
    public synchronized boolean allowRequest() {
        refillTokens(); // 补充令牌
        long currentTokens = tokens.get(); // 获取当前令牌数量
        if (currentTokens > 0) { // 令牌桶中有令牌
            tokens.decrementAndGet(); // 消费一个令牌
            return true; // 获取令牌成功
        }
        return false; // 令牌桶中无令牌,获取失败
    }

    // 补充令牌
    private synchronized void refillTokens() {
        long now = System.currentTimeMillis(); // 当前时间
        long elapsedTime = now - lastRefillTime; // 距离上次补充令牌的时间间隔
        long tokensToAdd = elapsedTime * rate / 1000; // 计算需要补充的令牌数量
        if (tokensToAdd > 0) { // 需要补充令牌
            tokens.set(Math.min(capacity, tokens.get() + tokensToAdd)); // 补充令牌,并确保不超过容量
            lastRefillTime = now; // 更新上次补充时间
        }
    }
}

令牌桶算法的优缺点

优点:
1、稳定性高:令牌桶算法能够控制请求的处理速度,从而使系统的负载变得稳定。
2、精确度高。算法可以根据实际情况动态调整生成令牌的速率,实现较高精度的限流。
缺点:
1、对短时请求难以处理。在短时间内有大量请求到来时,可能会导致令牌快速消耗完,从而限流。
2、时间精确要求高。算法需要在固定的时间间隔内生成令牌,对系统时间的准确性要求较高。

  • 13
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Hystrix提供了限流功能,用于在高并发访问下控制系统资源。在Spring Cloud架构中,可以在网关和每个微服务中启用Hystrix进行限流处理。其中,Hystrix支持两种限流算法:线程隔离和信号量隔离。 线程隔离模式下的限流算法是通过控制调用线程允许请求HystrixCommand.GetFallback()的最大数量来实现的。可以通过配置属性hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds来设置超时时间,以及hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests来设置允许的最大并发请求数量。如果降级线程的并发数超过最大限制数,则会抛出HystrixRuntimeException异常。 信号量隔离模式下的限流算法是通过控制信号量的数量来实现的。可以通过配置属性hystrix.command.default.execution.isolation.semaphore.maxConcurrentRequests来设置允许的最大并发请求数量。如果超过最大限制数,则会直接抛出HystrixRuntimeException异常。 综上所述,Hystrix提供了线程隔离和信号量隔离两种限流算法,可以根据具体场景选择适合的算法来进行限流处理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *2* *3* [Hystrix 限流、超时、熔断和降级](https://blog.csdn.net/zsh2050/article/details/114878025)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 100%"] [ .reference_list ]
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值