深度探索RocketMQ5:定时消息新特性解析【上】

名称解释:

定时消息:可随意选择一个时间点来进行消息定时发送

延时消息:定时消息的子集,只能从提供的延时等级中选择

简介

背景和意义

使用场景:在分布式定时调度触发、任务超时处理等场景,需要实现精准、可靠的定时事件触发。使用 Apache RocketMQ 的定时消息可以简化定时调度任务的开发逻辑,实现高性能、可扩展、高可靠的定时触发能力。(来源-官网介绍

RocketMQ5之前,并没有定时消息这个功能,只有延时消息这个类似定时消息但又不太像定时消息的功能。其中设定的延时时间只能从提供的18个定时级别中选择。

RocketMQ在这个5.0这个大版本中全面拥抱云原生,在此场景下,也是支持了任意延迟时间的延迟消息功能。

定时消息概述

之前定时消息的实现方式

在RocketMQ5之前,还没提供定时消息的特性之前,我们想要实现定时消息只能借助定时任务 来触发或者借用延时消息的同时使用一些奇技淫巧来间接的完成定时消息。

  • 定时任务:根据第三方系统来定时的生产消息并触发消息的发送
  • 奇技淫巧的延时消息:在RocketMQ的消息体中携带一个我们自定义的定时时间戳。一开始根据定时时间来选择最相近的延时等级,当消费者消费到这条消息后,判断当前时间与消息实际需要消费时间判断。如果不相等,重新获取相近的延时级别再次发送,一直这样循环下去。直到当前时间≤定时时间才真正的消费。
java 复制代码
// 伪代码
void consumerMessage(Message message) {
    long delayTimeStamp = message.getProperties("delayTimeStamp");
    long currTimeStamp = System.currentTimeMillis();
    if (delayTimeStamp <= currTimeStamp) {
        // 消费消息
        return;
    }

    // 重新发送消息
    long delayTime = delayTimeStamp - currTimeStamp;
    // 根据相差时间选择一个最接近的延时级别
    Integer delayLevel = getDelayLevel(delayTime);
    message.setDelayTimeLevel(delayLevel);
    producer.send(message);
}

这样做的好处在于不需要借助第三方工具如定时任务来触发消息,避免系统复杂度过高。但是也会牵扯到另外一个问题,那就是代码逻辑比较复杂,在消费到消息时不能直接消费,需要先判断是否真的需要消费。

定时消息使用方式

在RocketMQ5中增加定时消息功能后,我们就可以很简单的使用API来进行定时消息的发送了。

java 复制代码
//定时/延时消息发送
MessageBuilder messageBuilder = new MessageBuilderImpl();;
//以下示例表示:延迟时间为10分钟之后的Unix时间戳。
Long deliverTimeStamp = System.currentTimeMillis() + 10L * 60 * 1000;
Message message = messageBuilder.setTopic("topic")
        //设置消息索引键,可根据关键字精确查找某条消息。
        .setKeys("messageKey")
        //设置消息Tag,用于消费端根据指定Tag过滤消息。
        .setTag("messageTag")
        .setDeliveryTimestamp(deliverTimeStamp)
        //消息体
        .setBody("messageBody".getBytes())
        .build();
try {
    //发送消息,需要关注发送结果,并捕获失败等异常。
    SendReceipt sendReceipt = producer.send(message);
    System.out.println(sendReceipt.getMessageId());
} catch (ClientException e) {
    e.printStackTrace();
}

定时消息的改进和优势

在RocketMQ5发布时,社区实现了可自定义时间的延时消息功能,此时之前的延时消息就能真正变为定时消息了,不过在RocketMQ中,定时消息与延时消息的逻辑并非整合在一起 ,而是针对定时消息新写了一套逻辑。

优势:

  • 精度高、开发门槛低:基于消息通知方式不存在定时阶梯间隔。可以轻松实现任意精度事件触发,无需业务去重。
  • 高性能可扩展:传统的数据库扫描方式较为复杂,需要频繁调用接口扫描,容易产生性能瓶颈。 Apache RocketMQ 的定时消息具有高并发和水平扩展的能力。
  • RocketMQ自带功能,开箱即用,无需过多配置。

但是有一个特别的缺点,目前仅支持一天内的定时消息,超过一天的消息将会立即发送。

实现原理

基本概念和架构

在RocketMQ的github仓库中,我们可以找到这个特性的MR:[RIP-43]Support Timing Messages with Arbitrary Time Delay。在这个MR中,有一份google文档,从中我们可以看到这个功能的设计思路。

有两个新的数据结构在这次改动中被引入,用于实现定时消息触发逻辑。

  • TimerLog:存放着定时消息在commitLog中的位置以及大小等数据。
  • TimerWheel:对于每个时间粒度(槽,默认1s),存放着定时时间 、当前时刻需要处理的TimerLog首条位置 以及最后位置
官方文档中的时间轮的行进图

实现定时消息这个功能主要用到了时间轮的数据结构,时间轮和commitLog一样,都是存储在系统的本地文件 当中。在时间轮中,每个槽中都包含着TimerLog中的对应指针。当时间到达后,先从时间轮中获取TimerLog最后一条数据,从后往前 获取全部数据后再进行处理。

在本次RIP中,除了上述的两个数据结构之外,还牵涉到三条队列五个处理器

  • 三条队列:enqueuePutQueue、dequeueGetQueue、dequeuePutQueue
  • 五个处理器:TimeEnqueueGetService、TimerEnqueuePutService、TimerDequeueGetService、TimerDequeueGetMessageService、TimerDequeuePutMessageService

在研究并理解了定时消息的整个实现逻辑后,我重新绘制了RocketMQ的定时消息的逻辑图。

在我的下一篇文章中,我将详细介绍定时消息是怎么在定时时间被投递到消费者处的,敬请期待!

相关推荐
chuanauc25 分钟前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴41 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao1 小时前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc7871 小时前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
srrsheng2 小时前
RocketMQ面试题
rocketmq
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野7 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法