Redis ZSET 做延迟队列靠谱吗:score 时间窗、轮询频率和重复消费边界
订单延迟任务最容易被低估:把任务放进 Redis 只要一条命令,真正麻烦的是“什么时候算到期”“谁先取走”“取走后进程崩了怎么办”。用 Redis ZSET 做延迟队列是靠谱的轻量方案,但它不是带确认机制的完整消息队列。把 score 定义成到期时间,再配合短轮询、原子取出和业务幂等,边界就能说清楚。
- ZSET 的 score 存 Unix 时间戳或毫秒时间戳,成员保存唯一 task_id,不要把业务大 JSON 直接塞进成员。
- 轮询窗口使用当前时间作为上界;轮询间隔决定额外延迟,批量大小决定单轮压力。
- Redis 5.0 及以上可用 ZPOPMIN 原子取出最低分任务,取出成功不等于业务处理成功。
- 取出、业务调用、确认不是一个原子事务,必须用 task_id、状态表或结果键抵抗重复消费。
先把 ZSET 延迟队列的职责划清楚
Redis Sorted Set 会按浮点 score 排序,同时保证 member 唯一。延迟队列可以把“到期时间”放进 score,把任务编号放进 member:
ZADD delay:orders 1766745600000 task:8f31
这里的数字只是示例时间戳。生产环境要统一使用毫秒还是秒,写入端和消费端不能各自理解。任务详情建议放在 Hash、数据库或业务表中,ZSET 只承担排队索引,这样重试和审计更好处理。

写入阶段:score 只表达什么时候可以取
延迟队列的入队流程通常分成两步:先保存任务正文,再写入 ZSET 索引。两步之间若允许短暂不一致,必须由补偿扫描发现;如果任务不能丢,建议把正文和排队记录放进同一个数据库事务,再由投递器补写 Redis。
-- 到期时间为毫秒时间戳
ZADD delay:orders 1766745600000 task:8f31
HSET task:8f31 status pending payload "..."
不要用订单金额、重试次数之类会变化的字段当 score。score 只负责排序,任务状态应该单独保存,否则一次重试改分数后,很难判断它到底是新任务还是旧任务。
消费阶段:轮询窗口决定额外延迟
最小可用的消费逻辑是:读取当前时间,把已经到期的任务取出来,然后交给工作线程处理。轮询每 1 秒一次,理论上额外等待时间落在 0 到 1 秒之间;但 Redis 繁忙、批量处理过大或工作线程排队都会继续放大延迟。
ZRANGE delay:orders 0 19 BYSCORE -inf 1766745600000 WITHSCORES
这个命令适合观察和诊断。真正消费时,不能先查询再稍后删除:两个消费者可能读到同一批成员。Redis 5.0 及以上可以用 ZPOPMIN 直接原子取出最低分成员;取出后还要检查它的 score 是否已经到期,避免把未来任务提前交给业务。
轮询间隔和批量大小怎么定
| 参数 | 影响 | 起步建议 |
|---|---|---|
| 轮询间隔 | 越短越及时,也越频繁访问 Redis | 先从 500ms 至 1s 压测 |
| 单轮数量 | 越大吞吐越高,但业务瞬时并发会上升 | 从 20 或 50 开始 |
| 处理超时 | 决定失败后多久进入重试 | 按下游接口 SLA 设置 |
取出任务后崩溃,为什么一定要做幂等
ZPOPMIN 成功返回,只能说明任务从 ZSET 中移除了。假设 worker 刚取出 task:8f31,调用支付服务后还没写入本地成功状态就进程退出,这条任务不会自动回到 ZSET。反过来,如果业务已经成功、确认请求却超时,重试又可能造成重复业务调用。
比较稳妥的做法是给每个任务一个稳定的 task_id,在业务落库时建立唯一约束或幂等结果键:
SET task:result:8f31 success NX EX 86400
只有第一次写入成功的 worker 才继续执行“完成一次”的动作。对于扣库存、发券、发送通知这类操作,最好把幂等键落在业务数据库或下游系统,而不是只依赖 Redis 的短期键。

