延迟消息

使用示例

消费者没什么变化,和普通消费者一样,这里就不贴了

DefaultMQProducer producer = new DefaultMQProducer("ms-producer-group");
producer.setNamesrvAddr("127.0.0.1:9876");
producer.start();
Message message = new Message("orderMsTopic", "我是一个延迟消息".getBytes());
// 给消息设置一个延迟时间,不同的延迟级别表示不同的延迟时间
// messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
message.setDelayTimeLevel(3);
producer.send(message);
System.out.println("发送时间" + new Date());
producer.shutdown();

实现原理

1. 服务初始化阶段

Broker 启动时会初始化 ScheduleMessageService 延迟消息服务,这个服务会创建 18 个定时任务,分别对应 18 种延迟级别

ScheduleMessageService 服务内部创建一个线程池,线程个数默认是 CPU 核心数,由这些线程共同创建 18 个任务,可能一个线程负责多个任务

2. 消息存储阶段

Broker 接收到延迟消息后,先存入一个特殊的 Topic:SCHEDULE_TOPIC_XXXX,这个 Topic 下有 18 个队列(从 0 开始),对应 18 种延迟级别

store/consumequeue/SCHEDULE_TOPIC_XXXX/
   ├── 0/   # 级别1(1s延迟)的队列
   ├── 1/   # 级别2(5s延迟)的队列
   ...
   └── 17/  # 级别18(2h延迟)的队列

SCHEDULE_TOPIC_XXXX 不是目标 Topic 所以,消息此时不会投递给消费者

3. 定时任务执行

定时任务初始化完成后,第一次执行的时间是延迟级别的时间,之后是每秒执行一次,执行流程如下图:

flowchart LR A[任务触发] --> B{检查消息存储时间} B -->|存储时间+延迟≤当前时间| C[转移到目标 Topic] B -->|未到期| D[保持原位] C --> E[Broker 投递消息给消费者] D --> F[等待下次扫描]

如果定时任务扫描后消息还未到期,跳过这条消息,进入下一轮扫描(时间轮)

每次每个任务扫描对应队列的 100 条消息,不会全量扫描

4. 消息投递阶段

到期消息处理

  1. SCHEDULE_TOPIC_XXXX 删除消息,并转移到目标 Topic
  2. 将消息写入 CommitLog 中(是个文件,每个消息都会写入到这个文件,包含每个消息的Topic、Tag、Body等属性)
  3. 为消息构建索引(所有消息都放到这一个文件中,构建索引为了快速定位每条消息)
  4. 这时 Broker 就可以把消息推送给消费者了

注意事项

  1. 误差在秒级(业务需容忍±1秒偏差)
  2. 消息发送后无法修改延迟时间
  3. 大量延迟消息会增加定时任务压力
  4. 延迟消息不能和顺序、事务、批量消息组合
  5. 4.x 只支持级别,不同级别对应不同延迟时间,这个可以改,要到配置文件 conf 里改
  6. 5.x 可以指定时间了(上面的原理是 4.x 的,5.x 的估计要动态创建定时任务)
posted @ 2025-07-03 11:21  CyrusHuang  阅读(11)  评论(0)    收藏  举报