RocketMQ源码解析——存储部分(7)延迟消息的原理`ScheduleMessageService`

延迟消息描述介绍

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。注意,messageDelayLevelbroker的属性,不属于某个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存入特定的queuequeueId =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文件的代码没有贴出来,这里是第一次存储的关键部分。

  1. 这里会先判断消息的标志位,如果标识位不是事务消息或者事务消息的提交阶段。
  2. 会进一步判断是不是设置了延迟时间。
  3. 如果设置的延迟时间大于最大的延迟时间则把延迟时间设置为最大延迟时间
  4. 把消息的queueId属性修改为PROPERTY_REAL_QUEUE_ID,对应的topic属性设置为PROPERTY_REAL_TOPIC。同时把真正的queueIdtopic保存在property属性中。然后保存到CommitLog。

 在这里可以看到RocketMQ对于延迟消息,第一次的消息存储,会把消息的topicqueueId先修改,然后存放到特定的topic中去进行保存。

第二次消息存储

 RocketMQ中有一个专门处理topicRMQ_SYS_SCHEDULE_TOPIC的服务类ScheduleMessageService。这个类的初始化是在DefaultMessageStore中会在RocketMQ的Broker启动的时候初始化。

初始化延迟文件和配置

ScheduleMessageService在Broker启动的时候会先调用其load方法,加载delayOffset.json文件然后加载对应的延迟级别配置。

    public boolean load() {
   
        //调用父类的加载文件的方法,父类会调用子类实现的configFilePath方法确定文件
        boolean result = super.load();
        
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值