失败处理:把丢失窗口和重复窗口分开
这套方案有两个不同风险。第一是“取出后不再排队”,需要在任务表里记录 processing 状态,并让超时任务重新进入 ZSET。第二是“业务成功但响应丢失”,需要幂等键或唯一业务号挡住重复执行。两个问题不能只靠增加轮询次数解决。
可以按下面的状态变化实现:
- 任务入库并写入 ZSET,状态为
pending。 - worker 用
ZPOPMIN取出后,把状态改为processing,记录 claim 时间。 - 业务处理成功后写入结果并改为
done。 - 定时补偿扫描超过处理超时仍为
processing的任务,增加重试次数后重新入队。
补偿任务也要带上重试上限和退避时间。超过上限后进入人工可见的 dead-letter 状态,别让它无休止地回到 ZSET。
什么时候应该换成 Streams 或专用消息队列
如果任务只需要“到点触发、允许业务幂等、失败可以补偿”,ZSET 的结构简单、依赖少,很适合提醒、缓存刷新和低峰异步处理。若需要消费者组、消息确认、消费历史、积压可观察性或跨服务可靠投递,Redis Streams 或专用消息队列更合适。
判断标准不是“Redis 能不能做”,而是失败后谁负责找回任务、谁记录处理证据、谁接受重复。把这三个问题写进设计文档,方案通常就不会在上线后才暴露边界。
相关问题
轮询间隔设成 0 会不会更及时?
不会。忙等只会制造 Redis 请求压力,任务处理仍受线程池和下游耗时影响。应按可接受延迟设定间隔,再用批量大小控制吞吐。
ZPOPMIN 能保证业务一定只执行一次吗?
不能。它只保证从 ZSET 取出动作的原子性,业务调用和结果写入仍可能在中途失败,必须额外设计幂等。
同一个 task_id 重新 ZADD 会发生什么?
同名 member 仍是一个成员,新的 score 会覆盖旧排序位置。重试时应记录 attempt 和原因,不要把同一任务悄悄当成新任务。
落地前的检查清单
- 确认 score 单位统一,并用 Redis TIME 或应用时钟策略处理时钟偏差。
- 确认取出、状态更新、业务成功和补偿扫描都有可查日志。
- 确认重复请求不会重复扣款、扣库存或发放权益。
- 确认任务超过重试上限后能被发现,而不是静默消失。
MySQL 事务内批量更新为何越来越慢:索引维护成本与分批提交边界
- 上一篇
- MySQL 事务内批量更新为何越来越慢:索引维护成本与分批提交边界
- 下一篇
- Go pprof goroutine 画像怎么看阻塞点:采样信号、状态分类与复现验证
-
- 数据库 · Redis | 28分钟前 | Redis · 内存管理 · 性能排查 · 碎片率 · 运维验证 · redis 内存回收 INFO memory allocator_frag_ratio 内存碎片率
- Redis INFO memory 怎么看碎片率:allocator_frag_ratio、峰值与回收验证
- 261浏览 收藏
-
- 数据库 · Redis | 4小时前 | Redis · set · 性能边界 · 集合运算 · SINTERCARD · redis limit 集合交集 SINTERCARD 交集数量
- Redis SINTERCARD 如何只取交集数量:LIMIT、键类型与集群边界
- 331浏览 收藏
-
- 数据库 · Redis | 8小时前 | Redis · 缓存 · 性能优化 · 内存管理 · 缓存淘汰 Redis LFU allkeys-lfu lfu-decay-time
- Redis LFU 淘汰为什么不按访问次数排序:衰减周期、热点误判与验收
- 101浏览 收藏
-
- 数据库 · Redis | 10小时前 | Redis · 集群 · PubSub · 消息通信 · redis Redis Cluster SPUBLISH SSUBSCRIBE 分片发布
- Redis SPUBLISH 怎么做分片发布:频道哈希、订阅范围与消息验收
- 119浏览 收藏
-
- 数据库 · Redis | 14小时前 |
- Redis ZMPOP 怎么安全消费排行榜:数量限制、空结果与重试边界
- 330浏览 收藏
-
- 数据库 · Redis | 19小时前 | Redis · 客户端 · 集群 · Redis Cluster · 故障排查 · redis 重定向 Redis Cluster 哈希槽 MOVED ASK
- Redis Cluster MOVED 和 ASK 有什么区别:槽位迁移、客户端重定向与重试边界
- 191浏览 收藏
-
- 前端进阶之JavaScript设计模式
- 设计模式是开发人员在软件开发过程中面临一般问题时的解决方案,代表了最佳的实践。本课程的主打内容包括JS常见设计模式以及具体应用场景,打造一站式知识长龙服务,适合有JS基础的同学学习。
- 543次学习
-
- GO语言核心编程课程
- 本课程采用真实案例,全面具体可落地,从理论到实践,一步一步将GO核心编程技术、编程思想、底层实现融会贯通,使学习者贴近时代脉搏,做IT互联网时代的弄潮儿。
- 516次学习
-
- 简单聊聊mysql8与网络通信
- 如有问题加微信:Le-studyg;在课程中,我们将首先介绍MySQL8的新特性,包括性能优化、安全增强、新数据类型等,帮助学生快速熟悉MySQL8的最新功能。接着,我们将深入解析MySQL的网络通信机制,包括协议、连接管理、数据传输等,让
- 500次学习
-
- JavaScript正则表达式基础与实战
- 在任何一门编程语言中,正则表达式,都是一项重要的知识,它提供了高效的字符串匹配与捕获机制,可以极大的简化程序设计。
- 487次学习
-
- 从零制作响应式网站—Grid布局
- 本系列教程将展示从零制作一个假想的网络科技公司官网,分为导航,轮播,关于我们,成功案例,服务流程,团队介绍,数据部分,公司动态,底部信息等内容区块。网站整体采用CSSGrid布局,支持响应式,有流畅过渡和展现动画。
- 485次学习
-
- ljg-skills
- ljg-skills 是李继刚开源的 AI 技能与提示词集合,面向大模型使用者整理了一批可复用的 prompt、角色设定和任务技能模板,适合用于学习提示词设计、搭建个人 AI 工作流和沉淀团队常用智能体能力。
- 5291次使用
-
- MELO音乐
- MELO音乐是一站式AI视频与音乐制作助手,对标suno, udio的高品质体验。提供伴奏生成、原创写词、无损导出、哼唱识曲、混音变声等全套音频与短视频编辑工具。无论是流行Kpop、电音说唱、民谣古风、摇滚儿歌还是商用轻音乐,MELO为你免费谱曲,轻松做同款!
- 4807次使用
-
- UniScribe
- UniScribe 是一款 AI 音视频转文字与内容整理工具,支持上传音频、视频文件或粘贴 YouTube 链接,自动生成转写文本、摘要、思维导图和关键问题,并支持多格式导出,适合会议记录、课程学习、访谈整理和内容创作复盘。
- 4751次使用
-
- 剧云
- 剧云是专业中文剧本创作平台,安全稳定运行十余年,集成AI编剧、剧本医生审核、人物小传、剧情关系图、大纲编写、多人协作、Word导入导出、版权管控功能,数据安全防护,轻松高效创作剧本。
- 5016次使用
-
- 万象有声
- 万象有声,一个专为有声创作者打造的新一代智能有声内容创作平台。平台提供专业的智能拆章、智能画本编辑、AI配音、AI生成音效、后期制作、智能对轨、智能审听等有声创作全流程工具,可以帮助创作者高效、低成本创作出引人入胜的有声作品。立即体验,让有声书制作更简单!
- 4958次使用
-
- Go与Redis实现分布式互斥锁和红锁
- 2022-12-22 117浏览
-
- go+redis实现消息队列发布与订阅的详细过程
- 2023-01-07 161浏览
-
- Go+Redis实现延迟队列实操
- 2023-02-23 426浏览
-
- 一文搞懂Go语言操作Redis的方法
- 2023-01-07 171浏览
-
- Golang分布式应用之Redis示例详解
- 2023-01-07 113浏览

