zset实现延迟队列

文章目录

      • [一、 核心设计思想:什么是 ZSet 延迟队列?](#一、 核心设计思想:什么是 ZSet 延迟队列?)
      • [二、 运行流程:存 与 取](#二、 运行流程:存 与 取)
        • [1. 生产者:存入任务(投递消息)](#1. 生产者:存入任务(投递消息))
        • [2. 消费者:取出任务(轮询消费)](#2. 消费者:取出任务(轮询消费))
      • [三、 深度理解:`ZRANGEBYSCORE` 在这里起什么作用?](#三、 深度理解:ZRANGEBYSCORE 在这里起什么作用?)
      • [四、 生产环境必须注意的"隐患"](#四、 生产环境必须注意的“隐患”)

将利用 Redis 的 Sorted Set(ZSet) 实现延迟队列的核心逻辑与 ZRANGEBYSCORE 命令的作用合并,我们可以用"存 "和"取"两个动作来完整还原这个方案。


一、 核心设计思想:什么是 ZSet 延迟队列?

Redis 的 ZSet 是一个有序集合 。它里面的每一条数据都会关联一个分数(Score),Redis 会自动根据分数从小到大给数据排好序。

在延迟队列场景中,我们做了一个巧妙的映射:

  • 数据内容(Member) :代表你要处理的任务(比如:订单 ID order_id_10086)。
  • 分数(Score) :代表这个任务的具体执行时间戳 (即 当前时间戳 + 延迟秒数)。

因为 ZSet 会自动排序,所以最先到期的任务,永远排在队列的最前面。


二、 运行流程:存 与 取

1. 生产者:存入任务(投递消息)

当用户下单后,系统需要开启一个"10秒后未支付自动取消订单"的延迟任务。假设当前时间戳是 1700000000。

生产者计算出执行时间:1700000000 + 10 = 1700000010。

然后使用 ZADD 命令把任务塞进名为 delay_queue 的 ZSet 中:

sql 复制代码
ZADD delay_queue 1700000010 "order_id_10086"
2. 消费者:取出任务(轮询消费)

消费者(后台线程)会像一个定时闹钟一样,每隔 1 秒去 Redis 里巡检一次,看看有没有到期的任务。它使用的核心命令就是 ZRANGEBYSCORE。

假设现在时间走到了 1700000015(距离下单过去了 15 秒),消费者发起查询:

sql 复制代码
ZRANGEBYSCORE delay_queue 0 1700000015 LIMIT 0 1

三、 深度理解:ZRANGEBYSCORE 在这里起什么作用?

这行命令的字面意思是:"去 delay_queue 里,把分数在 0 到 1700000015 之间的任务捞出来,但我只要第 1 条。"

对应到延迟队列的业务逻辑,参数拆解如下:

  • 0(时间下限) :因为时间戳是个递增的正整数,写 0 代表从最早、最老的时间开始算起,确保那些过去已经超时的任务不会被漏掉。
  • 1700000015(时间上限 = 当前时间戳) :这是最关键的限制。限制上限为"当前时间",意味着只有"执行时间 ≤ \le ≤ 当前时间"的任务才符合条件。那些还没到期的任务(比如要求在第 150 秒执行),因为分数大于 125,就会被直接过滤掉。
  • LIMIT 0 1(数量限制) :类似于 SQL 的 LIMIT 1。意思是虽然满足到期条件的可能有很多条,但我一次只取最该执行的那 1 条。这能有效防止高并发下多个消费者同时抢到大量重复任务。

四、 生产环境必须注意的"隐患"

在了解了 ZRANGEBYSCORE 的作用后,你会发现一个问题:它只是把数据"读"了出来,但并没有从 Redis 里"删掉"。

如果两个消费者同时执行了上面那条命令,它们会同时拿到 order_id_10086,这就导致了重复消费。

工业级的解决方案:
  1. Lua 脚本(推荐) :将 ZRANGEBYSCORE(查询)和 ZREM(删除)打包写进一个 Lua 脚本中。因为 Redis 执行 Lua 脚本是原子的,能够保证"谁先查到,谁就立马删掉",别人绝对抢不走。
  2. ZPOPMIN 命令(Redis 5.0+):直接弹出队列中分数最小的元素。可以先通过该命令弹出,并在代码中判断弹出的那个值是否小于当前时间,如果没到期再塞回去(或者配合其他逻辑),从而避免了重复消费。
相关推荐
没有气的汽水19 分钟前
一次生产 Redis 内存打满的排查、清理和复盘
redis
hey you~1 小时前
云呼叫中心坐席自动分配实战:ACD 策略、Redis+Lua 实现与排查清单
数据库·redis·lua·云呼叫中心·acd·坐席分配·坐席状态机
我不会起名字3223 小时前
Redis pipeline 用了反而更慢:3 类误用与正确的批量姿势
redis·pipeline·lua·rtt·批量
Tsuki_tl3 小时前
【总结】Redis五大数据类型应用场景及基本命令
数据库·redis·后端·缓存·缓存中间件·redis命令·redis 五大数据类型
代码丰18 小时前
大 JSON 放进 Redis 之后,如何用 Stream 解耦耗时任务?
redis·后端·系统架构
枫子有风21 小时前
卡码网盘项目深挖
java·redis·jwt·es·pgvector
福大大架构师每日一题21 小时前
redis 8.10.3正式发布:聚焦集群总线超大消息传播场景的安全修复,8.10.3 Latest更新解读
数据库·redis·安全
江屿风1 天前
【Plain Language Large Model】【从麦当劳点餐到Redis接入:MCP如何让AI工具集成变简单】流食般投喂
人工智能·redis·笔记·语言模型·llm·mcp
码智社1 天前
Redis 哨兵模式详解:从原理到生产实践
java·redis
与数据交流的路上1 天前
一次 redis 幽灵内存的分析
数据库·redis·缓存