likes
comments
collection
share

RocketMQ是怎样实现延迟消息的?

作者站长头像
站长
· 阅读数 25

延迟消息时MQ中一种特殊的消息,可用于投递延迟任务,本文我们一起来聊一聊Rocket中延迟消息的实现。

1. RocketMQ的存储架构

在聊延迟消息的具体实现之前,我们需要先复习一下在RocketMQ中,一条普通消息的生命周期是怎样的。

  • Productor生产消息。
  • 消息存储到CommitLog。
  • CommitLog中的消息索引投递到对应的Topic队列。
  • Consumer消费对应Topic中的消息。

RocketMQ是怎样实现延迟消息的?

2. 多级延迟队列

2.1. 多级延迟队列介绍

延迟队列是一种定时器的实现方式,简单来说,我们可以借助队列先进先出的方式,将需要延迟发送的消息投递到一个队列中,之后用一个监听器监听队首节点是否到时间,到了的话就进行出队操作。延迟队列的设计可以避免对于每个节点都起一个监听线程。

但是,其问题也是显而易见的,队首节点是否出队是按照是否到定时结束时间判断的,那么,我们就必须保障队列中的节点倒计时是从队首递增排列到队尾的,否则就会有队伍中间的节点已经到了出队时间,但是被前面的节点“卡主”的情况。

多级延迟队列就是为了处理上述问题,我们可以预设多个队列,队列1处理延迟1s发动的消息,队列2处理延迟2s发动的消息...每个延迟级别的消息投递到对应的队列,来避免单个队列中的定时乱序问题。

2.2. Rocket中的多级延迟队列实现

知道了什么是多级延迟队列,那么Rocket中是如果实现它的呢?

我们之前复习了RocketMQ消息消费的流程,消息会写写入CommitLog,之后投递到对应的TopicQueue。RocketMQ的多级延迟队列正式借助这两个结构实现的,对于延迟消息,RocketMQ在首次写入CommitLog时会根据其延迟级别改写消息的ID和Topic,之后,会由对应延迟等级的延迟TopicQueue承接消费。当延迟TopicQueue中的消息满足延迟时间后,会重新写入CommitLog中,之后由正常的TopicQueue进行消费,来达到延迟发送消息的效果。

2.3. 多级延迟队列的优劣

这个设计很巧妙的复用了RocketMQ的现有逻辑,通过其本身架构保障了延迟消息的稳定性。但是,这个实现也有一个很明显的问题,延迟队列的延迟时间是写死的,也就是说,延迟队列只能支持固定时间粒度的延迟消息,并且,随着延迟级别的增加,每个延迟等级都要有承接的TopicQueue与监听器,成本会越来越高。

3. 时间轮

3.1. 什么是时间轮?

时间轮由一个首位相连的环形链表与一个不断移动的指针组成,链表上的每个节点都连接着一个待执行的任务列表,指针移动的速度就代表节点间的时间步长。

当定时任务产生时,其根据延迟时间挂载到对应的节点上,指针移动到对应节点时,执行节点的所有待执行任务。

RocketMQ是怎样实现延迟消息的?

随着时间跨度的增加,单层时间轮的轮询效率会受到限制,文件数量过多难以管理等问题。通过多个时间了不同级别刻度进行映射(类比时钟时分秒刻度盘),以少量空间换时间,有效避免大量空轮询“空推进”情况。

RocketMQ是怎样实现延迟消息的?

3.2. 时间轮在RocketMQ中的实现

RocketMQ通过一个TimerLog日志承接了时间轮中待执行任务列表的统计工作,TimerLog将随机写转变为顺序写,每个延迟任务会计算出自己应该接入的队列,之后通过一个双向指针与TimerLog中的前向节点连在一起。

  • 产生新的定时消息时,根据延迟时长定位到时间轮的对应时间格,完成链表的记录插入。
  • 指针移动到对应出发节点时,将消息投递回CommitLog,进行消费。

RocketMQ是怎样实现延迟消息的?

3.3. 时间轮的优劣

时间轮可以达到比多级延迟队列更细粒度控制消息延迟的效果,但是肉眼可见的,其实现也比延迟队列复杂了很多。TimerLog日志的引入也会带来日志维护,压缩等一系列的问题。另外,虽然TimerLog的写入虽然是顺序的,但是其读取是随机的,大量消息的堆积可能会导致性能问题。

转载自:https://juejin.cn/post/7394279685969018915
评论
请登录