定时/延时消息在RocketMQ 4.x到RocketMQ 5.0的演变:从固定延时等级到精准延时时间

原因

在消息中间件的实际应用中,延时消息是保障业务时序性的关键能力,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 延时消息流程以 "自由时间戳" 为核心,无需依赖等级映射,具体如下:​

  1. 写入 CommitLog 并标记延时

Producer 通过 setDeliveryTimeMs(timestamp) 直接指定消息的绝对投递时间(如 System.currentTimeMillis () + 3500 表示延时 3.5 秒)。Broker 收到消息后:​

  • 先写入 CommitLog 完成持久化;
  • 读取消息属性中的 deliveryTimeMs,标记为 "延时消息"。
  1. 分发至 Topic 专属延时队列​

Broker 不再将所有延时消息汇总到 SCHEDULE_TOPIC_XXXX,而是为每个原 Topic 创建专属的延时队列(特殊 ConsumeQueue)。消息根据 deliveryTimeMs 按时间顺序排序存入对应延时队列,实现 "先到先投" 的基础调度逻辑。​

  1. PreciseDelayScheduler 精准调度

Broker 后台引入全新调度器 PreciseDelayScheduler,基于多层时间轮(类似时钟的时针、分针、秒针)实现高效调度:​

  • 底层时间轮处理毫秒级短期任务,高层处理分钟级、小时级长期任务;
  • 消息按投递时间划入对应 "时间槽",到期后自动降级到下层时间轮,直至触发调度;
  • 相比 4.x 单等级 TimerTask,时间轮支持百万级消息的毫秒级调度,并发能力大幅提升。
  1. 恢复原 Topic 并分发消费

当消息到达投递时间,调度器触发如下操作:​

  • 从延时队列中取出消息,恢复其原 Topic、Tag 等元数据;
  • 将消息重新写入 CommitLog(生成新的偏移量);
  • 最终分发到原 Topic 的目标 ConsumeQueue,消费者即可正常拉取消费。

总结

  • 灵活:支持任意毫秒级延时,无需修改 Broker 配置即可适配多样化业务场景(如秒杀订单 1.8 秒倒计时、定时任务精确到 100ms 触发);
  • 高性能:Topic 专属延时队列避免全局拥堵,多层时间轮调度降低并发冲突;
  • 兼容性:保留对 4.x 延时等级的支持,老系统升级无需修改代码;
  • 逻辑解耦:投递时间通过消息属性存储,不再占用 Tag 字段,不影响原有业务逻辑。

相关推荐
syty20201 天前
kafka vs rocketmq
分布式·kafka·rocketmq
泊浮目1 天前
AutoMQ代码里的那些设计
大数据·消息队列
菠菠萝宝1 天前
【Java八股文】13-中间件面试篇
java·docker·kafka·rabbitmq·canal·rocketmq·es
承悦赋2 天前
微服务通信:5大消息队列横向对比
微服务·架构·kafka·rabbitmq·rocketmq
鼠鼠我捏,要死了捏2 天前
RabbitMQ死信队列与幂等性处理的性能优化实践指南
性能优化·消息队列·rabbitmq
李广坤2 天前
常用消息队列选型
消息队列
在未来等你3 天前
Kafka面试精讲 Day 20:集群监控与性能评估
大数据·分布式·面试·kafka·消息队列
云闲不收3 天前
RocketMQ基础以及和 Kafka 有什么区别
分布式·kafka·rocketmq
励志成为糕手3 天前
Kafka事务:构建可靠的分布式消息处理系统
分布式·kafka·消息队列·linq·数据一致性