[TOC] #### 1. 前言 --- 上两讲我们用 `TTL + DLX` 实现了订单 30 分钟超时取消,但在实际开发中,你可能会遇到更复杂的需求: + 会议开始前 15 分钟提醒 + 消息撤回后 2 分钟内可恢复 + 优惠券到期前 1 小时推送通知 这些场景的共同点是:在未来某个时间点执行任务,这就是「延迟队列」要解决的问题 这些需求看起来不一样,但本质上都一样:不是马上执行,而是在未来某个时间点执行,这类需求统称为 “延迟任务” 而 “延迟队列”,就是专门用来管理这些延迟任务的 #### 2. 实现方案 --- ##### 方案一:TTL + DLX(上一讲的方案) RabbitMQ 本身并没有一个叫「延迟队列」的独立组件 我们通常是利用 RabbitMQ 的其它能力来组合实现,最常见的就是 TTL + DLX 这是最经典的方案,也是面试标准答案 优点: + 纯 RabbitMQ 实现,不需要额外组件 + 原理简单,容易理解 + 消息不会丢失(持久化 + DLX) 缺点: + 只能设置固定时长(30分钟),不能指定具体时间(明天下午3点) + 每个延迟时间需要一个单独的队列 + TTL 是队列级别或消息级别,不够灵活 什么场景够用 ? 订单超时、验证码过期这种「固定时长」的延迟,TTL + DLX 完全够用 ##### 方案二:延迟消息插件 RabbitMQ 有一个官方插件:rabbitmq_delayed_message_exchange 安装之后,你可以直接在 Exchange 层面设置延迟: ```php // 声明延迟类型的 Exchange $args = new AMQPTable([ 'x-delayed-type' => 'direct', ]); $channel->exchange_declare('delayed_exchange', 'x-delayed-message', false, true, false, false, false, $args); // 发消息时指定延迟时间 $message = new AMQPMessage(json_encode($data), [ 'headers' => [ 'x-delay' => 1800000, // 延迟30分钟 ], ]); $channel->basic_publish($message, 'delayed_exchange', 'order.created'); ``` 优点: + 比 TTL + DLX 更灵活 + 一条消息一个延迟时间,不需要建很多队列 + 支持修改延迟时间 缺点: + 需要额外安装插件 + 集群环境下每个节点都要装 + 插件方案需要额外关注持久化、高可用和集群配置 ##### 方案三:Redis 延迟队列 如果你的项目里已经有 Redis,可以用 Redis 的有序集合(Sorted Set)实现延迟队列 思路是:把消息放进 Sorted Set,score 设为执行时间戳,后台定时任务每隔几秒查一次,取出到期的消息处理 ```php // 放入延迟任务 $redis->zAdd('delay_queue', time() + 1800, json_encode($data)); // 定时任务:取出到期的任务 $tasks = $redis->zRangeByScore('delay_queue', 0, time()); foreach ($tasks as $task) { // 处理任务 processTask($task); $redis->zRem('delay_queue', $task); } ``` 优点: + 不依赖 RabbitMQ + 支持精确到秒的延迟 + 灵活,可以按时间范围查询 缺点: + 需要自己实现定时任务 + 不是专业的消息队列,没有 ACK、重试等机制 + Redis 重启后数据可能丢失(除非开启持久化) 方案对比: | 方案 | 适用场景 | 优点 | 缺点 | | ------------ | ------------ | ------------ | ------------ | | TTL + DLX | 固定时长延迟 | 纯 RabbitMQ,稳定可靠 | 不够灵活 | | 延迟插件 | 灵活延迟 | 使用简单 | 需要装插件 | | Redis | 已有 Redis,简单场景 | 灵活,精确 | 需要自己维护 | 实际项目中的选择建议: + 项目里已经有 RabbitMQ:先用 TTL + DLX,够用就行 + 需要更灵活的延迟:装延迟插件 + 项目里已经有 Redis 且场景简单:用 Redis 也行 + 不要为了一个超时取消的功能,同时引入 RabbitMQ 和 Redis #### 3. 本文小结 --- 延迟队列的核心需求是:在未来某个时间点执行任务 三种实现方案各有优劣,选最适合你项目的那个 对于大部分业务系统来说,订单超时取消、优惠券过期、预约提醒等场景,TTL + DLX 已经足够 不要为了简单需求引入过于复杂的方案 下一讲,我们来聊一个更根本的问题:RabbitMQ 发出去的消息,一定不会丢吗 ?