首页/数据/Redis 进阶速查/ZSet 延时队列:分数存执行时间
数据结构与选型42 条

ZSet 延时队列:分数存执行时间

把任务的执行时间戳作为 ZSet 分数,轮询 ZRANGEBYSCORE 取出到期任务,用 Lua 保证取出加删除原子,避免多消费者重复领取。

Redis 进阶速查数据更新于 2026-10-13

ZSet 延时队列是什么

延时队列要解决的是「某件事过一段时间再执行」:订单超时关闭、优惠券到期提醒、失败任务延后重试。用 ZSet 实现的方式很直接,把任务的执行时间戳当作分数,任务标识当作成员,写入即投递;消费端用 ZRANGEBYSCORE 取出分数小于当前时间的成员,也就是所有已到期的任务。取出与删除必须在同一个 Lua 脚本内完成,否则多消费者会重复领到同一条任务。

基本信息

成员任务标识,如订单号或任务 ID,必须唯一
分数到期时间的 Unix 时间戳,秒或毫秒要全系统统一
投递ZADD delay <执行时间戳> <任务ID>
领取ZRANGEBYSCORE delay -inf <当前时间> LIMIT 0 10 后立即 ZREM
原子性取与删必须放在一段 Lua 脚本里,单条命令无法保证

典型使用场景

  • 订单超时未支付自动关闭:下单时写入到期时间,到点后被消费端取出执行关单
  • 失败任务退避重试:按指数退避算出下次执行时间,重新 ZADD 回队列
  • 定时提醒与通知:消息本身不带调度能力,靠延时队列把发送时间前置
  • 本地消息表的兜底触发:业务落库后投递延时任务,到点校验是否已完成
  • 限流后的延迟放行:被限流的请求延后若干毫秒重新排队

投递与领取

投递与领取
redis-cli ZADD delay 1730020000 task:1001      # 到点时间戳作为分数
redis-cli ZADD delay 1730023600 task:1002
redis-cli ZRANGEBYSCORE delay -inf 1730021000 LIMIT 0 10   # 取出已到期任务
redis-cli ZREM delay task:1001                 # 领取后移除(必须与上一步原子)
redis-cli ZCARD delay                          # 队列积压量,用于监控
redis-cli ZRANGEBYSCORE delay -inf +inf LIMIT 0 1 WITHSCORES   # 看最早的任务到点时间

Lua 原子领取

Lua 原子领取
local jobs = redis.call('ZRANGEBYSCORE', KEYS[1], '-inf', ARGV[1], 'LIMIT', 0, 10)
for i, id in ipairs(jobs) do
  redis.call('ZREM', KEYS[1], id)
end
return jobs

可靠性的取舍

顺序风险缓解手段
先删除后执行进程崩溃导致任务永久丢失用待确认集合或补偿扫描兜底
先执行后删除崩溃或超时导致任务被重复执行业务逻辑必须幂等
Lua 内取删已取出但尚未执行时进程崩溃配合失败重投表与告警
单 key 集中任务量大时成为热点按业务维度拆成多个 delay key
轮询间隔决定实际精度:到期任务只会在下一次扫描时才被领取,扫描间隔 1 秒就意味着最坏 1 秒的额外延迟;间隔过密则空转请求增多。多数业务按秒级精度设计,用固定间隔扫描比给每个任务建定时器更简单可靠。
时间戳单位必须全系统统一:秒与毫秒混用会让任务在错误的时间点被领取,可能提前数十年执行。推荐统一使用毫秒并把单位写进接口约定与代码注释,避免跨团队协作时出错。

易错点

  • 用先 ZRANGEBYSCORE 再 ZREM 的两条命令实现领取,多消费者并发时会同时取到同一任务,必须用 Lua 或单脚本封装
  • 任务执行与删除之间进程崩溃导致丢失,队列里没有痕迹也没有告警,需要把已领取任务落到待确认结构或对账表中
  • 不监控 ZCARD 与最早到期时间:任务堆积或时间戳写错时无人察觉,往往等到业务侧反馈「该关闭的订单没关」才暴露
  • 所有业务共用一条 delay key,热点与积压互相影响,单个大 key 的迁移也会拖慢整个集群,应按业务再哈希分片

数据来源与参考

  • Redis 官方文档:ZRANGEBYSCORE / EVAL 命令
  • 《Redis 设计与实现》:延时队列思路
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