mq不支持任意的时间京都,如果要支持,不可避免的需要在Broker层做消息排序,加上持久化方面的考量,将不可避免地带来巨大的性能消耗,所以rocketMQ只支持特定级别的延迟消息。
在Broker短通过messageDelayLevel配置。实现类:org.apache.rocketmq.store.schedule.ScheduleMessageService
public class ScheduleMessageService extends ConfigManager { private static final InternalLogger log = InternalLoggerFactory.getLogger(LoggerName.STORE_LOGGER_NAME); public static final String SCHEDULE_TOPIC = "SCHEDULE_TOPIC_XXXX";//定时消息统一主题 private static final long FIRST_DELAY_TIME = 1000L;//第一次调度时延迟的时间,默认1s private static final long DELAY_FOR_A_WHILE = 100L;//每一延时级别调度一次后延迟该时间间隔后再放入调度池。 private static final long DELAY_FOR_A_PERIOD = 10000L;//发送一场后延迟该时间后再继续参与调度 private final ConcurrentMap/* level */, Long/* delay timeMillis */> delayLevelTable = new ConcurrentHashMap (32);//延迟级别与时间 private final ConcurrentMap /* level */, Long/* offset */> offsetTable = new ConcurrentHashMap (32);//延迟级别消息消费进度 private final DefaultMessageStore defaultMessageStore;//默认消息存储器 private final AtomicBoolean started = new AtomicBoolean(false); private Timer timer; private MessageStore writeMessageStore; private int maxDelayLevel;//MessageStoreConfig#messageDelayLevel中最大消息延迟级别
org.apache.rocketmq.store.DefaultMessageStore#load:
延迟消息消费队列消息进度加载+delayLevelTable数据构造。延迟队列消息消费进度默认存储路径为${ROCKET_HOME}/store/config/delayoffset
ScheduleMessageService#start:
每一个delaylevel-1对应一个消息队列,对应一个调度任务:
org.apache.rocketmq.store.schedule.ScheduleMessageService.DeliverDelayedMessageTimerTask:
class DeliverDelayedMessageTimerTask extends TimerTask { private final int delayLevel; private final long offset; public DeliverDelayedMessageTimerTask(int delayLevel, long offset) { this.delayLevel = delayLevel; this.offset = offset; } @Override public void run() { try { if (isStarted()) { this.executeOnTimeup(); } } catch (Exception e) { // XXX: warn and notify me log.error("ScheduleMessageService, executeOnTimeup exception", e); ScheduleMessageService.this.timer.schedule(new DeliverDelayedMessageTimerTask( this.delayLevel, this.offset), DELAY_FOR_A_PERIOD); } }
根据队列ID与延迟主题 查找消息消费队列,如果未找到,说明目前并不存在该延时级别的消息,每100毫秒检查一次:
----------------
根据offset从消息消费队列中获取当前队列中所有有效消息。如果没找到,检查offset合法性之后100ms后重试:
----------------------------
遍历ConsumeQueue,解析出消息的物理偏移量,消息长度,消息tag hashcode
---------------
检查是否到执行时间,未到的话,延迟到正确时间执行:
到的话:
根据消息物理偏移量与消息大小从commitlog文件中查找消息。如果未找到则跳过这条消息
根据消息重新构建新的消息对象,清楚消息的延迟级别属性,并恢复消息原先的消息主题与消息消费队列,消息的消费次数reconsumeTime并不会丢失。将消息再次存入到commitlog。并转发到对应的消息队列上,供消费者再次消费。
遍历结束后,100ms后开始下一次并更新消费进度。