Rocket MQ 延迟队列浅析

背景

使用延迟队列的场景非常多,支付超时关闭、用户签约超时、拼团失败取消等,延迟 15分钟、30分钟 ...

实现延迟处理的手段很多:

  1. 定时任务扫描,到时间就处理
  2. 利用 Redis 队列,不断投递检查,到时间就处理
  3. JDK 的延迟队列
  4. Rocket MQ的延迟队列
  5. Rabbit MQ的延迟队列
  6. ...

工具无好坏,选择最适合自己场景的一款即可 ~

本文主要讲解 RocketMQ 的延迟队列,主要有以下优点:

  1. 高吞吐量
  2. 低延迟
  3. 使用人群范围广

RocketMQ 延迟队列

RocketMQ 是阿里巴巴开源的一款分布式消息中间件,具有高性能、高可靠性、分布式等特性,受众群体广。

特性

1、高吞吐

得益于 RocketMQ 队列设计的高吞吐能力,通过高效的存储和网络传输机制,能够支持每秒数百万条消息的吞吐量。

2、低延迟

RocketMQ 的消息传输延迟通常在毫秒级别,能够满足对实时性要求较高的应用场景。

每个延迟级别的扫描时间间隔是固定的,通常为 1 秒。这意味着 RocketMQ 的定时任务会每秒钟扫描一次所有的延迟队列,检查是否有消息的延迟时间已经到达。如果有消息的延迟时间到达,这些消息会被重新投递到目标队列中。

3、可靠性

RocketMQ 提供了多种机制来保证消息的可靠传输和存储。

  • 消息持久化:RocketMQ 支持将消息持久化到磁盘,确保在系统故障时消息不会丢失。
  • 消息重试:RocketMQ 支持消息重试机制,当消息消费失败时,可以自动进行重试,确保消息最终被成功处理。
  • 消息确认:RocketMQ 支持消息确认机制,消费者在成功处理消息后需要发送确认,确保消息不会被重复消费。

4、设计简单

多个 Level 队列,定时任务扫描每个 Level 数据,到时间则投递至目标队列进行等待消费。

基本使用

生产者:

java 复制代码
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;

public class DelayProducer {
    public static void main(String[] args) throws Exception {
        // 创建生产者实例
        DefaultMQProducer producer = new DefaultMQProducer("delay_producer_group");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        // 创建消息
        Message msg = new Message("DelayTopic", "Hello RocketMQ".getBytes());

        // 设置延迟级别,延迟10秒
        msg.setDelayTimeLevel(3);

        // 发送消息
        SendResult sendResult = producer.send(msg);
        System.out.printf("%s%n", sendResult);

        // 关闭生产者
        producer.shutdown();
    }
}

消费者:

java 复制代码
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class DelayConsumer {
    public static void main(String[] args) throws Exception {
        // 创建消费者实例
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer_group");
        consumer.setNamesrvAddr("localhost:9876");
        consumer.subscribe("DelayTopic", "*");

        // 注册消息监听器
        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> msgs, ConsumeConcurrentlyContext context) {
                for (MessageExt msg : msgs) {
                    System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msg.getBody()));
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });

        // 启动消费者
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}

原理

RocketMQ 实现原理简单:支持 18 个级别的 Level,最多 2 小时;每个 Level 都对应一个队列,定时任务直接扫描每个延迟级别的队列,并在延迟时间到达时将消息投递到目标队列。

1、延迟级别

RocketMQ 通过预定义的延迟级别来实现延迟队列。每个延迟级别对应一个固定的延迟时间。默认情况下,RocketMQ 提供了 18 个延迟级别,延迟时间从 1 秒到 2 小时不等。

lua 复制代码
生产者发送消息 -> 延迟队列(根据延迟级别存储消息)

+----------------+       +----------------+       +----------------+
| 生产者          |       | 延迟队列       |       | 目标队列       |
|                |       | Level 1: 1s    |       |                |
| +------------+ |       | Level 2: 5s    |       | +------------+ |
| | 发送消息   |  | ----> | Level 3: 10s   | ----> | | 消费者     | |
| +------------+ |       | ...            |       | +------------+ |
+----------------+       +----------------+       +----------------+

2、消息存储

当生产者发送一条延迟消息时,消息会被存储在一个特殊的队列中,这个队列根据消息的延迟级别进行划分。每个延迟级别都有一个对应的队列。

3、定时扫描

RocketMQ 的 Broker 端有一个定时任务,用于扫描这些延迟队列。当定时任务发现某个延迟队列中的消息已经到达指定的延迟时间时,会将这些消息重新投递到目标队列中。

lua 复制代码
定时任务扫描延迟队列 -> 检查消息延迟时间 -> 重新投递到目标队列

+----------------+       +----------------+       +----------------+
| 定时任务        |       | 延迟队列       |       | 目标队列        |
| +------------+ |       | Level 1: 1s    |       |                |
| | 扫描队列   |  | ----> | Level 2: 5s    | ----> | +------------+ |
| +------------+ |       | Level 3: 10s   |       | | 消费者     |  |
|                |       | ...            |       | +------------+ |
+----------------+       +----------------+       +----------------+

4、消息重新投递

当延迟时间到达时,消息会被重新投递到目标队列中,消费者可以从目标队列中消费这些消息。

lua 复制代码
消息重新投递到目标队列 -> 消费者消费消息

+----------------+       +----------------+       +----------------+
| 定时任务        |       | 延迟队列       |       | 目标队列        |
|                |       | Level 1: 1s    |       |                |
|                |       | Level 2: 5s    |       | +------------+ |
|                |       | Level 3: 10s   | ----> | | 消费者     | |
|                |       | ...            |       | +------------+ |
+----------------+       +----------------+       +----------------+
相关推荐
2401_882727571 小时前
低代码配置式组态软件-BY组态
前端·后端·物联网·低代码·前端框架
追逐时光者2 小时前
.NET 在 Visual Studio 中的高效编程技巧集
后端·.net·visual studio
大梦百万秋3 小时前
Spring Boot实战:构建一个简单的RESTful API
spring boot·后端·restful
斌斌_____3 小时前
Spring Boot 配置文件的加载顺序
java·spring boot·后端
路在脚下@3 小时前
Spring如何处理循环依赖
java·后端·spring
海绵波波1074 小时前
flask后端开发(1):第一个Flask项目
后端·python·flask
小奏技术5 小时前
RocketMQ结合源码告诉你消息量大为啥不需要手动压缩消息
后端·消息队列
AI人H哥会Java7 小时前
【Spring】控制反转(IoC)与依赖注入(DI)—IoC容器在系统中的位置
java·开发语言·spring boot·后端·spring
凡人的AI工具箱7 小时前
每天40分玩转Django:Django表单集
开发语言·数据库·后端·python·缓存·django
奔跑草-7 小时前
【数据库】SQL应该如何针对数据倾斜问题进行优化
数据库·后端·sql·ubuntu