常用限流算法

限流(Rate Limiting)是保护系统稳定性、优化资源使用和提高服务质量的重要手段,通常在高并发环境下防止系统过载。

常见的限流算法有令牌桶(Token Bucket)、漏桶(Leaky Bucket)、固定窗口计数器(Fixed Window Counter)、滑动窗口计数器(Sliding Window Counter)和滑动窗口日志(Sliding Window Log)。以下是这些算法的详细原理、优缺点分析及其Java代码实现示例。

 

1. 令牌桶算法(Token Bucket)

原理

  • 令牌以固定速率生成并存放在桶中。
  • 每个请求消耗一个令牌,桶中无令牌时请求被拒绝或延迟处理。
  • 桶的容量限制了令牌的最大数量,防止过多的令牌堆积。

优缺点

优点:

  • 灵活性高,支持突发流量。
  • 容易实现。

缺点:

  • 需要精确计时和锁机制,可能增加系统开销。

Java 实现示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * 令牌桶算法实现
 */
public class TokenBucket {
    private final long capacity; // 桶的最大容量
    private final long tokensPerSecond; // 每秒生成的令牌数
    private long tokens; // 当前令牌数
    private long lastRefillTimestamp; // 上次填充令牌的时间戳
    private final ReentrantLock lock = new ReentrantLock(); // 线程安全锁
 
    /**
     * 构造函数
     * @param capacity 桶的最大容量
     * @param tokensPerSecond 每秒生成的令牌数
     */
    public TokenBucket(long capacity, long tokensPerSecond) {
        this.capacity = capacity;
        this.tokensPerSecond = tokensPerSecond;
        this.tokens = capacity; // 初始令牌数等于桶的容量
        this.lastRefillTimestamp = System.nanoTime(); // 初始化上次填充时间
    }
 
