首页 > Spring > 当前页面

RocketMQ延迟消息是怎么实现的?如果让你设计一个延迟消息系统你会怎么做?

2026-06-09 NEW个对象

🚀 RocketMQ延迟消息是怎么实现的?如果让你设计一个延迟消息系统你会怎么做?

📌 核心结论:
RocketMQ的延迟消息本质上并不是把消息直接存储到未来时间队列,而是采用“时间轮思想 + 多级延迟队列 + 定时任务扫描”的设计。发送延迟消息时先进入特殊Topic,Broker后台线程定时扫描到期消息并重新投递到真实Topic。其核心目标是避免海量定时器导致内存爆炸,同时保证高吞吐、高可靠和可扩展能力。

1️⃣ 问题背景

延迟消息是消息队列系统中非常常见的能力。

例如:

  • 订单30分钟未支付自动取消
  • 外卖超时自动退款
  • 优惠券7天后过期
  • 直播预约提醒
  • 短信定时发送
  • 会员到期通知
  • 物流超时提醒

假设电商平台每天产生1000万订单。

每个订单都需要:

30分钟后检查支付状态

如果直接创建1000万个Java定时任务:

⚠️ JVM会出现大量TimerTask、线程调度和内存消耗问题,系统无法支撑。

因此消息中间件必须提供高性能延迟消息能力。

2️⃣ 核心原理

很多开发者认为延迟消息实现方式是:

消息发送

启动定时器

到时间触发

投递消费者

实际上RocketMQ并不是这样实现。

因为:

  • 消息量可能达到亿级
  • 定时器数量无法控制
  • Broker重启后定时器丢失
  • 维护成本极高

RocketMQ采用:

发送消息

进入延迟Topic

存储CommitLog

定时扫描线程

到期重新投递

进入真实Topic

消费者消费
✅ RocketMQ延迟消息本质是“二次投递”,而不是“定时执行”。

3️⃣ 数据结构分析

真实Topic

OrderTopic

正常订单消息存放位置。

延迟Topic

SCHEDULE_TOPIC_XXXX

RocketMQ内部保留Topic。

所有延迟消息先写入这里。

延迟级别映射

1s
5s
10s
30s
1m
2m
5m
10m
30m
1h
2h

每个延迟级别对应一个Queue。

例如:

DelayLevel 1 → Queue1
DelayLevel 2 → Queue2
DelayLevel 3 → Queue3

这样避免所有延迟消息堆积在一个队列中。

消息存储结构

真实Topic
真实QueueId
投递时间
消息体

延迟消息只是临时修改Topic和Queue信息。

4️⃣ 算法分析

发送阶段

发送:

setDelayTimeLevel(14)

Broker收到消息:

保存真实Topic

替换Topic

写入SCHEDULE_TOPIC_XXXX

扫描阶段

Broker启动后台线程:

ScheduleMessageService

每秒扫描一次。

核心逻辑:

获取消息

判断是否到期

未到期继续等待

已到期重新投递

时间复杂度:

O(N)

但由于按延迟等级分桶,因此扫描成本可控。

5️⃣ 执行流程

RocketMQ延迟消息完整流程

生产者发送消息

指定延迟等级

Broker修改Topic

进入SCHEDULE_TOPIC_XXXX

写入CommitLog

后台线程扫描

判断是否到期

恢复真实Topic

重新投递

消费者消费

为什么性能高?

  • 没有海量Timer
  • 没有大量线程
  • 利用顺序写CommitLog
  • 利用时间分桶降低扫描范围

6️⃣ 实际案例

订单超时取消

用户下单:

创建订单

发送30分钟延迟消息

消息内容:

orderId=10001

30分钟后:

消息到期

重新进入OrderTimeoutTopic

消费者检查订单状态

未支付则取消订单

延迟消息数量估算

假设:

  • 每天订单1000万
  • 延迟30分钟
  • 平均消息1KB

存储:

约10GB消息数据

RocketMQ完全可以支撑。

7️⃣ 如果让我设计延迟消息系统

第一阶段:简单版

MySQL

execute_time索引

定时任务扫描

适合百万级数据。

第二阶段:Redis ZSet方案

score = executeTime
member = msgId

定时扫描:

ZRANGEBYSCORE

获取到期消息

发送MQ

适合千万级场景。

第三阶段:时间轮方案

消息进入时间轮

秒槽位

分钟槽位

小时槽位

触发投递

类似Kafka Timer Wheel。

时间复杂度:

O(1)

适合亿级消息系统。

第四阶段:最终架构

Producer

Delay Topic

Time Wheel

Schedule Service

Real Topic

Consumer

同时增加:

  • 多副本存储
  • 失败重试
  • 死信队列
  • 消息追踪
  • 幂等控制

8️⃣ 面试常见问题

RocketMQ为什么不用定时器?

因为海量定时器会导致内存和线程资源消耗巨大,不适合分布式消息系统。

延迟消息为什么要二次投递?

这样可以复用现有消息存储和消费体系,避免设计全新的调度架构。

RocketMQ延迟消息有什么缺陷?

  • 早期版本仅支持固定延迟等级
  • 不是绝对精确时间
  • 依赖Broker扫描线程

Redis ZSet和时间轮哪个好?

千万级以内ZSet简单高效;亿级以上时间轮更节省资源。

如果你设计延迟消息系统怎么做?

采用时间轮加MQ架构,消息先进入延迟存储层,通过时间轮管理触发时间,到期后重新投递真实Topic,同时结合多副本、重试机制和幂等控制保证可靠性与扩展性。

9️⃣ 总结

✅ RocketMQ延迟消息本质是延迟存储加二次投递。

✅ 消息首先进入SCHEDULE_TOPIC_XXXX。

✅ Broker后台线程定期扫描到期消息。

✅ 到期后恢复真实Topic并重新发送。

✅ RocketMQ避免了海量Timer带来的资源消耗问题。

✅ 如果自研延迟消息系统,推荐演进路线:

MySQL定时扫描

Redis ZSet

多级时间轮

MQ二次投递

分布式延迟消息平台

这也是当前大型互联网公司在订单超时、支付超时、定时通知、营销触达等场景下最主流的延迟消息设计方案。

相关文章

  • 接口优化的几种方法

    使用那种方法进行接口优化,取决于不同的业务场景,常见的优化方法:

    NEW个对象 2024-10-22

  • Nacos与Eureka如何选择?

    1、Nacos支持AP和CP两种模式,Eureka仅支持AP,如果对数据一致性要求比较高,选择nacos。 2、Nacos支持多语言,比如:java、python、go等,Eureka仅支持java 3、Nacos是Spring alibaba的组件,Eureka是Netflix的组件。 4、Nacos 不仅提供服务注册与发现,还提供配置管理、动态 DNS 服务等。

    NEW个对象 2024-12-30

  • 认证授权:OAuth2简介及四种授权模型详解

    认证授权:OAuth2简介及四种授权模型详解

    NEW个对象 2026-06-11

NEW个对象 NEW个对象
JAVA是世界上最好的语言