闭包实现滑动窗口限流器的核心是封装时间戳数组、窗口大小和清理逻辑,每次调用时动态剔除过期时间戳并判断长度是否超限,从而实现“最近N秒内”的精准计数,天然支持多实例隔离与状态私有化。

用闭包实现滑动窗口限流拦截器,核心是把时间窗口、请求计数和清理逻辑封装在闭包内,避免外部污染,同时保持状态私有且可复用。
滑动窗口的基本思路
滑动窗口不是固定时间段(如“每秒最多10次”),而是看“最近 N 秒内”的请求数。比如窗口大小 60 秒,允许最多 100 次请求——那么任意时刻,只统计时间戳落在 [当前时间 - 60s, 当前时间] 内的请求。
实际中常用“时间桶”或“队列记录时间戳”两种方式。闭包适合后者:维护一个按时间排序的请求时间戳数组,每次请求时剔除过期项,再判断长度是否超限。
闭包封装限流器工厂函数
用函数返回一个限流函数,内部变量(如 timestamps 数组、窗口大小、最大请求数)被闭包捕获,每个限流器实例互不干扰:
在 Java 中初始化和管理阿里云 SDK客户端。包括单例模式、线程安全、endpoint 与 region 配置、VPC 终端节点、同步与异步等。
立即学习“Java免费学习笔记(深入)”;
function createSlidingWindowLimiter(windowMs = 60000, maxRequests = 100) {
const timestamps = []; // 存储每次请求的时间戳(毫秒)
<p>return function() {
const now = Date.now();</p><pre class="brush:php;toolbar:false;">// 清理过期时间戳:只保留 windowMs 时间范围内的
while (timestamps.length > 0 && timestamps[0] <= now - windowMs) {
timestamps.shift(); // 从头部移除过期项(O(n)但窗口不大时可接受)
}
// 判断是否超限
if (timestamps.length >= maxRequests) {
return false; // 拦截
}
timestamps.push(now); // 记录本次请求
return true; // 放行}; }
作为拦截器集成到请求流程
可在 fetch 封装、Axios 请求拦截器或 Express 中间件里使用。例如在前端封装 fetch:
const rateLimit = createSlidingWindowLimiter(10000, 5); // 10秒内最多5次
<p>async function limitedFetch(url, options = {}) {
if (!rateLimit()) {
throw new Error('Request limit exceeded');
}
return fetch(url, options);
}服务端(Express)示例:
const ipLimiter = new Map(); // 按 IP 隔离限流状态
app.use('/api/', (req, res, next) => {
const ip = req.ip;
if (!ipLimiter.has(ip)) {
ipLimiter.set(ip, createSlidingWindowLimiter(60000, 30));
}
const limiter = ipLimiter.get(ip);
if (!limiter()) {
return res.status(429).json({ error: 'Too Many Requests' });
}
next();
});优化点与注意事项
- 性能考虑:频繁 shift() 在高并发下开销大;可改用双端队列模拟(如用索引 head/tail 管理数组),或换用 Map + 定时清理
- 精度权衡:Date.now() 是毫秒级,对秒级窗口足够;若需更高精度(如 100ms 窗口),注意浏览器/Node 的定时器抖动
- 内存管理:长时间运行需防 timestamps 数组无限增长;上面的清理逻辑已覆盖,但建议加兜底(如限制数组最大长度为 maxRequests × 2)
- 多实例隔离:每个 createSlidingWindowLimiter() 调用生成独立闭包,天然支持不同接口、不同用户、不同 IP 的差异化限流策略
不复杂但容易忽略细节:闭包让状态私有、复用灵活,而滑动窗口的关键在于“动态裁剪”而非“静态计数”。写的时候盯住两个动作——清理过期 + 判断长度,其余都是围绕它展开。

















