深度探索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的定时消息的逻辑图。

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

相关推荐
ok!ko3 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2401_857622663 小时前
SpringBoot框架下校园资料库的构建与优化
spring boot·后端·php
2402_857589364 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰4 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
哎呦没5 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
_.Switch5 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
编程、小哥哥5 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
IT学长编程6 小时前
计算机毕业设计 玩具租赁系统的设计与实现 Java实战项目 附源码+文档+视频讲解
java·spring boot·毕业设计·课程设计·毕业论文·计算机毕业设计选题·玩具租赁系统
莹雨潇潇6 小时前
Docker 快速入门(Ubuntu版)
java·前端·docker·容器
杨哥带你写代码7 小时前
足球青训俱乐部管理:Spring Boot技术驱动
java·spring boot·后端