RocketMQ 的延迟消息实现依赖于延迟级别(Delay Level)的设计。每个延迟级别对应一个固定的延迟时间。消息被发送到 Broker 时,可以指定一个延迟级别,Broker 会在相应的延迟时间后将消息投递给消费者。
延迟消息的实现原理
-
延迟级别(Delay Level):
- RocketMQ 定义了 18 个延迟级别,每个级别对应一个固定的延迟时间。这些级别和对应的延迟时间可以在
broker.conf配置文件中进行配置。 - 默认延迟级别和对应的时间如下:
"1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h"
- RocketMQ 定义了 18 个延迟级别,每个级别对应一个固定的延迟时间。这些级别和对应的延迟时间可以在
-
消息的存储和处理:
- 当生产者发送延迟消息时,指定延迟级别,消息被存储在 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 实现延迟消息的关键在于以下几点:
- 延迟级别(Delay Level):通过延迟级别定义了不同的延迟时间。
- 消息存储和处理:延迟消息存储在 CommitLog 中,Broker 通过定时任务扫描和处理延迟消息。
- 定时任务(ScheduledMessageService):Broker 内部的定时任务服务,根据延迟级别计算应投递的时间,到达时间后重新将消息投递到原始主题队列。
- 消费者处理:消费者不需要做特别处理,延迟消息到达原始主题队列后,会像普通消息一样被消费。
通过这种设计,RocketMQ 实现了灵活的延迟消息机制,可以满足多种业务场景下的延迟消息需求。