当前位置:首页 > 文章列表 > 数据库 > Redis > Redis 过期键通知怎么做自动清理:订阅、补偿扫描和幂等门禁

Redis 过期键通知怎么做自动清理:订阅、补偿扫描和幂等门禁

来源:17golang原创 2026-07-08 11:13:47 0浏览 收藏

订单草稿、验证码、临时授权、上传会话这类数据,我们经常会给对应的Redis key设置TTL。真正头疼的从来不是Redis到点自动删key,而是key删掉之后,还要同步清理MySQL里的关联状态、对象存储上的临时文件,或是业务侧的各种占位记录。别把Redis过期键通知当成高可靠队列,它本质就是个提醒铃:铃响了就启动清理流程,万一铃没响,也得靠定时补偿扫描把漏下的活补完。

实践要点
  • Redis 过期键通知只保证有概率收到提醒,没法承诺每个过期key都能被消费到。
  • 业务写入临时资源的时候,要同步落一条待清理记录,TTL到期事件只是触发器,不能当作清理的唯一依据。
  • 清理任务要先走幂等门禁校验,再改业务状态,最后删除外部资源,避免重复操作把数据清错。
  • 订阅断开、Redis重启、消费者发布上线的时候都可能丢消息,补偿扫描必须按固定节奏跑才能兜底。

过期事件只负责提醒,真正清理要走任务流水线

Redis 的 keyspace notification 功能开启后,客户端可以订阅类似 __keyevent@0__:expired 的频道,收到某个key已经过期的提醒。这个机制用起来很顺手,尤其适合“到点之后触发一个后续动作”的场景,比如释放草稿锁、删除临时上传文件、注销一次性token。

但它有个很容易被忽略的边界:这个提醒不是持久化消息。订阅客户端掉线、网络抖动、Redis实例重启,都会直接让对应的过期事件丢失。生产环境更建议把它放在整个清理流水线的第一环,只负责尽早触发清理任务;后续流程还是要靠待处理表、状态字段和补偿扫描来兜底。

Redis TTL 到期后通过过期事件触发清理任务的数据生命周期示意图

打开 notify-keyspace-events 前先确认权限和通知范围

如果业务只需要关注过期事件,配置不用开得太宽。常规可以先检查当前配置:

CONFIG GET notify-keyspace-events

需要接收过期事件时,通用的配置写法是:

CONFIG SET notify-keyspace-events Ex

E 表示发布keyevent类型事件,x 表示仅推送过期相关事件。线上环境不要只临时输命令改完就完事,最好把配置写入Redis配置文件或者交给配置管理系统统一下发,否则实例重启后配置会恢复成默认值,直接收不到通知。

检查项 建议做法 容易踩的坑
事件范围 只打开过期事件需要的标记位 把所有事件标记全打开,低峰看不出问题,高峰会多出大量无用通知占用带宽
订阅库号 按业务实际使用的DB编号订阅对应频道 硬编码只监听0号库,结果测试环境和线上用的库号不同,完全收不到事件
消费者身份 用独立服务或者独立进程消费事件 把消费逻辑挂在Web请求进程里,服务发布重启的时候整段事件都错过
兜底数据 把清理上下文写入业务表的待清理状态 只依赖Redis key名带业务信息,等key过期删除后反而找不到对应的业务上下文

业务写入时就把清理上下文准备好

比较稳妥的实现模型是:创建临时资源的同时,写Redis TTL key和一条业务侧的待清理记录。比如上传会话可以写成 upload_session:{id},TTL设30分钟;MySQL里则保留 session_id、object_key、expire_at、cleanup_status。等Redis过期事件推送过来,消费者只需要从key里解析出业务ID,再回业务表读取完整的清理上下文就行。

这么做有两个明显好处。第一,Redis key删除的时候不会把对应的业务信息一起清掉;第二,即便事件没收到,直接扫描 expire_at 且 cleanup_status='pending' 的记录,照样能把遗漏的任务找回来。

清理任务要先过幂等门禁

