当前位置: 首页 > news >正文

Redis限流踩坑记:我的incr+expire组合拳为何打出了永不过期的Key?

Redis限流踩坑记:incr+expire组合为何会制造"永生Key"?

那天下午,运维群突然炸开了锅——OCR服务的错误率曲线像坐了火箭一样直线上升。用户反馈页面不断弹出"操作过于频繁"的提示,可后台数据显示这些用户当天的调用次数还不到限额的十分之一。作为负责接口限流逻辑的开发者,我盯着监控大屏上那些TTL显示为-1的Redis Key,突然意识到自己掉进了一个经典的Redis陷阱。

1. 故障现场:限流系统为何突然失效?

事情要从三个月前上线的新版OCR服务说起。为了防范恶意刷接口,我们设计了每分钟50次的调用限制。当时调研了几种方案后,选择了最轻量级的Redis计数方案:

// 简化后的限流检查伪代码 func CheckRateLimit(userID string) bool { key := "rate_limit:" + userID count, err := redis.Get(key) if err == redis.Nil { // Key不存在时初始化 redis.Set(key, 1, time.Minute) return true } if count >= 50 { return false } redis.Incr(key) return true }

这套逻辑在测试环境运行良好,直到那天下午出现诡异现象:部分用户的Key变成了"永生状态"。通过Redis命令查看问题Key时,发现了两个致命特征:

127.0.0.1:6379> TTL rate_limit:user123 (integer) -1 # 永不过期 127.0.0.1:6379> GET rate_limit:user123 "2147483647" # 已经累加到最大值

更棘手的是,这些Key由于没有设置过期时间,会永久占用Redis内存。随着时间推移,内存使用率持续攀升,最终触发了OOM killer。

2. 原理深挖:incr与expire的原子性陷阱

通过复现测试,我们终于锁定了问题发生的精确条件:

  1. Key的TTL剩余最后1秒时,用户发起请求
  2. 服务端通过GET判断未超限(此时Key仍存在)
  3. 执行OCR识别耗时超过1秒(网络I/O或计算密集型操作)
  4. 执行INCR时Key已自然过期

此时Redis的行为出人意料:

操作时序Redis行为产生后果
TTL=1s时GET返回当前计数值判断未超限
Key自然过期Redis自动删除Key原始计数消失
执行INCR创建新Key并设为1TTL默认为-1

关键发现INCR命令在操作不存在的Key时,会将其初始化为1,但不会继承之前的过期时间。这与SET命令的NX/XX参数行为完全不同。

3. 临时补救:双重检查真的可靠吗?

我们首先尝试了"存在性检查+TTL验证"的复合方案:

