原因
在消息中间件的实际应用中,延时消息是保障业务时序性的关键能力,RocketMQ 作为主流消息中间件,其 4.x 版本的延时消息机制已服务多年,但固定延时等级的设计逐渐难以满足灵活的业务需求。
RocketMQ 5.0 针对性地重构了延时消息架构,实现了从 "固定等级" 到 "任意精度" 的跨越。本文将通过对比 4.x 与 5.0 的处理流程,拆解这一重要演进。
RocketMQ 4.x的延时消息
Producer将消息发送到Broker后,Broker会首先将消息写入到commitlog文件,然后需要将其分发到相应的consumequeue。
不过,在分发之前,系统会先判断消息中是否带有延时等级。若没有,则直接正常分发;若有则需要经历一个复杂的过程:
修改延时消息
- 修改消息的Topic为SCHEDULE_TOPIC_XXXX
- 根据延时等级,在consumequeue目录中SCHEDULE_TOPIC_XXXX主题下创建出相应的queueId 目录与consumequeue文件(如果没有这些目录与文件的话)。
- 修改消息索引单元内容。索引单元中的Message Tag HashCode部分原本存放的是消息的Tag的 Hash值。现修改为消息的投递时间。
- 投递时间 = 消息存储时间 + 延时等级时间。
- 将消息索引写入到SCHEDULE_TOPIC_XXXX主题下相应的consumequeue中
投递时间是指该消息被重新修改为原Topic后再次被写入到 commitlog中的时间。
消息存储时间指的是消息 被发送到Broker时的时间戳。
投递延时消息
- Broker内部有⼀个延迟消息服务类,其会按照消息的投递时间消费SCHEDULE_TOPIC_XXXX中的消息
ScheuleMessageService在Broker启动时,会创建并启动一个定时器Timer,用于执行相应的定时任务。系统会根据延时等级的个数,定义相应数量的TimerTask,每个TimerTask负责一个延迟等级消息的消费与投递。
- 从commitlog 中将原来写入的消息再次读出,并将其原来的延时等级设置为0,即原消息变为了一条不延迟的普通消息。然后再次将消息投递到目标Topic中。
局限
- 延时精度受限于固定等级(如默认等级仅支持 1s、5s、10s 等,无法自定义 1.5s 或 300ms);
- 所有延时消息共享 SCHEDULE_TOPIC_XXXX,高并发下易出现队列拥堵;
- 占用 Tag HashCode 字段,可能影响基于 Tag 的过滤逻辑。
RocketMQ 5.0 的延时消息
5.0 版本打破固定等级束缚,引入 "精确延时" 设计
优化维度 | 4.x 实现 | 5.0 实现 |
延时设置方式 | 固定延时等级(delayLevel) | 任意时间戳(deliveryTimeMs) |
临时存储载体 | 全局共享 SCHEDULE_TOPIC_XXXX | Topic 专属延时队列 |
投递时间存储 | 占用 Tag HashCode 字段 | 消息属性单独存储 |
调度精度 | 秒级(依赖等级) | 毫秒级(时间轮调度) |
流程
5.0 延时消息流程以 "自由时间戳" 为核心,无需依赖等级映射,具体如下:
- 写入 CommitLog 并标记延时
Producer 通过 setDeliveryTimeMs(timestamp) 直接指定消息的绝对投递时间(如 System.currentTimeMillis () + 3500 表示延时 3.5 秒)。Broker 收到消息后:
- 先写入 CommitLog 完成持久化;
- 读取消息属性中的 deliveryTimeMs,标记为 "延时消息"。
- 分发至 Topic 专属延时队列
Broker 不再将所有延时消息汇总到 SCHEDULE_TOPIC_XXXX,而是为每个原 Topic 创建专属的延时队列(特殊 ConsumeQueue)。消息根据 deliveryTimeMs 按时间顺序排序存入对应延时队列,实现 "先到先投" 的基础调度逻辑。
- PreciseDelayScheduler 精准调度
Broker 后台引入全新调度器 PreciseDelayScheduler,基于多层时间轮(类似时钟的时针、分针、秒针)实现高效调度:
- 底层时间轮处理毫秒级短期任务,高层处理分钟级、小时级长期任务;
- 消息按投递时间划入对应 "时间槽",到期后自动降级到下层时间轮,直至触发调度;
- 相比 4.x 单等级 TimerTask,时间轮支持百万级消息的毫秒级调度,并发能力大幅提升。
- 恢复原 Topic 并分发消费
当消息到达投递时间,调度器触发如下操作:
- 从延时队列中取出消息,恢复其原 Topic、Tag 等元数据;
- 将消息重新写入 CommitLog(生成新的偏移量);
- 最终分发到原 Topic 的目标 ConsumeQueue,消费者即可正常拉取消费。
总结
- 灵活:支持任意毫秒级延时,无需修改 Broker 配置即可适配多样化业务场景(如秒杀订单 1.8 秒倒计时、定时任务精确到 100ms 触发);
- 高性能:Topic 专属延时队列避免全局拥堵,多层时间轮调度降低并发冲突;
- 兼容性:保留对 4.x 延时等级的支持,老系统升级无需修改代码;
- 逻辑解耦:投递时间通过消息属性存储,不再占用 Tag 字段,不影响原有业务逻辑。