清理这类操作最怕“看起来执行成功,其实重复跑了第二次”。比如删除对象存储文件重复调用没什么影响,但把订单状态改成已取消、把库存回滚、把积分返还这类操作,就完全不能靠运气。任务启动前先做一层门禁校验:只有 cleanup_status='pending' 的记录能被抢占,抢占成功后立刻把状态改成 processing,同时带上 cleanup_worker 和 locked_at。

UPDATE upload_session
SET cleanup_status = 'processing',
    cleanup_worker = 'redis-expire-worker-01',
    locked_at = NOW()
WHERE session_id = ?
  AND cleanup_status = 'pending';

这条更新语句如果影响行数为1,说明当前消费者成功拿到了这条记录的处理权;如果影响行数为0,说明这条记录已经被其他消费者处理,或者状态不满足清理条件。后续的删除外部资源、更新完成状态动作,全部放在这层门禁之后执行。这个顺序比“先删文件再改状态”要稳妥很多,出问题的时候也更容易追溯补偿。

订阅漏消息时,补偿扫描要补上

Redis过期事件的优势是触发及时,补偿扫描的优势是足够可靠。两者搭配使用,才能成为可以直接放到生产环境的自动清理方案。扫描任务可以每1到5分钟跑一次,按 expire_at 分批拉取待清理记录,每批数量控制在几百条以内,避免一次性查询把数据库打满。

扫描的时候不能只看过期时间,还要专门处理卡在 processing 的旧记录。比如 locked_at 超过10分钟仍未执行完成,可以把它重新置回 pending,或者打上 retrying 状态交给下一轮扫描任务处理。这里的超时阈值要按清理动作的实际耗时来定,删除对象存储文件和调用第三方接口的等待窗口肯定不能设成一样的。

Redis 过期事件漏消息后通过补偿扫描和幂等门禁恢复清理任务的流程图

失败处理不要只写固定重试次数

配置重试次数是必要的,但不够。清理任务失败时至少要记录三类信息:失败所在的执行阶段、错误摘要、下一次允许重试的时间。比如Redis事件已经触发,业务表抢占成功,但对象存储返回404,这和数据库更新超时完全是两类问题。404大概率说明资源已经被提前删除,适合标记为完成并备注说明;数据库超时则更适合做短延迟重试。

一套简单的适配规则可以这样定:

  • 资源不存在:确认业务规则允许后标记完成,备注写清外部资源已不存在。
  • 网络超时:按30秒、2分钟、10分钟做指数递增重试。
  • 权限拒绝:停止自动重试,直接触发告警,这类问题重复跑大概率还是失败。
  • 业务状态变化:放弃清理或者转人工复核,避免把用户刚恢复的正常会话误删掉。

上线前用三组用例验证流程

这个功能很容易在开发环境测试时显得一切正常,毕竟手动设个10秒TTL,消费者一直在线,很快就能收到事件。上线前至少补三组测试用例:消费者断开期间key过期、Redis重启后key过期、清理任务执行到一半进程被杀死。所有漏下的任务都能被补偿扫描捞回来,才说明这套链路没有把可靠性完全押在订阅连接上。

另外,告警不要只盯着消费者进程是否存活。更有参考价值的是这些指标:待清理记录总数量、最老待处理记录的滞留时长、处理中超时的任务数量、连续失败的任务数量。这些指标能直观反馈清理工作是不是真的执行完了,而不是服务只是表面还在运行。

常见问题

Redis 过期键通知能替代消息队列吗?

不建议这么做。它只适合做轻量提醒,没法承诺消息100%可靠投递。需要强可靠的场景,还是应该用待处理表、Stream、专业消息队列或者任务调度系统来承载任务全生命周期状态。

过期事件没有收到,一定是 Redis 配置错了吗?

不一定。配置没开、订阅频道不匹配、消费者连接断开、实例重启都可能导致收不到。先查 notify-keyspace-events,再看消费者连接日志,最后确认后续流程有没有被补偿扫描正常接管。

补偿扫描会不会和订阅消费者同时处理同一条记录?

