Redis实现分布式滑动+滚动窗口限流

dalang · · 70 次点击 · · 开始浏览    
### 一、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 滚动窗口**:基于时间分片计数,适合简单低频场景
70 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传