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

名称解释:
定时消息:可随意选择一个时间点来进行消息定时发送
延时消息:定时消息的子集,只能从提供的延时等级中选择

简介

背景和意义

使用场景:在分布式定时调度触发、任务超时处理等场景,需要实现精准、可靠的定时事件触发。使用 Apache RocketMQ 的定时消息可以简化定时调度任务的开发逻辑,实现高性能、可扩展、高可靠的定时触发能力。(来源-官网介绍
RocketMQ5之前,并没有定时消息这个功能,只有延时消息这个类似定时消息但又不太像定时消息的功能。其中设定的延时时间只能从提供的18个定时级别中选择。
Apache RocketMQ 支持的延时级别
RocketMQ在这个5.0这个大版本中全面拥抱云原生,在此场景下,也是支持了任意延迟时间的延迟消息功能。

定时消息概述

之前定时消息的实现方式

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

  • 定时任务:根据第三方系统来定时的生产消息并触发消息的发送
  • 奇技淫巧的延时消息:在RocketMQ的消息体中携带一个我们自定义的定时时间戳。一开始根据定时时间来选择最相近的延时等级,当消费者消费到这条消息后,判断当前时间与消息实际需要消费时间判断。如果不相等,重新获取相近的延时级别再次发送,一直这样循环下去。直到当前时间≤定时时间才真正的消费。
// 伪代码
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来进行定时消息的发送了。

//定时/延时消息发送
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的定时消息的逻辑图。
converted.jpg
在我的下一篇文章中,我将详细介绍定时消息是怎么在定时时间被投递到消费者处的,敬请期待!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值