常用限流算法
限流(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代码实现示例能帮助你更好地理解和应用限流算法。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)