基于时间步长的while循环实现轻量级本地滑动窗口限流,不依赖外部存储与复杂框架,纯内存+时间戳滚动管理窗口数据,适用于单机微服务及中低QPS场景。

用基于时间步长的 while 循环实现轻量级本地滑动窗口限流,核心是**不依赖外部存储、不引入复杂框架、靠纯内存+时间戳滚动管理窗口数据**。它适合单机微服务(如 Spring Boot 独立部署实例)、QPS 中低(
关键设计:用时间步长驱动窗口滑动
所谓“时间步长”,是指把整个滑动窗口切分成若干等长小段(比如 1 秒窗口分 10 段,每段 100ms)。每个步长对应一个计数器,随系统时间推进,旧步长自动失效、新步长动态激活——这正是 while 循环能高效完成的事:持续检查并清理过期槽位。
- 窗口总时长固定(如 1000ms),步长数量固定(如 10),每个步长代表 100ms
- 用数组或循环队列存各步长请求数,索引由
(currentTime / stepMs) % slotCount动态计算 -
while循环不用于阻塞等待,而是用于“批量清理过期槽位”:从当前最老有效槽开始,逐个判断其时间戳是否已过期,是则清零
Java 实战代码结构(无锁、无第三方依赖)
以下是一个可直接运行的轻量级实现,重点看 allowRequest() 中的 while 清理逻辑:
public class SimpleSlidingWindowLimiter {
private final int[] slots; // 步长计数数组,长度 = slotCount
private final long[] timestamps; // 对应每个槽的时间戳(毫秒)
private final int slotCount;
private final long stepMs;
private final int limit;
<pre class="brush:php;toolbar:false;">private final AtomicLong lastCleanTime = new AtomicLong();
public SimpleSlidingWindowLimiter(int limit, long windowMs, int slotCount) {
this.limit = limit;
this.slotCount = slotCount;
this.stepMs = windowMs / slotCount;
this.slots = new int[slotCount];
this.timestamps = new long[slotCount];
Arrays.fill(timestamps, -1L);
}
public boolean allowRequest() {
long now = System.currentTimeMillis();
int idx = (int) ((now / stepMs) % slotCount);
// 用 while 循环批量清理所有过期槽位(从上一次清理点开始推)
long cleanStart = lastCleanTime.get();
while (cleanStart < now - (slotCount * stepMs)) {
int cleanIdx = (int) ((cleanStart / stepMs) % slotCount);
if (timestamps[cleanIdx] != -1 && timestamps[cleanIdx] <= cleanStart) {
slots[cleanIdx] = 0;
timestamps[cleanIdx] = -1;
}
cleanStart += stepMs;
}
lastCleanTime.set(cleanStart);
// 更新当前槽:若时间戳已过期或未初始化,重置;否则累加
if (timestamps[idx] == -1 || timestamps[idx] < now - (slotCount * stepMs)) {
slots[idx] = 1;
timestamps[idx] = now;
} else {
slots[idx]++;
}
// 统计当前窗口内所有有效槽的请求总数
int sum = 0;
for (int i = 0; i < slotCount; i++) {
if (timestamps[i] != -1 && timestamps[i] > now - (slotCount * stepMs)) {
sum += slots[i];
}
}
return sum <= limit;
}}
说明:
– while 块负责“懒清理”,只在每次请求时顺手清理掉明确过期的槽,避免定时任务开销
– 时间判断用 now - (slotCount * stepMs) 定义窗口起始边界,确保统计的是最近完整窗口
– 所有操作都在内存中,无锁(仅靠数组和原子变量),吞吐高、延迟低
如何集成到 Spring Boot 微服务
作为拦截器或 AOP 切面使用,按接口路径或用户 ID 构建限流 key:
- 定义 Bean:将
SimpleSlidingWindowLimiter声明为@Scope("prototype"),按需创建不同维度的限流器(如/order/create用 100 QPS/1s,/user/profile用 500 QPS/1s) - AOP 示例:在切点方法执行前调用
limiter.allowRequest(),返回 false 则抛出ResponseStatusException(HttpStatus.TOO_MANY_REQUESTS) - 注意:每个限流维度(如 user:123、api:/login)需独立实例,避免共享状态冲突
适用边界与优化提示
这个方案不是万能的,但够用、可控、易调试:
- ✅ 单机有效,多实例需配合分布式方案(如 Redis + Lua)或升级为 Sentinel
- ✅ 内存占用极小(例如 100ms 步长 × 10 槽 = 仅 10 个 int + 10 个 long)
- ⚠️ 不适用于超大窗口(如 5 分钟窗口分 300 步长)——此时建议改用时间轮或 Redisson
- ⚠️ 高并发下极端情况可能因
while清理略慢导致短暂超限,可通过预分配时间戳范围或双缓冲结构进一步优化

