确实会遇到同一条记录被两边同时捕捉到的情况,所以幂等门禁必须放在所有清理动作的最前面。只有抢占状态成功的任务才能继续往下执行,不会出现重复清理问题。

清理任务失败后应该一直无限重试吗?

不要一刀切配置无限重试。网络抖动类的临时故障可以重试,权限拒绝和业务状态冲突这类场景更适合触发告警或者走人工确认。重试规则最好按失败的具体阶段拆分配置。

小结

用好Redis过期键通知,可以让临时资源的清理动作更及时;如果把它当成唯一可靠的清理链路,可靠性隐患就会藏在不稳定的订阅连接里。更稳妥的做法是把它当成触发器:业务表保存全量清理上下文,消费者先过幂等门禁再执行操作,补偿扫描定时捞取遗漏任务,失败任务进入可观测的重试和告警流程。这样即便过期事件偶尔丢失,系统也不会长期积压脏数据。

版本声明
本文转载于:17golang原创 如有侵犯,请联系study_golang@163.com删除
Redis 热点 key 过期后数据库被打满:缓存击穿怎么排查和修复Redis 热点 key 过期后数据库被打满:缓存击穿怎么排查和修复
上一篇
Redis 热点 key 过期后数据库被打满:缓存击穿怎么排查和修复
VS Code 写 Go 提示 gopls 不可用怎么办:插件、PATH 和保存验证一步排查
下一篇
VS Code 写 Go 提示 gopls 不可用怎么办:插件、PATH 和保存验证一步排查
查看更多
最新文章
查看更多
课程推荐
  • 前端进阶之JavaScript设计模式
    前端进阶之JavaScript设计模式
    设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
    543次学习
  • GO语言核心编程课程
    GO语言核心编程课程
    本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
    516次学习
  • 简单聊聊mysql8与网络通信
    简单聊聊mysql8与网络通信
    如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
    500次学习
  • JavaScript正则表达式基础与实战
    JavaScript正则表达式基础与实战
    在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
    487次学习
  • 从零制作响应式网站—Grid布局
    从零制作响应式网站—Grid布局
    本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
    485次学习
查看更多
AI推荐
  • PubMedQA数据集详解:生物医学问答基准、功能与应用指南
    PubMedQA
    深入了解PubMedQA生物医学问答数据集,涵盖其核心功能、使用方法及在临床决策、药物研发等场景的应用,助力提升NLP模型性能。
    384次使用
  • H2O EvalGPT:开源LLM大模型评估与排行榜工具
    H2O EvalGPT
    H2O EvalGPT是H2O.ai推出的开源LLM评估平台,提供详细的大模型性能排行榜、行业特定基准测试及A/B测试功能,助您快速选择最适合项目的高性能大语言模型。
    457次使用
  • LMArena是什么?伯克利AI模型评估平台使用指南与功能解析
    LMArena
    LMArena是加州大学伯克利分校推出的AI模型匿名评测平台。通过盲测投票机制,用户可对比不同大模型回答并生成实时排行榜,助力开发者优化模型及用户选择最佳AI工具。
    470次使用
  • 斯坦福HELM:大语言模型Holistic Evaluation整体评估框架详解
    HELM
    深入了解斯坦福推出的HELM(Holistic Evaluation of Language Models)大模型评测体系。本文解析其核心功能、安装配置步骤及应用场景,涵盖准确性、公平性、鲁棒性等多维度指标,助力开发者全面优化语言模型性能。
    409次使用
  • MMBench详解:多模态大模型基准测试、功能特点与使用指南
    MMBench
    MMBench是由上海人工智能实验室等机构联合推出的多模态基准测试平台,提供细粒度能力评估、大规模数据集及VLMEvalKit工具。本文详细介绍其核心功能、安装使用方法及应用场景,助力开发者全面评估多模态模型性能。
    237次使用
微信登录更方便
  • 密码登录
  • 注册账号
登录即同意 用户协议 和 隐私政策
返回登录
  • 重置密码