func SafeIncr(key string) error { // 第一重检查:Key是否存在 exists, err := redis.Exists(key) if exists == 0 { return redis.Set(key, 1, time.Minute) } // 第二重检查:TTL是否有效 ttl, err := redis.TTL(key) if ttl < 0 { // -1表示无过期时间,-2表示不存在 return redis.Expire(key, time.Minute) } _, err = redis.Incr(key) return err }

这套方案在大多数情况下工作正常,但依然存在理论上的竞态条件:

  1. 检查TTL时返回剩余10ms
  2. 网络延迟导致INCR命令在Key过期后到达
  3. 依然会产生无过期时间的Key

4. 终极方案:Lua脚本实现原子操作

Redis的Lua脚本可以保证多个命令的原子执行,最终我们采用了如下方案:

-- KEYS[1] 限流Key -- ARGV[1] 限流阈值 -- ARGV[2] 过期时间(秒) local current = redis.call('INCR', KEYS[1]) if current == 1 then -- 首次设置时初始化过期时间 redis.call('EXPIRE', KEYS[1], ARGV[2]) else -- 检查是否永不过期 local ttl = redis.call('TTL', KEYS[1]) if ttl == -1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end end return current

对应的Go实现:

var rateLimitScript = redis.NewScript(` local current = redis.call('INCR', KEYS[1]) if current == 1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) else local ttl = redis.call('TTL', KEYS[1]) if ttl == -1 then redis.call('EXPIRE', KEYS[1], ARGV[2]) end end return current `) func AtomicIncr(key string, limit int, expiration time.Duration) (int, error) { return rateLimitScript.Run( redisClient, []string{key}, limit, int(expiration.Seconds()) ).Int() }

这个方案完美解决了三个问题:

  1. 初始化计数与设置过期时间的原子性
  2. 防止INCR导致Key永不过期
  3. 避免多次网络往返带来的竞态条件

5. 生产环境优化实践

在实际部署中,我们还做了以下优化:

内存保护措施

  • 对Lua脚本增加执行超时监控
  • 设置Redis的maxmemory-policy为volatile-lru
  • 增加监控告警规则,检测TTL为-1的限流Key

性能对比测试

方案QPS平均延迟内存占用
原始方案12k1.2ms持续增长
双重检查9k1.8ms稳定
Lua脚本11k1.3ms稳定

异常处理建议

// 良好的错误处理范例 func HandleOCRRequest(userID string) error { count, err := AtomicIncr(userKey(userID), 50, time.Minute) if err != nil { metrics.Increment("redis_errors") return fallbackToLegacyRateLimit() // 降级方案 } if count > 50 { return ErrRateLimitExceeded } // 处理业务逻辑 }

那次事故后,我们在所有使用Redis计数的场景都加上了"防永生"检查。现在每次看到TTL值为-1的Key,都会想起那个手忙脚乱的下午——技术债总是要还的,区别只是主动还是被动。

http://www.jsqmd.com/news/753703/

相关文章:

  • 2026长沙卫生间免砸砖防水、外墙、地下室、楼顶渗漏+彩钢瓦、阳光房隔热 本地专业防水公司TOP5权威推荐(2026年5月本地最新深度调研) - 企业资讯
  • 告别死记硬背!用‘场景+功能’思维图解SAP FICO核心事务代码(附记忆技巧)
  • 嵌入式C++安全编码Checklist(仅限认证工程师发放):含137条可自动化校验规则、SonarQube插件配置包及TÜV认证报告引用模板
  • Rightmove 房源自动化爬取与飞书多维表格同步系统 — 完整技术方案
  • Conda安装环境总报错?可能是你的environment.yml没写对(避坑指南)
  • 2025届毕业生推荐的五大AI辅助论文网站实测分析
  • MAE框架:多智能体协同进化提升大语言模型性能
  • 第十章:定时任务与自动化(Cron)
  • 为什么92%的.NET开发者在.NET 9中AI功能踩坑?——6个被文档刻意忽略的关键配置陷阱(含VS2022 v17.11兼容性避雷清单)
  • gRPC 与 Protobuf 实战指南
  • 构建个人音频库:跨平台下载工具的技术实现与实践指南
  • 2026天津卫生间免砸砖防水、外墙、地下室、楼顶渗漏+彩钢瓦、阳光房隔热 本地专业防水公司TOP5权威推荐(2026年5月本地最新深度调研) - 企业资讯
  • Node.js 回调地狱导致 Event Loop blocked 警告如何定位和优化
  • 2026年RFID资产盘点系统横评:功能、服务谁更强?
  • SkillLite 原生系统级沙箱功能代码导览
  • 别再只重启服务了!解决Jetson Nano上jtop失效的深层原因与预防指南
  • 2026最权威的十大AI辅助写作方案实际效果
  • 构建本地化个人知识搜索引擎:Memex的语义搜索与自托管实践
  • 告别枯燥代码!用Screen Painter像画图一样设计SAP界面(ABAP Dialog程序实战)
  • 第四章:CLI/TUI 与会话管理
  • 2026徐州卫生间免砸砖防水、外墙、地下室、楼顶渗漏+彩钢瓦、阳光房隔热 本地专业防水公司TOP5权威推荐(2026年5月本地最新深度调研) - 企业资讯
  • 告别手敲命令!个人开源 AI 运维神器 AITerm,用自然语言远程管理服务器
  • 解放游戏时间:MAA明日方舟助手如何让日常任务自动化成为现实
  • 2025届学术党必备的六大AI写作方案横评
  • 2026 环保设备工程厂家技术深度测评:从核心指标看行业优质供给 - 小艾信息发布
  • 招行:开始闯入“龙虾”圈,openclaw 应用正忙,《银行业务智能体构建:通用业务智能体OpenClaw+Skills+RAG+Agent构建案例实操》
  • 分类数据集 - 人脸遮挡检测图像分类数据集下载
  • 2026苏州卫生间免砸砖防水、外墙、地下室、楼顶渗漏+彩钢瓦、阳光房隔热 本地专业防水公司TOP5权威推荐(2026年5月本地最新深度调研) - 企业资讯
  • WSL2里systemctl用不了?试试这3种替代方案(含Docker Desktop配置)
  • 2026咸宁卫生间免砸砖防水、外墙、地下室、楼顶渗漏+彩钢瓦、阳光房隔热 本地专业防水公司TOP5权威推荐(2026年5月本地最新深度调研) - 企业资讯