从令牌桶到漏桶:探究接口限流的两种经典算法

目录

1. 令牌桶算法

                  1.1 漏桶算法的原理:

1.2 漏桶算法的作用:

1.3 实现用例

2. 漏桶算法

2.1 令牌桶算法的原理:

2.2 令牌桶算法的作用:

2.3 实现用例


1. 令牌桶算法

1.1 漏桶算法的原理:

  • 漏桶:像一个虚拟的漏桶一样,有一个固定的容量,可以存放一定数量的请求。
  • 请求进入漏桶:当请求到达时,会被放入漏桶中,占据一定的容量。
  • 处理请求:系统以恒定的速率处理漏桶中的请求,不受请求到达的速率影响。
  • 请求离开漏桶:每个请求在被处理后,以恒定的速率从漏桶中释放出去,就像水从漏桶中流出一样。

1.2 漏桶算法的作用:

  • 平滑流量:漏桶算法能够平滑地控制系统输出的流量,使得系统能够稳定地提供服务,防止突发大量请求导致系统崩溃或性能下降。
  • 控制输出速率:通过设置恒定的处理速率,漏桶算法可以控制系统对外提供服务的输出速度,保护系统的稳定性和可用性。
  • 保护系统资源:漏桶算法可以限制系统对资源的消耗,防止恶意请求或异常情况下的过载对系统造成损害。
  • 平衡系统负载:漏桶算法可以分散请求的压力,平衡系统的负载,避免部分资源过度利用而导致其他资源无法正常工作。

1.3 实现用例

  •  逻辑类
package com.jmh.demo03.current.utils;

public class RateLimiter {
    private static RateLimiter instance;
    private final int maxTokens;
    private final int tokensPerSecond;
    private int availableTokens;
    private long lastRefillTimestamp;

    private RateLimiter(int maxTokens, int tokensPerSecond) {
        this.maxTokens = maxTokens;
        this.tokensPerSecond = tokensPerSecond;
        this.availableTokens = maxTokens;
        this.lastRefillTimestamp = System.currentTimeMillis();
    }

    public static synchronized RateLimiter getInstance(int maxTokens, int tokensPerSecond) {
        if (instance == null) {
            instance = new RateLimiter(maxTokens-1, tokensPerSecond);
        }
        return instance;
    }

    public synchronized boolean isAllowed() {
        refillTokens();

        if (availableTokens > 0) {
            availableTokens--;
            return true;
        }

        return false;
    }

    private void refillTokens() {
        long currentTime = System.currentTimeMillis();
        long elapsedTime = currentTime - lastRefillTimestamp;
        long refillInterval = 1000 / tokensPerSecond;
        int tokensToAdd = (int)(elapsedTime / refillInterval);

        if (tokensToAdd > 0) {
            lastRefillTimestamp = currentTime;

            if (availableTokens + tokensToAdd <= maxTokens) {
                availableTokens += tokensToAdd;
            } else {
                availableTokens = maxTokens;
            }
        }
    }
}
  •  调用类
package com.jmh.demo03.current.controller;

import com.jmh.demo03.current.utils.LeakyBucketRateLimiter;
import com.jmh.demo03.current.utils.RateLimiter;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author 蒋明辉
 * @data 2023/8/8 16:16
 */
@RequestMapping("/current")
@RestController
public class currentController {

    //令牌桶
    //参数:容量(10)和令牌产生速率(10)。
    //工作原理:在令牌桶中以固定的速率生成令牌,每个令牌代表一个请求。仅当有令牌可用时,才能进行接口调用。
    //效果:在开始时,令牌桶中有 10 个令牌。每秒钟会生成 10 个新令牌,所以在第一秒内您可以连续调用接口 10 次。如果超过了 10 次调用,则需要等待后续的秒数以获取新的令牌。
    private final RateLimiter rateLimiter = RateLimiter.getInstance(10, 1);

    /**
     * 令牌桶限流
     *
     * @return 限流结果
     */
    @PostMapping("demo01")
    @SneakyThrows
    public boolean demo01() {

        return rateLimiter.isAllowed();
    }
}

2. 漏桶算法

2.1 令牌桶算法的原理:

  • 令牌生成:在令牌桶中以固定速率生成令牌,比如每秒生成一定数量的令牌。
  • 令牌消耗:当请求到达时,需要从令牌桶中获取一个令牌,如果桶中有可用的令牌,则将其发放给请求,并进行处理;如果桶中没有足够的令牌,则拒绝该请求。
  • 令牌存储:令牌桶具有一个最大容量,即令牌桶中可以存储的最大令牌数量。当令牌生成速度超过令牌消耗速度时,多余的令牌会被存储在桶中,但不会超过最大容量。

2.2 令牌桶算法的作用:

  • 平滑流量:令牌桶算法能够平滑地控制请求的流量,使得系统能够以稳定的速率处理请求,防止突发大量请求导致系统崩溃或性能下降。
  • 控制访问速率:通过限制令牌生成的速率,令牌桶算法可以控制系统对外提供服务的访问速率,保护系统的稳定性和可用性。
  • 灵活适应突发流量:令牌桶算法允许在短时间内产生大量的令牌,以处理短期的突发流量,而不会导致长期的性能问题。
  • 公平性:令牌桶算法保证了请求的公平性,每个请求在获取令牌时都是平等的,没有特权或优先级。

2.3 实现用例

  • 逻辑类
package com.jmh.demo03.current.utils;

import java.time.Duration;
import java.time.Instant;

public class LeakyBucketRateLimiter {
    private final int capacity; // 漏斗容量
    private final int rate; // 每秒流出的速率
    private int water; // 当前水量
    private Instant lastRequestTime; // 上次请求时间

    public LeakyBucketRateLimiter(int capacity, int rate) {
        this.capacity = capacity;
        this.rate = rate;
        this.water = 0;
        this.lastRequestTime = Instant.now();
    }

    public synchronized boolean isAllowed() {
        Instant now = Instant.now();
        Duration elapsedTime = Duration.between(lastRequestTime, now);
        lastRequestTime = now;

        int elapsedSeconds = (int) elapsedTime.getSeconds();
        water = Math.max(0, water - elapsedSeconds * rate);

        if (water < capacity) {
            water++;
            return true; // 允许请求通过
        }

        return false; // 限制请求访问
    }
}
  • 调用类
package com.jmh.demo03.current.controller;

import com.jmh.demo03.current.utils.LeakyBucketRateLimiter;
import com.jmh.demo03.current.utils.RateLimiter;
import lombok.SneakyThrows;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.concurrent.TimeUnit;

/**
 * @author 蒋明辉
 * @data 2023/8/8 16:16
 */
@RequestMapping("/current")
@RestController
public class currentController {

    //漏桶
    //参数:容量(10)和流出速率(10)。
    //工作原理:漏桶按照固定的速率流出请求,不管请求是否连续到达。当漏桶存储的请求数量超过容量时,多余的请求将被丢弃。
    //效果:在开始时,漏桶为空,可以立即调用接口。但是,一旦第一秒内的调用超过了容量(10 次),后续的请求将被漏桶丢弃,直到漏桶重新流出新的请求(每秒 10 次)。
    private final LeakyBucketRateLimiter limiter = new LeakyBucketRateLimiter(10, 10);

    /**
     * 漏桶限流
     * @return 限流结果
     */
    @PostMapping("demo02")
    @SneakyThrows
    public boolean demo02() {

        return limiter.isAllowed();
    }

}

猜你喜欢

转载自blog.csdn.net/m0_63300795/article/details/132180755