    /**
     * 是否允许请求
     * @return 如果允许请求返回true,否则返回false
     */
    public boolean allowRequest() {
        lock.lock();
        try {
            refill(); // 尝试填充令牌
            if (tokens > 0) {
                tokens--; // 消耗一个令牌
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }
 
    /**
     * 填充令牌
     */
    private void refill() {
        long now = System.nanoTime();// 获取当前时间(纳秒)
        long tokensToAdd = (now - lastRefillTimestamp) * tokensPerSecond / 1_000_000_000;// 计算从上一次填充到现在产生的令牌数
        tokens = Math.min(capacity, tokens + tokensToAdd);// 更新令牌数,但不超过桶的容量
        lastRefillTimestamp = now;// 更新上一次填充的时间戳
    }
 
    public static void main(String[] args) throws InterruptedException {
        TokenBucket bucket = new TokenBucket(10, 1); // 创建令牌桶,容量为10,每秒生成1个令牌
        for (int i = 0; i < 20; i++) {
            if (bucket.allowRequest()) {
                System.out.println("Request allowed");
            } else {
                System.out.println("Request denied");
            }
            Thread.sleep(100); // 每100毫秒发起一次请求
        }
    }
}

 

2. 漏桶算法(Leaky Bucket)

原理

  • 请求以固定速率处理,超过容量的请求被丢弃。
  • 类似于一个装满水的桶,水以固定速率漏出。

优缺点

优点:

  • 控制流量的输出速率,非常稳定。
  • 简单易实现。

缺点:

  • 突发流量处理能力差。

Java 实现示例

复制代码
import java.util.LinkedList;
import java.util.Queue;

/**
 * 漏桶算法实现
 */
public class LeakyBucket {
    private final int capacity; // 桶的最大容量
    private final long leakRatePerSecond; // 每秒漏出的请求数
    private long lastLeakTimestamp; // 上次漏出的时间戳
    private final Queue<Long> requests = new LinkedList<>(); // 存储请求的时间戳

    /**
     * 构造函数
     * @param capacity 桶的最大容量
     * @param leakRatePerSecond 每秒漏出的请求数
     */
    public LeakyBucket(int capacity, long leakRatePerSecond) {
        this.capacity = capacity;
        this.leakRatePerSecond = leakRatePerSecond;
        this.lastLeakTimestamp = System.nanoTime(); // 初始化上次漏出时间
    }

    /**
     * 是否允许请求
     * @return 如果允许请求返回true,否则返回false
     */
    public synchronized boolean allowRequest() {
        leak(); // 尝试漏出请求
        if (requests.size() < capacity) {
            requests.add(System.nanoTime()); // 添加请求时间戳
            return true;
        }
        return false;
    }

    /**
     * 漏出请求
     */
    private void leak() {
        long now = System.nanoTime();// 获取当前时间(纳秒)
        long elapsed = now - lastLeakTimestamp;// 计算自上次漏水以来的时间差(纳秒)
        long leaks = elapsed * leakRatePerSecond / 1_000_000_000;// 计算在时间差内应该漏出的请求数
        for (int i = 0; i < leaks && !requests.isEmpty(); i++) {
            requests.poll(); // 移除漏出的请求
        }
        lastLeakTimestamp = now;// 更新上一次漏水的时间戳
    }

    public static void main(String[] args) throws InterruptedException {
        LeakyBucket bucket = new LeakyBucket(10, 1); // 创建漏桶,容量为10,每秒漏出1个请求
        for (int i = 0; i < 20; i++) {
            if (bucket.allowRequest()) {
                System.out.println("Request allowed");
            } else {
                System.out.println("Request denied");
            }
            Thread.sleep(100); // 每100毫秒发起一次请求
        }
    }
}
复制代码

 

3. 固定窗口计数器(Fixed Window Counter)

原理

  • 将时间划分为固定长度的窗口,每个窗口内请求数有上限。

优缺点

优点:

  • 实现简单。
  • 易于理解。

缺点:

  • 窗口边界问题,可能会导致突发流量在窗口边界时被接受。

Java 实现示例

复制代码
import java.util.concurrent.locks.ReentrantLock;

/**
 * 固定窗口计数器实现
 */
public class FixedWindowCounter {
    private final int rate; // 每个窗口允许的最大请求数
    private final long windowSizeInMillis; // 窗口大小(毫秒)
    private long windowStart; // 当前窗口的起始时间
    private int requestCount; // 当前窗口内的请求数
    private final ReentrantLock lock = new ReentrantLock(); // 线程安全锁

    /**
     * 构造函数
     * @param rate 每个窗口允许的最大请求数
     * @param windowSizeInMillis 窗口大小(毫秒)
     */
    public FixedWindowCounter(int rate, long windowSizeInMillis) {
        this.rate = rate;
        this.windowSizeInMillis = windowSizeInMillis;
        this.windowStart = System.currentTimeMillis();
        this.requestCount = 0;
    }

    /**
     * 是否允许请求
     * @return 如果允许请求返回true,否则返回false
     */
    public boolean allowRequest() {
        lock.lock();
        try {
            long now = System.currentTimeMillis();
            if (now >= windowStart + windowSizeInMillis) {
                windowStart = now;
                requestCount = 0; // 重置请求计数
            }
            if (requestCount < rate) {
                requestCount++;
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        FixedWindowCounter counter = new FixedWindowCounter(10, 1000); // 创建固定窗口计数器,每秒最多允许10个请求
        for (int i = 0; i < 20; i++) {
            if (counter.allowRequest()) {
                System.out.println("Request allowed");
            } else {
                System.out.println("Request denied");
            }
            Thread.sleep(100); // 每100毫秒发起一次请求
        }
    }
}
复制代码

 

4. 滑动窗口计数器(Sliding Window Counter)

原理

  • 将时间窗口细分为多个小窗口,统计所有小窗口的请求数。

优缺点

优点:

  • 更精确地控制流量。
  • 可以更好地处理突发流量。

缺点:

  • 实现较为复杂。
  • 需要更多的存储和计算资源。

Java 实现示例

复制代码
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 滑动窗口计数器实现
 */
public class SlidingWindowCounter {
    private final int rate; // 每个窗口允许的最大请求数
    private final long windowSizeInMillis; // 窗口大小(毫秒)
    private final Deque<Long> requestTimestamps = new LinkedList<>(); // 存储请求的时间戳
    private final ReentrantLock lock = new ReentrantLock(); // 线程安全锁

    /**
     * 构造函数
     * @param rate 每个窗口允许的最大请求数
     * @param windowSizeInMillis 窗口大小(毫秒)
     */
    public SlidingWindowCounter(int rate, long windowSizeInMillis) {
        this.rate = rate;
        this.windowSizeInMillis = windowSizeInMillis;
    }

    /**
     * 是否允许请求
     * @return 如果允许请求返回true,否则返回false
     */
    public boolean allowRequest() {
        long now = System.currentTimeMillis();
        lock.lock();
        try {
            while (!requestTimestamps.isEmpty() && requestTimestamps.peekFirst() <= now - windowSizeInMillis) {
                requestTimestamps.pollFirst(); // 移除不在窗口内的请求
            }
            if (requestTimestamps.size() < rate) {
                requestTimestamps.addLast(now); // 添加当前请求时间戳
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SlidingWindowCounter counter = new SlidingWindowCounter(10, 1000); // 创建滑动窗口计数器,每秒最多允许10个请求
        for (int i = 0; i < 20; i++) {
            if (counter.allowRequest()) {
                System.out.println("Request allowed");
            } else {
                System.out.println("Request denied");
            }
            Thread.sleep(100); // 每100毫秒发起一次请求
        }
    }
}
复制代码

 

5. 滑动窗口日志(Sliding Window Log)

原理

  • 记录每个请求的时间戳,动态统计当前窗口内的请求数。

优缺点

优点:

  • 精确控制流量。
  • 更好地处理突发流量。

缺点:

  • 实现复杂。
  • 存储和计算开销较大。

Java 实现示例

复制代码
import java.util.Deque;
import java.util.LinkedList;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 滑动窗口日志实现
 */
public class SlidingWindowLog {
    private final int rate; // 每个窗口允许的最大请求数
    private final long windowSizeInMillis; // 窗口大小(毫秒)
    private final Deque<Long> requestTimestamps = new LinkedList<>(); // 存储请求的时间戳
    private final ReentrantLock lock = new ReentrantLock(); // 线程安全锁

    /**
     * 构造函数
     * @param rate 每个窗口允许的最大请求数
     * @param windowSizeInMillis 窗口大小(毫秒)
     */
    public SlidingWindowLog(int rate, long windowSizeInMillis) {
        this.rate = rate;
        this.windowSizeInMillis = windowSizeInMillis;
    }

    /**
     * 是否允许请求
     * @return 如果允许请求返回true,否则返回false
     */
    public boolean allowRequest() {
        long now = System.currentTimeMillis();
        lock.lock();
        try {
            while (!requestTimestamps.isEmpty() && requestTimestamps.peekFirst() <= now - windowSizeInMillis) {
                requestTimestamps.pollFirst(); // 移除不在窗口内的请求
            }
            if (requestTimestamps.size() < rate) {
                requestTimestamps.addLast(now); // 添加当前请求时间戳
                return true;
            }
            return false;
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        SlidingWindowLog log = new SlidingWindowLog(10, 1000); // 创建滑动窗口日志,每秒最多允许10个请求
        for (int i = 0; i < 20; i++) {
            if (log.allowRequest()) {
                System.out.println("Request allowed");
            } else {
                System.out.println("Request denied");
            }
            Thread.sleep(100); // 每100毫秒发起一次请求
        }
    }
}
复制代码

 

这些限流算法各有优缺点,适用于不同的场景。选择合适的限流算法取决于具体的应用需求和系统特性。希望这些详细的原理说明和Java代码实现示例能帮助你更好地理解和应用限流算法。

 

posted @   zhangleinewcharm  阅读(179)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示