### 一、Redis ZSET 实现滑动窗口限流的核心原理
#### 1. **数据结构选择**
使用 Redis 的 **ZSET(有序集合)** 作为核心数据结构:
• **Score 字段**:存储请求的时间戳(精确到毫秒或秒)
• **Value 字段**:建议使用 UUID 或 MD5 值(避免时间戳重复导致数据覆盖)
#### 2. **滑动窗口的原子操作流程**
通过 Lua 脚本实现以下步骤(以 10 秒窗口、最大 100 次请求为例):
```lua
-- KEYS[1] = 限流键(如 "rate_limit:/api/login:127.0.0.1")
-- ARGV[1] = 窗口大小(秒)
-- ARGV[2] = 当前时间戳(秒)
-- ARGV[3] = 最大请求数
local window_start = tonumber(ARGV[2]) - tonumber(ARGV[1]) -- 计算窗口起始时间
redis.call("ZREMRANGEBYSCORE", KEYS[1], 0, window_start) -- 清理过期数据
local count = redis.call("ZCARD", KEYS[1]) -- 统计当前窗口请求数
if count >= tonumber(ARGV[3]) then
return 0 -- 触发限流
else
redis.call("ZADD", KEYS[1], ARGV[2], ARGV[2]) -- 记录当前请求
redis.call("EXPIRE", KEYS[1], ARGV[1] + 1) -- 设置过期时间
return 1 -- 允许请求
end
```
#### 3. **关键操作解析
• **ZREMRANGEBYSCORE**:动态移除窗口外的旧数据,实现“滑动”效果
• **ZCARD**:统计当前窗口内的请求总数
• **ZADD + EXPIRE**:原子性插入新请求并刷新过期时间
---
### 二、滚动窗口的实现原理对比
#### 1. **滑动窗口 vs 滚动窗口**
| 类型 | 实现原理 | 适用场景 |
|------------|------------------------------------------------------------------------|------------------------------|
| 滑动窗口 | 窗口边界随时间推移动态变化(如每请求一次窗口滑动一次) | 高精度限流(如每秒 1000 次) |
| 滚动窗口 | 窗口按固定时间间隔滚动(如每分钟窗口自动重置) | 简单计数场景(如每日上限) |
#### 2. **滚动窗口的 Redis 实现(基于 INCR)**
```java
// 限制每5秒最多10次请求
public boolean allowRequest(RedisTemplate redis, String key) {
String finalKey = key + ":" + (System.currentTimeMillis() / 5000); // 5秒为一个窗口
Long count = redis.opsForValue().increment(finalKey, 1);
if (count == 1) {
redis.expire(finalKey, 6, TimeUnit.SECONDS); // 设置略大于窗口的过期时间
}
return count <= 10;
}
```
• **优点**:实现简单,内存占用低
• **缺点**:窗口边界突发流量问题(如第4.9秒和第5.1秒可能突破限制)
---
### 三、两种方案的适用场景对比
#### 1. **ZSET 滑动窗口方案**
• **适用场景**:
• 需要精确控制任意时间段的请求密度(如限制每秒 1000 次)
• 高频接口风控(如登录、支付)
• **性能影响**:ZSET 操作复杂度为 O(logN),建议窗口时间 ≤ 300 秒
#### 2. **INCR 滚动窗口方案**
• **适用场景**:
• 低频简单计数(如每日用户发言次数)
• 运维限制 Lua 脚本使用的环境
• **优化技巧**:结合 Pipeline 批量操作降低网络开销
---
### 四、生产环境实践建议
#### 1. **ZSET 方案优化**
• **时间精度**:使用毫秒级时间戳提升控制精度(需调整 Lua 中的计算逻辑)
• **集群适配**:通过 `{hash_tag}` 确保相同 key 路由到同一 Redis 节点
```lua
local key = "rate_limit:{user_123}:/api/login" -- 使用 {} 定义哈希标签
```
#### 2. **监控与告警**
• **Prometheus 指标**:
```java
Counter.build().name("rate_limit_rejected").help("被拒绝的请求数").register();
```
• **Redis 监控项**:
• `memory_usage`:ZSET 内存增长趋势
• `ops_per_sec`:Lua 脚本执行频率
---
### 总结
• **ZSET 滑动窗口**:通过时间戳范围删除实现动态窗口,适合高精度限流
• **INCR 滚动窗口**:基于时间分片计数,适合简单低频场景