RocketMQ 的延迟消息,18 个 Delay Level

936 阅读3分钟

RocketMQ 的延迟消息实现依赖于延迟级别(Delay Level)的设计。每个延迟级别对应一个固定的延迟时间。消息被发送到 Broker 时,可以指定一个延迟级别,Broker 会在相应的延迟时间后将消息投递给消费者。

延迟消息的实现原理

  1. 延迟级别(Delay Level)

    • RocketMQ 定义了 18 个延迟级别,每个级别对应一个固定的延迟时间。这些级别和对应的延迟时间可以在 broker.conf 配置文件中进行配置。
    • 默认延迟级别和对应的时间如下:
      "1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
      
  2. 消息的存储和处理

    • 当生产者发送延迟消息时,指定延迟级别,消息被存储在 CommitLog 中,并记录消息的延迟级别。
    • Broker 有一个专门的定时任务(ScheduledMessageService),用于定时扫描这些延迟消息,并根据延迟级别将消息重新投递到相应的队列

延迟消息的发送和处理流程

1. 发送延迟消息

生产者在发送消息时,可以通过设置消息的延迟级别来指定消息的延迟投递时间。

import org.apache.rocketmq.client.exception.MQBrokerException;
import org.apache.rocketmq.client.exception.MQClientException;
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.exception.RemotingException;

public class DelayMessageProducer {
    public static void main(String[] args) throws MQClientException, RemotingException, MQBrokerException, InterruptedException {
        // 创建一个生产者实例
        DefaultMQProducer producer = new DefaultMQProducer("DelayMessageProducerGroup");
        producer.setNamesrvAddr("127.0.0.1:9876");
        producer.start();

        // 创建一个消息实例,指定Topic、Tag和消息体
        Message message = new Message("DelayTopic", "TagA", "Hello RocketMQ, delayed message!".getBytes());

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

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

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

2. 定时任务处理延迟消息

Broker 内部有一个 ScheduledMessageService 服务,用于处理延迟消息。

  • 存储延迟消息:延迟消息首先被存储在 CommitLog 中,并记录消息的延迟级别。
  • 定时扫描ScheduledMessageService 定时扫描这些延迟消息,根据消息的延迟级别计算应投递的时间。
  • 重新投递:当到达指定的延迟时间后,Broker 会将消息重新放回到原始的主题队列中,供消费者消费。
public class ScheduledMessageService extends ServiceThread {
    // 延迟级别对应的延迟时间
    private final long[] delayLevelTable = new long[18];
    // 定时扫描处理延迟消息
    @Override
    public void run() {
        while (!this.isStopped()) {
            try {
                this.timer.schedule(new DeliverDelayedMessageTimerTask(this.delayLevelTable[level]), delayTime);
            } catch (Exception e) {
                log.error("ScheduledMessageService, execute DeliverDelayedMessageTimerTask error", e);
            }
        }
    }

    class DeliverDelayedMessageTimerTask extends TimerTask {
        private final long delayTime;

        public DeliverDelayedMessageTimerTask(long delayTime) {
            this.delayTime = delayTime;
        }

        @Override
        public void run() {
            // 扫描并重新投递延迟消息
            this.deliverDelayedMessage();
        }

        private void deliverDelayedMessage() {
            // 逻辑处理,根据延迟时间将消息重新投递到原始主题队列
        }
    }
}

3. 消费延迟消息

消费者不需要做特别的处理,延迟消息到达原始主题队列后,会像普通消息一样被消费。

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.message.MessageExt;

import java.util.List;

public class DelayMessageConsumer {
    public static void main(String[] args) throws MQClientException {
        // 创建一个消费者实例
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("DelayMessageConsumerGroup");
        consumer.setNamesrvAddr("127.0.0.1:9876");
        consumer.subscribe("DelayTopic", "*");

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

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

总结

RocketMQ 实现延迟消息的关键在于以下几点:

  1. 延迟级别(Delay Level):通过延迟级别定义了不同的延迟时间。
  2. 消息存储和处理:延迟消息存储在 CommitLog 中,Broker 通过定时任务扫描和处理延迟消息。
  3. 定时任务(ScheduledMessageService):Broker 内部的定时任务服务,根据延迟级别计算应投递的时间,到达时间后重新将消息投递到原始主题队列。
  4. 消费者处理:消费者不需要做特别处理,延迟消息到达原始主题队列后,会像普通消息一样被消费。

通过这种设计,RocketMQ 实现了灵活的延迟消息机制,可以满足多种业务场景下的延迟消息需求。