延迟消息描述介绍
RocketMQ的定时消息(延迟队列)是指消息发送到broker后,不会立即被消费,等待特定时间投递给真正的topic。broker有配置项messageDelayLevel
,默认值为1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
,18个level。可以配置自定义messageDelayLevel
。注意,messageDelayLevel
是broker
的属性,不属于某个topic。发消息时,设置delayLevel等级即可:msg.setDelayLevel(level)。level有以下三种情况:
- level 为 0,消息为非延迟消息
- 1<=level<=maxLevel,消息延迟特定时间,例如level==1,延迟1s
- level > maxLevel,则level== maxLevel,例如level==20,延迟2h
定时消息会暂存在名为SCHEDULE_TOPIC_XXXX
的topic中,并根据delayTimeLevel
存入特定的queue
,queueId
=delayTimeLevel – 1
,即一个queue
只存相同延迟的消息,保证具有相同发送延迟的消息能够顺序消费。broker会调度地消费SCHEDULE_TOPIC_XXXX
,将消息写入真实的topic。
需要注意的是,定时消息会在第一次写入和调度写入真实topic时都会计数,因此发送数量、tps都会变高。
源码分析
第一次存储消息
第一次存储延迟消息是在CommitLog的putMessage方法中进行的,关于这部分代码分析可以看看前面的分析CommitLog文件的文章。这里不重复分析,只截取部分的代码片段出来。
public PutMessageResult putMessage(final MessageExtBrokerInner msg) {
......
//如果不是事务消息 或者 是事务消息的提交阶段
if (tranType == MessageSysFlag.TRANSACTION_NOT_TYPE
|| tranType == MessageSysFlag.TRANSACTION_COMMIT_TYPE) {
// 如果设置了延迟时间
if (msg.getDelayTimeLevel() > 0) {
//延迟级别不能超过最大的延迟级别,超过也设置为最大的延迟级别
if (msg.getDelayTimeLevel() > this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel()) {
msg.setDelayTimeLevel(this.defaultMessageStore.getScheduleMessageService().getMaxDelayLevel());
}
//设置延迟消息的topic
topic = ScheduleMessageService.SCHEDULE_TOPIC;
//延迟消息的queueId= 延迟级别-1
queueId = ScheduleMessageService.delayLevel2QueueId(msg.getDelayTimeLevel());
// Backup real topic, queueId 备份真正的topic和queueId
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_TOPIC, msg.getTopic());
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_REAL_QUEUE_ID, String.valueOf(msg.getQueueId()));
msg.setPropertiesString(MessageDecoder.messageProperties2String(msg.getProperties()));
msg.setTopic(topic);
msg.setQueueId(queueId);
}
}
......
}
后续的把消息保存到CommitLog文件的代码没有贴出来,这里是第一次存储的关键部分。
- 这里会先判断消息的标志位,如果标识位不是事务消息或者事务消息的提交阶段。
- 会进一步判断是不是设置了延迟时间。
- 如果设置的延迟时间大于最大的延迟时间则把延迟时间设置为最大延迟时间
- 把消息的
queueId
属性修改为PROPERTY_REAL_QUEUE_ID
,对应的topic
属性设置为PROPERTY_REAL_TOPIC
。同时把真正的queueId
和topic
保存在property属性中。然后保存到CommitLog。
在这里可以看到RocketMQ对于延迟消息,第一次的消息存储,会把消息的topic
和queueId
先修改,然后存放到特定的topic中去进行保存。
第二次消息存储
RocketMQ中有一个专门处理topic
为RMQ_SYS_SCHEDULE_TOPIC
的服务类ScheduleMessageService
。这个类的初始化是在DefaultMessageStore
中会在RocketMQ的Broker启动的时候初始化。
初始化延迟文件和配置
ScheduleMessageService
在Broker启动的时候会先调用其load
方法,加载delayOffset.json
文件然后加载对应的延迟级别配置。
public boolean load() {
//调用父类的加载文件的方法,父类会调用子类实现的configFilePath方法确定文件
boolean result = super.load();