限流算法详解 - 滑动窗口算法代码实现
代码以下是一个使用 Java 实现滑动窗口限流算法的完整示例。该算法通过记录每个请求的时间戳并清理窗口之外的过期记录从而精确控制时间窗口内的请求数量。importjava.util.concurrent.ConcurrentLinkedQueue;importjava.util.concurrent.atomic.AtomicInteger;importjava.util.concurrent.locks.ReentrantLock;/** * 滑动窗口限流器 * 经典实现维护一个队列存储最近窗口内所有请求的时间戳 * 每次新请求到达时移除窗口外的旧请求然后判断当前窗口内请求数是否超过阈值 */publicclassSlidingWindowRateLimiter{// 窗口大小毫秒privatefinallongwindowSizeMs;// 窗口内允许的最大请求数privatefinalintmaxRequests;// 存储请求时间戳的队列使用线程安全的队列 外部锁privatefinalConcurrentLinkedQueueLongtimestampsnewConcurrentLinkedQueue();// 保证清理和添加操作的原子性privatefinalReentrantLocklocknewReentrantLock();publicSlidingWindowRateLimiter(longwindowSizeMs,intmaxRequests){if(windowSizeMs0||maxRequests0){thrownewIllegalArgumentException(windowSizeMs 和 maxRequests 必须大于 0);}this.windowSizeMswindowSizeMs;this.maxRequestsmaxRequests;}/** * 尝试获取一个许可 * return true 表示允许通过false 表示被限流 */publicbooleantryAcquire(){longnowSystem.currentTimeMillis();lock.lock();try{// 1. 清理窗口之外的过期时间戳longwindowStartnow-windowSizeMs;while(!timestamps.isEmpty()timestamps.peek()windowStart){timestamps.poll();}// 2. 检查当前窗口内的请求数是否已达上限if(timestamps.size()maxRequests){timestamps.offer(now);returntrue;}else{returnfalse;}}finally{lock.unlock();}}/** * 获取当前窗口内的请求数用于观察 */publicintgetCurrentRequestCount(){lock.lock();try{longnowSystem.currentTimeMillis();longwindowStartnow-windowSizeMs;// 清理过期数据保持队列准确while(!timestamps.isEmpty()timestamps.peek()windowStart){timestamps.poll();}returntimestamps.size();}finally{lock.unlock();}}// ---------- 测试方法 ----------publicstaticvoidmain(String[]args)throwsInterruptedException{// 定义限流规则3秒内最多允许 5 次请求SlidingWindowRateLimiterlimiternewSlidingWindowRateLimiter(3000,5);// 模拟 12 次请求每次间隔 200 毫秒for(inti1;i12;i){booleanallowedlimiter.tryAcquire();System.out.printf(请求 %2d | 结果%-8s | 窗口内当前请求数%d%n,i,allowed?允许通过:限流拦截,limiter.getCurrentRequestCount());if(!allowed){System.out.println( - 触发限流请稍后重试);}Thread.sleep(200);// 间隔 200ms 发送一个请求}// 额外演示等待 2 秒后窗口滑动旧请求过期可以重新发送System.out.println(\n等待 2 秒让部分旧请求滑出窗口...);Thread.sleep(2000);for(inti1;i3;i){booleanallowedlimiter.tryAcquire();System.out.printf(延迟后请求 %d | 结果%-8s | 窗口内当前请求数%d%n,i,allowed?允许通过:限流拦截,limiter.getCurrentRequestCount());}}}运行结果示例实际输出取决于系统时间请求 1 | 结果允许通过 | 窗口内当前请求数1 请求 2 | 结果允许通过 | 窗口内当前请求数2 请求 3 | 结果允许通过 | 窗口内当前请求数3 请求 4 | 结果允许通过 | 窗口内当前请求数4 请求 5 | 结果允许通过 | 窗口内当前请求数5 请求 6 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 7 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 8 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 9 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 10 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 11 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 请求 12 | 结果限流拦截 | 窗口内当前请求数5 - 触发限流请稍后重试 等待 2 秒让部分旧请求滑出窗口... 延迟后请求 1 | 结果允许通过 | 窗口内当前请求数2 延迟后请求 2 | 结果允许通过 | 窗口内当前请求数3 延迟后请求 3 | 结果允许通过 | 窗口内当前请求数4算法核心说明数据结构使用ConcurrentLinkedQueue存储窗口内所有请求的时间戳毫秒。清理机制每次请求时计算当前时间往前windowSizeMs的边界移除队列中早于该边界的时间戳。判断逻辑清理后队列的大小即为当前窗口内的请求总数若小于阈值则允许并记录当前时间戳否则拒绝。并发安全通过ReentrantLock保证清理和添加操作的原子性避免多线程环境下计数错误。该实现精确反映了滑动窗口的行为窗口边界随着时间连续滑动能够避免固定窗口算法在边界处的突发流量问题是一种常用的限流算法。