RocketMQ延迟消息实现原理(下)

上一篇文章:rocketmq延迟消息实现原理(上)分析了rocketmq4.x版本中支持的默认级别的延迟消息的实现原理,默认的延迟级别无法满足用户对任意秒级时间的诉求,rocketmq5版本中支持了定时任意秒级时间的,所以本文一起来看支持任意秒级时间的定时消息的实现原理。

发送消息到服务端完整流程

发送定时消息

 发送定时或延迟消息的代码示例,如下代码块所示:

// 实例化一个生产者来产生消息
DefaultMQProducer producer = new DefaultMQProducer("TimerMessageProducerGroup");
 // 设置NameServer的地址
producer.setNamesrvAddr("localhost:9876");
// 启动生产者
producer.start();
Message message = new Message("TimerTopic", ("Hello timer message ").getBytes()); // 设置定时时间为10s
// 设置自定义 【延迟10s】
message.setDeliverTimeMs(System.currentTimeMillis() + 10_000L);
 // 发送消息 
 producer.send(message);  
 // 关闭生产者
 producer.shutdown()

发送定时消息是通过对要发送的消息调用setDelayTimeSec这个方法去设置的。Message类中提供下面这三个方法都可以实现消息的定时或延迟。


public void setDelayTimeSec(long sec) {
        this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_SEC, String.valueOf(sec));
}
public void setDelayTimeMs(long timeMs) {
    this.putProperty(MessageConst.PROPERTY_TIMER_DELAY_MS, String.valueOf(timeMs));
}
public void setDeliverTimeMs(long timeMs) {
        this.putProperty(MessageConst.PROPERTY_TIMER_DELIVER_MS, String.valueOf(timeMs));
  }

存储预处理定时消息

消息发送到broker后,执行putMessage之前,先是调用了3个hook方法进行预处理,针对定时或延迟消息的预处理逻辑,是在HookUtils#handleScheduleMessage进行执行的,主要是做了2件事:

1、合法性与流控校验;

2、把真实的消息的topic修改设置为默认的定时消息topic:rmq_sys_wheel_timer,queueId为固定的0,原来真实的消息topic与queueId先放到消息的扩展属性中。

其他的步骤处理逻辑与普通消息没有区别。

时间轮算法

涉及两个核心的数据结构:TimerWheel(时间轮,org.apache.rocketmq.store.timer.TimerWheel用于定时消息到时)与TimerLog(org.apache.rocketmq.store.timer.TimerLog存储消息索引)

TimerLog,设计的定时消息的记录文件,Append Only。每条记录包含一个prev_pos,指向前一条定时到同样时刻的记录。每条记录的内容可以包含定时消息本身,也可以只包含定时消息的位置信息。

TimerWheel,是对时刻表的一种抽象,通常使用数组实现。时刻表上的每一秒,顺序对应到数组中的位置,然后数组循环使用。时间轮的每一格(org.apache.rocketmq.store.timer.Slot),指向了TimerLog中的对应位置,如果这一格的时间到了,则按TimerLog中的对应位置以及prev_pos位置依次读出每条消息。

时间轮一格一格向前推进,配合TimerLog,依次读出到期的消息,从而达到定时消息的目的。

org.apache.rocketmq.store.timer.Slot设计成固定32个字节的,包含的属性及长度如下所示。

public class Slot {
    public static final short SIZE = 32;
    //delayed time
    public final long timeMs; 
    // 首个消息位置
    public final long firstPos;
    // 最后一个消息位置
    public final long lastPos;
    // 消息数量
    public final int num;
    public final int magic; 
}

定时消息处理逻辑

基于上面的算法与Timerlog、TimerWheel的数据结构设计,具体实现定时消息的处理的逻辑都在org.apache.rocketmq.store.timer.TimerMessageStore类中,主要涉及到下面5个线程的执行。

private TimerEnqueueGetService enqueueGetService;
private TimerEnqueuePutService enqueuePutService;
private TimerDequeueGetService dequeueGetService;
private TimerDequeueGetMessageService[] dequeueGetMessageServices;
private TimerDequeuePutMessageService[] dequeuePutMessageServices;

这几个线程配合执行的流程图如下所示:

从图中可以看出,共有五个Service线程分别处理定时消息的放置和存储。工作流如下:

1、针对放置定时消息的service,每100ms从commitLog读取指定主题(TIMER_TOPIC)的定时消息。

  1. TimerEnqueueGetService线程启动后,不停的从topic为rmq_sys_wheel_timer,队列id为0的队列拉取消息,并先将其放入本地的enqueuePutQueue中,如果拉取不到,则每隔100ms后继续此操作。
  2. 另一个线程TimerEnqueuePutService不停的从enqueuePutQueue拉取放入的定时消息,将其以append的方式放入timerLog中,更新timeWheel时间轮的存储内容。将该任务放进时间轮的指定位置。
     

2、针对取出定时消息的service,每50ms读取下一秒的Slot。有三个线程将读取到的消息重新放回commitLog。

  1. 首先,TimerDequeueGetService线程启动后,不停地从timerWheel中获取下一秒的Slot,从timerLog中得到指定的msgs,并放进dequeueGetQueue,如果获取不到,则等待100ms继续;
  2. 而后TimerDequeueGetMessageService线程,从dequeueGetQueue中取出msg,并将其放入队列待写入CommitLog的队列dequeuePutQueue中;
  3. 最后TimerDequeuePutMessageService线程启动后,不停地从这个dequeuePutQueue中取出消息,放回commitlog,若定时时间已到期则修改topic为真实的topic(这时消息可以被消费),否则继续按原topic(rmq_sys_wheel_timer)写回CommitLog滚动。

感兴趣可以关注公众号 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值