文章目录

消息队列 RocketMQ:(一)概述

消息队列 RocketMQ:(二)系统架构

消息队列 RocketMQ:(三)发送普通消息(三种方式)

消息队列 RocketMQ:(四)顺序消息


前提

创建一个 Maven 的 Java 工程。
引入 RocketMQ 的 Client 依赖,版本需要和服务端一致。

<dependency>
	    <groupId>org.apache.rocketmq</groupId>
	    <artifactId>rocketmq-client</artifactId>
	    <version>4.9.0</version>
	</dependency>

三、延时消息

当消息写入到 Broker 后,在指定的时长后才可被消费处理的消息,称为延时消息。采用 RocketMQ 的延时消息可以实现 定时任务 的功能,而无需使用定时器。

延时消息适用于以下场景:

  • 消息生产和消费有时间窗口要求,例如在电商交易中超时未支付关闭订单的场景,在订单创建时会发送一条延时消息。这条消息将会在30分钟以后投递给消费者,消费者收到此消息后需要判断对应的订单是否已完成支付。如支付未完成,则关闭订单。如已完成支付则忽略。

1、延时等级

延时消息的延迟时长不支持随意时长的延迟,是通过特定的延迟等级来指定的。延时等级定义在 RocketMQ 服务端的 MessageStoreConfig 类中的如下变量中:

@ImportantField
private FlushDiskType flushDiskType = FlushDiskType.ASYNC_FLUSH;
private int syncFlushTimeout = 1000 * 5;
private String messageDelayLevel = "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h";
private long flushDelayOffsetInterval = 1000 * 10;

即,若指定的延时等级为3,则表示延迟时长为10s,即延迟等级是从1开始计数的。

如果需要自定义的延时等级,可以通过在 broker 加载的配置中新增如下配置(例如下面增加了1天这个等级1d)。配置文件在 RocketMQ 安装目录下的conf目录中。

messageDelayLevel = 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h 1d

2、延时消息实现

kafka延时队列DelayedProduce kafka 延时消息队列_java

修改消息

Producer 将消息发送到 Broker 后,Broker 会首先将消息写入到 commitlog 文件,然后需要将其分发到相应的 consumequeue。在分发之前,系统会先判断消息中是否带有延时等级。若没有,则直接正常分发;若有则需要经历以下的过程:

  • 修改消息的 Topic 为 SCHEDULE_TOPIC_XXXX
  • 根据延时等级,在 consumequeue 目录中 SCHEDULE_TOPIC_XXXX 主题下创建出相应的 queueId 目录与 consumequeue 文件
  • 修改消息索引单元内容
  • 将消息索引写入到 SCHEDULE_TOPIC_XXXX 主题下相应的 consumequeue 中

投递延时消息

Broker 内部有⼀个延迟消息服务类 ScheuleMessageService,其会消费 SCHEDULE_TOPIC_XXXX 中的消息,即按照每条消息的投递时间,将延时消息投递到⽬标 Topic 中。在投递之前会从 commitlog 中将原来写入的消息再次读出,并将其原来的延时等级设置为0,即原消息变为了一条不延迟的普通消息。然后再次将消息投递到目标 Topic 中。

消息重新写入

延迟消息服务类 ScheuleMessageService 将延迟消息再次发送给了 commitlog,并再次形成新的消息索引条目,分发到相应 Queue。这相当一次普通消息发送。只不过这次的消息 Producer 是延迟消息服务类 ScheuleMessageService

3、发送延时消息

import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.remoting.common.RemotingHelper;

import java.text.SimpleDateFormat;
import java.util.Date;

public class DelayProducer {

    public static void main(String[] args) throws Exception {
        DefaultMQProducer producer = new DefaultMQProducer("pg");
        producer.setNamesrvAddr("localhost:9876");
        producer.start();

        for (int i = 0; i < 1; i++) {
            byte[] body = ("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET);
            Message msg = new Message("LearnTopic", "DonTag-Delay", body);
            // 指定消息延迟等级为3级,即延迟10s
            msg.setDelayTimeLevel(3);
            SendResult sendResult = producer.send(msg);
            // 输出消息被发送的时间
            System.out.println(new SimpleDateFormat("mm:ss").format(new Date()));
            System.out.println(sendResult);
        }
        producer.shutdown();
    }

}

4、订阅延时消息

延时消息的订阅与普通消息订阅一致:

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.client.exception.MQClientException;
import org.apache.rocketmq.common.consumer.ConsumeFromWhere;
import org.apache.rocketmq.common.message.MessageExt;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

public class DelayConsumer {

    public static void main(String[] args) throws MQClientException {
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("cg");
        consumer.setNamesrvAddr("localhost:9876");

        consumer.setConsumeFromWhere(ConsumeFromWhere.CONSUME_FROM_FIRST_OFFSET);
        consumer.subscribe("LearnTopic", "*");

        consumer.registerMessageListener(new MessageListenerConcurrently() {
            @Override
            public ConsumeConcurrentlyStatus consumeMessage(List<MessageExt> list,
                                                            ConsumeConcurrentlyContext consumeConcurrentlyContext) {
                for (MessageExt msg : list) {
                    // 输出消息被消费的时间
                    System.out.println(new SimpleDateFormat("mm:ss").format(new Date()));
                    System.out.println(msg);
                }
                return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
            }
        });
        consumer.start();
        System.out.println("Consumer Started");
    }

}

5、运行测试

kafka延时队列DelayedProduce kafka 延时消息队列_java_02


kafka延时队列DelayedProduce kafka 延时消息队列_队列_03


可以看到 10s 后消费


Schedule example