方案对比

方案对比

选延时队列方案时,最容易犯的错误是只问”支不支持延时”。真正决定方案的,是四个维度:原生支持(要不要自己造轮子)、精度(能接受多大的误差)、吞吐(能不能扛住突发)、运维成本(谁来维护这套延时机制)。中间还夹着两个隐性维度:可取消性和顺序性——前者决定”订单付款后能不能撤销取消任务”,后者决定”同一订单的消息是否还有序”。

下表是四种主流消息系统在这四个维度上的横向对比。

维度RabbitMQKafkaRocketMQ
延时支持插件实现需自实现内置支持
延时精度毫秒级秒级毫秒级
最大延时有限制无限制18 天
吞吐量中等极高高
可靠性高高高
复杂度中高中
适用场景中小规模大规模大规模

关于表中的”18 天”

RocketMQ 的”18”指的是 18 个预设的延迟级别,不是 18 天。经典的 18 级是 1s、5s、10s、30s、1m、2m、3m、4m、5m、6m、7m、8m、9m、10m、20m、30m、1h、2h,单级最长 2 小时,超过 2 小时需要 4.x/5.x 的定时消息能力或自行改造。级别是离散的,级别之间靠档位取整,实际触发可能比预期晚一整档。

把 Pulsar 和四个维度一起放进来,是一张更完整的选型表:

维度RabbitMQKafkaRocketMQPulsar
原生支持❌ 依赖 rabbitmq_delayed_message 插件❌ 无原生延时✅ 内置延迟级别(5.x 支持定时消息)⚠️ 以消费端延迟投递为主,无生产端原生延迟级别
延时精度毫秒级(插件按毫秒调度)秒级(取决于轮询间隔)毫秒级到秒级(级别离散,精度受档位影响)毫秒级(消费端 deliverAt 语义)
最大延时受插件实现限制,量级在数十天理论无限制传统级别最长 2 小时,定时消息可覆盖更长周期取决于 broker 缓冲能力
吞吐量中等(万级 TPS)极高(百万级 TPS)高(十万级 TPS)高(多租户、分层存储)
可靠性高(持久化队列 + ACK + 镜像)高(副本 + acks=all)高(主从 + DLedger 等)高(BookKeeper 多副本写入)
运维成本中(插件需匹配版本,升级要重装)高(分区/保留期/消费位点都要自己管)中(内置能力,运维相对简单)高(Broker + BookKeeper 两层,组件多)
可取消性⚠️ 插件队列里的消息无法直接删除❌ 只能靠消费端跳过⚠️ 同上,一般靠业务状态兜底❌ 同上
顺序性单队列内有序分区内严格有序分区内有序KeyShared 模式下有序

关于 Pulsar 的延迟投递

Pulsar 的延时能力主要落在消费端:消费者可以指定”这条消息在某个时间戳之后再投递给我”,由 broker 侧缓冲到期前的消息。这和 RocketMQ 那种”生产端就指定延时级别”不是一回事——语义位置不同,broker 侧的缓冲压力也不同。生产端任意延时通常仍要靠自建延迟 topic 加搬运服务。

除了 broker 自身的能力,延时队列还有三个通用能力必须在选型时问清楚:

通用能力为什么重要各家的现状
到期时刻可控能不能精确到”某个时间点”而不是”再等 N 秒”RocketMQ 5.x 定时消息、Pulsar deliverAt 支持;Kafka 需自建
可撤销订单已支付,取消任务应失效四家都不支持真正的删除,只能靠消费端查状态后跳过
积压可控某个业务突增会不会拖垮其他业务需要分区隔离或队列隔离,四家都要自己设计

最后一条是实践中最容易被忽略的。共用一个 topic 意味着共用一个延迟队列,一个大客户的批量任务会把其他客户的延时消息一起压住。隔离能力比延时精度更重要,因为精度差一点只是晚几秒,隔离没做好是整条业务链路停摆。


选型建议

是否已有 MQ 基础设施?
├─ 是 → 使用已有 MQ
│   ├─ RabbitMQ → 使用延时插件
│   ├─ Kafka → 分区策略实现
│   └─ RocketMQ → 使用内置延时
│
└─ 否 → 专用方案
   ├─ 中小规模 → Redis ZSet
   └─ 大规模 → 时间轮 + 持久化

这张决策树的方向是对的,但它隐含了一个前提:你已经接受”用 MQ 做延时队列”这件事本身。在实际选型时,更前置的问题应该是”这件事到底该不该用 MQ”。

先问三个前置问题。

  1. 延时能不能被业务状态覆盖? 如果订单付款后取消任务就失效,那么”精确到毫秒触发”其实没有价值——真正重要的是”最晚不要超过 N 分钟触发”。这种情况下,数据库轮询加一个合适的索引(见《索引优化》)往往比任何 MQ 都简单可靠。
  2. 任务量到底有多大? 每天几千个任务,Redis 单实例加一个 ZSet 就够了;每天上千万还要求毫秒精度,才值得引入 MQ 的复杂度。
  3. 可取消性重要吗? 重要的话,四家 MQ 都不擅长,任务事实应该落在数据库,中间件只做触发。

再按基础设施选型。

现状推荐理由
已有 RocketMQ直接用内置延迟级别零开发成本,精度够用,注意级别离散
已有 RabbitMQ用 rabbitmq_delayed_message 插件插件成熟,注意版本匹配和最大延时上限
已有 Kafka,且日志吞吐极高分区策略或分级延迟 topic复用现有设施,但要自建搬运或调度逻辑
已有 Pulsar消费端延迟投递消费端语义,注意 broker 缓冲压力
没有 MQ,任务量不大Redis ZSet实现简单,精度够用,见《Redis ZSet 方案》
没有 MQ,需要可靠审计数据库轮询 + 定时任务可查询、可取消、最不容易出错

最后是一条经验: 很多团队一开始用 MQ 做延时,一年后发现真正的瓶颈不是延时能力,而是”任务到底执行了没有”这个可观测性问题。中间件只保证”消息送到了”,不保证”业务做对了”。所以无论选哪家,都要配一套自己的任务状态表和幂等消费逻辑,这部分工作量不会因为选了 MQ 而减少。相关的可靠性设计见《可靠性》。

发现内容有问题?

告诉我们哪里需要改进。