📖 开场:外卖小哥的预约配送
想象你点了一份外卖 🍔:
普通外卖(立即送达):
下单 → 商家制作 → 立即配送 → 送达
时间:30分钟
预约外卖(延迟送达):
下单 → 预约2小时后送达 ⏰
↓
等待2小时...
↓
商家制作 → 配送 → 准时送达!✅
这就是延迟消息(Delayed Message)!
定义:消息不立即投递,而是在指定时间后才能被消费
🎯 延迟消息的应用场景
场景1:订单超时自动取消 🛒
用户下单 → 发送延迟消息(30分钟后)
↓
30分钟内用户支付 → 取消延迟消息 ✅
↓
30分钟后用户未支付 → 延迟消息到达 → 自动取消订单 ❌
不使用延迟消息的做法:
方案1:定时任务每分钟扫描数据库
- 缺点:延迟不准确,数据库压力大 💀
方案2:Timer或ScheduledExecutorService
- 缺点:应用重启丢失,无法持久化 💀
方案3:Quartz定时任务
- 缺点:配置复杂,资源消耗大 💀
使用延迟消息:
发送延迟消息 → MQ自动延迟 → 到期自动投递 ✅
- 优点:准确、高效、分布式 ⭐⭐⭐⭐⭐
场景2:定时任务调度 📅
发送定时消息:明天上午10点发送营销短信
↓
MQ延迟到明天10点
↓
准时投递 → 消费者发送短信 ✅
场景3:重试机制 🔄
处理失败 → 发送延迟消息(5秒后重试)
↓
5秒后消息到达 → 重新处理
↓
处理成功 ✅
场景4:限流削峰 🌊
瞬时流量 10000 QPS → 转为延迟消息,延迟1-10秒
↓
流量被平滑分散到10秒内 → 1000 QPS ✅
🔧 延迟消息的实现方案
方案1:RocketMQ延迟消息 ⭐⭐⭐⭐⭐
1.1 固定延迟级别
RocketMQ支持18个延迟级别:
级别 延迟时间
1 1秒
2 5秒
3 10秒
4 30秒
5 1分钟
6 2分钟
7 3分钟
8 4分钟
9 5分钟
10 6分钟
11 7分钟
12 8分钟
13 9分钟
14 10分钟
15 20分钟
16 30分钟
17 1小时
18 2小时
配置文件:
# RocketMQ Broker配置
messageDelayLevel=1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
1.2 发送延迟消息
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.common.message.Message;
public class DelayMessageProducer {
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(
"OrderTopic",
"TagA",
"订单ID-12345,30分钟后检查支付状态".getBytes()
);
// ⭐ 设置延迟级别:5 = 1分钟后投递
msg.setDelayTimeLevel(5);
// 记录发送时间
long sendTime = System.currentTimeMillis();
System.out.println("发送时间: " + new Date(sendTime));
// 发送消息
SendResult result = producer.send(msg);
System.out.println("发送结果: " + result);
producer.shutdown();
}
}
运行结果:
发送时间: 2025-10-24 10:00:00
发送结果: SendResult [sendStatus=SEND_OK, msgId=...]
1.3 消费延迟消息
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.*;
import org.apache.rocketmq.common.message.MessageExt;
import java.util.List;
public class DelayMessageConsumer {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("delay_consumer_group");
consumer.setNamesrvAddr("localhost:9876");
consumer.subscribe("OrderTopic", "*");
consumer.registerMessageListener(new MessageListenerConcurrently() {
@Override
public ConsumeConcurrentlyStatus consumeMessage(
List<MessageExt> msgs,
ConsumeConcurrentlyContext context
) {
for (MessageExt msg : msgs) {
// 记录消费时间
long consumeTime = System.currentTimeMillis();
long storeTime = msg.getStoreTimestamp();
long delay = consumeTime - storeTime;
System.out.printf("消费时间: %s%n", new Date(consumeTime));
System.out.printf("延迟时长: %d 秒%n", delay / 1000);
System.out.printf("消息内容: %s%n", new String(msg.getBody()));
// 业务处理:检查订单支付状态
checkOrderPayment(new String(msg.getBody()));
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
}
});
consumer.start();
System.out.println("延迟消息消费者启动成功!");
}
private static void checkOrderPayment(String orderInfo) {
System.out.println("检查订单支付状态: " + orderInfo);
// 查询数据库,如果未支付则取消订单
}
}
运行结果:
消费时间: 2025-10-24 10:01:00
延迟时长: 60 秒 ← 正好1分钟!
消息内容: 订单ID-12345,30分钟后检查支付状态
检查订单支付状态: 订单ID-12345,30分钟后检查支付状态
1.4 RocketMQ延迟消息原理
核心思想:使用多个内部Topic存储不同延迟级别的消息
┌─────────────────────────────────────────────────┐
│ Producer │
│ 发送延迟消息(delayLevel=5,1分钟) │
└─────────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ Broker接收消息 │
│ │
│ 1. 原始Topic: OrderTopic │
│ 2. 延迟级别: 5 (1分钟) │
│ │
│ ⭐ 修改Topic和Queue: │
│ 原Topic: OrderTopic │
│ → 修改为: SCHEDULE_TOPIC_XXXX │
│ 原Queue: 0 │
│ → 修改为: 4 (delayLevel - 1) │
│ │
│ 3. 存储到延迟Topic │
└─────────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ SCHEDULE_TOPIC_XXXX (内部Topic) │
│ │
│ Queue-0: 延迟1秒的消息 │
│ Queue-1: 延迟5秒的消息 │
│ Queue-2: 延迟10秒的消息 │
│ Queue-3: 延迟30秒的消息 │
│ Queue-4: 延迟1分钟的消息 ← 我们的消息在这里! │
│ ... │
│ Queue-17: 延迟2小时的消息 │
│ │
└─────────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ 定时任务(每秒扫描) │
│ │
│ for (每个延迟级别的Queue) { │
│ // 取出到期的消息 │
│ if (消息到期时间 <= 当前时间) { │
│ // ⭐ 恢复原始Topic和Queue │
│ msg.setTopic("OrderTopic"); │
│ msg.setQueueId(0); │
│ // 重新投递到原Topic │
│ broker.putMessage(msg); │
│ } │
│ } │
│ │
└─────────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ OrderTopic (原始Topic) │
│ │
│ 消息恢复到原Topic,Consumer可以消费了! │
│ │
└─────────────────┬───────────────────────────────┘
│
↓
┌─────────────────────────────────────────────────┐
│ Consumer消费消息 │
└─────────────────────────────────────────────────┘
流程总结:
1. Producer发送延迟消息(delayLevel=5)
2. Broker接收,修改Topic为SCHEDULE_TOPIC_XXXX,修改Queue为4
3. 存储到延迟Queue-4
4. 定时任务每秒扫描Queue-4,找到到期的消息
5. 恢复原始Topic和Queue
6. 重新投递到OrderTopic
7. Consumer从OrderTopic消费消息
1.5 优缺点
优点:
- ✅ 实现简单,只需设置delayLevel
- ✅ 性能高,基于时间轮算法
- ✅ 可靠性高,持久化到磁盘
缺点:
- ❌ 只支持固定的18个延迟级别(不能自定义任意延迟时间)
- ❌ 不支持取消延迟消息
- ❌ 延迟时间最长只有2小时
适用场景:
- 延迟时间是固定的几个级别
- 大部分业务场景 ⭐⭐⭐⭐⭐
方案2:RabbitMQ延迟消息 ⭐⭐⭐⭐
2.1 基于TTL + 死信队列
原理:
1. 消息发送到延迟队列(设置TTL)
2. TTL到期,消息变成死信
3. 死信转发到死信交换机
4. 死信交换机路由到目标队列
5. Consumer从目标队列消费
架构图:
Producer
↓
┌─────────────────────────────────────────┐
│ 延迟交换机 (Delay Exchange) │
└─────────────┬───────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ 延迟队列 (Delay Queue) │
│ - TTL: 60000ms (1分钟) │
│ - 不绑定Consumer ⭐ │
│ - 死信交换机: Target Exchange │
└─────────────┬───────────────────────────┘
│ 消息过期(1分钟后)
↓
┌─────────────────────────────────────────┐
│ 目标交换机 (Target Exchange) │
└─────────────┬───────────────────────────┘
│
↓
┌─────────────────────────────────────────┐
│ 目标队列 (Target Queue) │
└─────────────┬───────────────────────────┘
│
↓
Consumer
2.2 代码实现
配置类:
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class DelayQueueConfig {
// ========== 延迟队列配置 ==========
@Bean
public Queue delayQueue() {
return QueueBuilder.durable("delay.queue")
.withArgument("x-message-ttl", 60000) // ⭐ TTL: 1分钟
.withArgument("x-dead-letter-exchange", "target.exchange") // ⭐ 死信交换机
.withArgument("x-dead-letter-routing-key", "target.routing.key") // ⭐ 死信路由键
.build();
}
@Bean
public DirectExchange delayExchange() {
return new DirectExchange("delay.exchange");
}
@Bean
public Binding delayBinding(Queue delayQueue, DirectExchange delayExchange) {
return BindingBuilder.bind(delayQueue)
.to(delayExchange)
.with("delay.routing.key");
}
// ========== 目标队列配置 ==========
@Bean
public Queue targetQueue() {
return new Queue("target.queue", true);
}
@Bean
public DirectExchange targetExchange() {
return new DirectExchange("target.exchange");
}
@Bean
public Binding targetBinding(Queue targetQueue, DirectExchange targetExchange) {
return BindingBuilder.bind(targetQueue)
.to(targetExchange)
.with("target.routing.key");
}
}
生产者:
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class DelayMessageProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送延迟消息
*/
public void sendDelayMessage(String message) {
System.out.println("发送延迟消息: " + message);
System.out.println("发送时间: " + new Date());
// ⭐ 发送到延迟交换机
rabbitTemplate.convertAndSend(
"delay.exchange",
"delay.routing.key",
message
);
}
}
消费者:
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
@Component
public class DelayMessageConsumer {
/**
* 消费目标队列的消息
*/
@RabbitListener(queues = "target.queue")
public void receiveMessage(String message) {
System.out.println("消费延迟消息: " + message);
System.out.println("消费时间: " + new Date());
// 业务处理
processMessage(message);
}
private void processMessage(String message) {
// 业务逻辑:检查订单支付状态
System.out.println("处理消息: " + message);
}
}
2.3 支持任意延迟时间
方法1:动态设置TTL
public void sendDelayMessage(String message, long delayMillis) {
rabbitTemplate.convertAndSend(
"delay.exchange",
"delay.routing.key",
message,
msg -> {
// ⭐ 动态设置TTL
msg.getMessageProperties().setExpiration(String.valueOf(delayMillis));
return msg;
}
);
}
// 使用
producer.sendDelayMessage("订单12345", 30 * 60 * 1000); // 30分钟
producer.sendDelayMessage("订单67890", 2 * 60 * 60 * 1000); // 2小时
⚠️ 注意:动态TTL有个坑!
问题:RabbitMQ只会检查队列头部的消息是否过期
如果队列中有多条消息,且TTL不同:
消息1: TTL=10秒
消息2: TTL=5秒 ← 虽然先到期,但在消息1后面
消息3: TTL=20秒
结果:消息2要等消息1过期后才能被处理!💀
解决方案:rabbitmq-delayed-message-exchange插件
方法2:使用延迟交换机插件 ⭐⭐⭐⭐⭐
安装插件:
# 下载插件
wget https://github.com/rabbitmq/rabbitmq-delayed-message-exchange/releases/download/3.12.0/rabbitmq_delayed_message_exchange-3.12.0.ez
# 复制到RabbitMQ插件目录
cp rabbitmq_delayed_message_exchange-3.12.0.ez /usr/lib/rabbitmq/plugins/
# 启用插件
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
# 重启RabbitMQ
systemctl restart rabbitmq-server
配置类:
@Configuration
public class DelayExchangeConfig {
@Bean
public CustomExchange delayExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct"); // ⭐ 延迟类型
return new CustomExchange(
"delay.exchange",
"x-delayed-message", // ⭐ 类型:延迟消息
true,
false,
args
);
}
@Bean
public Queue targetQueue() {
return new Queue("target.queue", true);
}
@Bean
public Binding binding(Queue targetQueue, CustomExchange delayExchange) {
return BindingBuilder.bind(targetQueue)
.to(delayExchange)
.with("target.routing.key")
.noargs();
}
}
生产者:
public void sendDelayMessage(String message, long delayMillis) {
rabbitTemplate.convertAndSend(
"delay.exchange",
"target.routing.key",
message,
msg -> {
// ⭐ 设置延迟时间(毫秒)
msg.getMessageProperties().setDelay((int) delayMillis);
return msg;
}
);
System.out.printf("发送延迟消息,延迟%d秒: %s%n", delayMillis / 1000, message);
}
// 使用
producer.sendDelayMessage("订单12345", 30 * 60 * 1000); // 30分钟后投递
producer.sendDelayMessage("订单67890", 2 * 60 * 60 * 1000); // 2小时后投递
优点:
- ✅ 支持任意延迟时间
- ✅ 没有TTL队列头部阻塞问题
- ✅ 性能好
方案3:Kafka延迟消息(自己实现)⭐⭐⭐
Kafka本身不支持延迟消息,需要自己实现
3.1 方案:延迟Topic + 定时轮询
/**
* 延迟消息服务
*/
@Service
@Slf4j
public class KafkaDelayMessageService {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 发送延迟消息
*/
public void sendDelayMessage(String topic, String message, long delayMillis) {
// 封装延迟消息
DelayMessage delayMsg = new DelayMessage();
delayMsg.setTargetTopic(topic);
delayMsg.setMessage(message);
delayMsg.setDeliverTime(System.currentTimeMillis() + delayMillis); // 投递时间
// ⭐ 发送到延迟Topic
kafkaTemplate.send("delay-topic", JSON.toJSONString(delayMsg));
log.info("发送延迟消息,{}秒后投递: {}", delayMillis / 1000, message);
}
}
@Data
class DelayMessage {
private String targetTopic; // 目标Topic
private String message; // 消息内容
private long deliverTime; // 投递时间戳
}
定时任务:轮询延迟Topic:
@Component
@Slf4j
public class DelayMessageScheduler {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
/**
* 每秒扫描一次延迟Topic
*/
@Scheduled(fixedRate = 1000)
public void scanDelayMessages() {
// 消费延迟Topic
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
long currentTime = System.currentTimeMillis();
for (ConsumerRecord<String, String> record : records) {
DelayMessage delayMsg = JSON.parseObject(record.value(), DelayMessage.class);
if (delayMsg.getDeliverTime() <= currentTime) {
// ⭐ 到期了,投递到目标Topic
kafkaTemplate.send(delayMsg.getTargetTopic(), delayMsg.getMessage());
log.info("延迟消息到期,投递: {}", delayMsg.getMessage());
} else {
// ⭐ 未到期,重新发送到延迟Topic(设置更短的延迟)
kafkaTemplate.send("delay-topic", JSON.toJSONString(delayMsg));
}
}
}
}
缺点:
- ❌ 需要定时轮询,性能开销
- ❌ 精度有限(1秒轮询一次)
- ❌ 实现复杂
推荐:使用RocketMQ或RabbitMQ的原生支持
方案4:Redis + 定时任务 ⭐⭐⭐
原理:使用Redis的Sorted Set存储延迟消息,定时扫描
@Service
@Slf4j
public class RedisDelayMessageService {
@Autowired
private RedisTemplate<String, String> redisTemplate;
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
private static final String DELAY_KEY = "delay:messages";
/**
* 发送延迟消息
*/
public void sendDelayMessage(String topic, String message, long delayMillis) {
long deliverTime = System.currentTimeMillis() + delayMillis;
DelayMessage delayMsg = new DelayMessage();
delayMsg.setTargetTopic(topic);
delayMsg.setMessage(message);
delayMsg.setDeliverTime(deliverTime);
// ⭐ 存储到Redis Sorted Set(score=投递时间)
redisTemplate.opsForZSet().add(
DELAY_KEY,
JSON.toJSONString(delayMsg),
deliverTime
);
log.info("发送延迟消息,{}秒后投递: {}", delayMillis / 1000, message);
}
/**
* 定时扫描到期的消息
*/
@Scheduled(fixedRate = 1000)
public void scanExpiredMessages() {
long currentTime = System.currentTimeMillis();
// ⭐ 查询score <= currentTime的消息(已到期)
Set<String> expiredMessages = redisTemplate.opsForZSet()
.rangeByScore(DELAY_KEY, 0, currentTime);
if (expiredMessages != null && !expiredMessages.isEmpty()) {
for (String msgJson : expiredMessages) {
DelayMessage delayMsg = JSON.parseObject(msgJson, DelayMessage.class);
// 投递到目标Topic
kafkaTemplate.send(delayMsg.getTargetTopic(), delayMsg.getMessage());
log.info("延迟消息到期,投递: {}", delayMsg.getMessage());
// ⭐ 从Redis删除
redisTemplate.opsForZSet().remove(DELAY_KEY, msgJson);
}
}
}
}
优点:
- ✅ 支持任意延迟时间
- ✅ 实现简单
- ✅ 性能较高
缺点:
- ❌ 依赖Redis,增加了复杂度
- ❌ Redis故障会导致消息丢失(需要持久化)
📊 方案对比总结
| 方案 | 任意延迟 | 最大延迟 | 性能 | 复杂度 | 推荐度 |
|---|---|---|---|---|---|
| RocketMQ(固定级别) | ❌ | 2小时 | ⚡⚡⚡ | 😊 简单 | ⭐⭐⭐⭐ |
| RabbitMQ(TTL+死信) | ✅ | 无限制 | ⚡⚡ | 😐 中等 | ⭐⭐⭐⭐⭐ |
| RabbitMQ(插件) | ✅ | 无限制 | ⚡⚡⚡ | 😊 简单 | ⭐⭐⭐⭐⭐ |
| Kafka(自己实现) | ✅ | 无限制 | ⚡ | 😰 复杂 | ⭐⭐ |
| Redis + 定时任务 | ✅ | 无限制 | ⚡⚡ | 😐 中等 | ⭐⭐⭐ |
🎓 面试题速答
Q1: 什么是延迟消息?
A: 延迟消息 = 消息不立即投递,而是在指定时间后才能被消费
应用场景:
- 订单超时自动取消
- 定时任务调度
- 重试机制
- 限流削峰
Q2: RocketMQ如何实现延迟消息?
A:
- 设置延迟级别:
msg.setDelayTimeLevel(5)(1分钟) - 修改Topic:Broker将消息存储到内部Topic
SCHEDULE_TOPIC_XXXX - 定时扫描:定时任务每秒扫描延迟Queue,找到到期的消息
- 恢复原Topic:将到期的消息恢复到原始Topic
- 投递消费:Consumer从原Topic消费消息
限制:
- 只支持18个固定延迟级别
- 最长延迟2小时
Q3: RabbitMQ如何实现延迟消息?
A: 两种方案!
方案1:TTL + 死信队列
- 消息发送到延迟队列(设置TTL)
- TTL到期,消息变成死信
- 死信转发到目标队列
- Consumer消费
方案2:延迟交换机插件(推荐)
- 安装
rabbitmq-delayed-message-exchange插件 - 发送消息时设置
x-delay头 - 支持任意延迟时间
Q4: Kafka支持延迟消息吗?
A: Kafka本身不支持延迟消息
自己实现的方案:
-
延迟Topic + 定时轮询:
- 发送到延迟Topic
- 定时任务扫描,到期后投递到目标Topic
-
Redis + 定时任务:
- 存储到Redis Sorted Set
- 定时任务扫描,到期后投递
推荐:使用RocketMQ或RabbitMQ的原生支持
Q5: 如何选择延迟消息方案?
A: 如果使用RocketMQ:
- 延迟时间是固定的18个级别 → 直接用RocketMQ ⭐⭐⭐⭐⭐
- 需要任意延迟时间 → 考虑切换RabbitMQ
如果使用RabbitMQ:
- 直接使用延迟交换机插件 ⭐⭐⭐⭐⭐
如果使用Kafka:
- 自己实现(不推荐)
- 或者引入RocketMQ/RabbitMQ处理延迟消息
🎯 最佳实践
订单超时自动取消
@Service
@Slf4j
public class OrderService {
@Autowired
private DelayMessageProducer producer;
/**
* 创建订单
*/
@Transactional
public void createOrder(Order order) {
// 1. 保存订单到数据库(状态:待支付)
orderRepository.save(order);
// 2. 发送延迟消息:30分钟后检查支付状态
DelayMessage msg = new DelayMessage();
msg.setOrderId(order.getId());
msg.setAction("CHECK_PAYMENT");
producer.sendDelayMessage(
JSON.toJSONString(msg),
30 * 60 * 1000 // 30分钟
);
log.info("订单创建成功,30分钟后检查支付状态: {}", order.getId());
}
/**
* 用户支付成功
*/
@Transactional
public void payOrder(String orderId) {
// 更新订单状态为已支付
Order order = orderRepository.findById(orderId);
order.setStatus(OrderStatus.PAID);
orderRepository.save(order);
log.info("订单支付成功: {}", orderId);
// ⭐ 注意:延迟消息已经发送,无法取消
// 但是消费者会检查订单状态,如果已支付则忽略
}
}
消费者:
@Component
@Slf4j
public class OrderDelayMessageConsumer {
@Autowired
private OrderRepository orderRepository;
/**
* 消费延迟消息:检查订单支付状态
*/
@RabbitListener(queues = "order.delay.queue")
public void handleDelayMessage(String message) {
DelayMessage msg = JSON.parseObject(message, DelayMessage.class);
if ("CHECK_PAYMENT".equals(msg.getAction())) {
// 查询订单状态
Order order = orderRepository.findById(msg.getOrderId());
if (order.getStatus() == OrderStatus.UNPAID) {
// ⭐ 30分钟后仍未支付,自动取消订单
order.setStatus(OrderStatus.CANCELLED);
orderRepository.save(order);
log.info("订单超时未支付,自动取消: {}", order.getId());
// 可以发送通知给用户
notifyUser(order);
} else {
log.info("订单已支付,无需处理: {}", order.getId());
}
}
}
}
🎬 总结
延迟消息实现方案全景图
┌──────────────────────────────────────────────┐
│ RocketMQ(固定18个级别) │
│ ├─ 优点:简单、性能高 │
│ ├─ 缺点:只支持固定延迟时间 │
│ └─ 适用:大部分业务场景 ⭐⭐⭐⭐ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ RabbitMQ(TTL + 死信队列) │
│ ├─ 优点:支持任意延迟时间 │
│ ├─ 缺点:队列头部阻塞问题 │
│ └─ 适用:一般场景 ⭐⭐⭐ │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ RabbitMQ(延迟交换机插件)⭐⭐⭐⭐⭐ │
│ ├─ 优点:支持任意延迟,无头部阻塞 │
│ ├─ 缺点:需要安装插件 │
│ └─ 适用:推荐方案! │
└──────────────────────────────────────────────┘
┌──────────────────────────────────────────────┐
│ Kafka(自己实现) │
│ ├─ 优点:可以实现 │
│ ├─ 缺点:复杂,性能差 │
│ └─ 适用:不推荐 ⭐⭐ │
└──────────────────────────────────────────────┘
推荐:RabbitMQ延迟交换机插件 🏆
🎉 恭喜你!
你已经完全掌握了延迟消息的实现原理!🎊
核心要点:
- RocketMQ:18个固定延迟级别,简单高效
- RabbitMQ:TTL+死信队列,或延迟交换机插件(推荐)
- Kafka:不支持,需自己实现
下次面试,这样回答:
"延迟消息是指消息不立即投递,而是在指定时间后才能被消费。常用于订单超时取消、定时任务等场景。
RocketMQ通过内部Topic和定时任务实现,支持18个固定延迟级别,实现简单性能高。
RabbitMQ可以通过TTL+死信队列实现,或者安装延迟交换机插件,支持任意延迟时间。
Kafka本身不支持延迟消息,需要自己实现,比如使用延迟Topic+定时轮询,或Redis+定时任务。
我们项目中使用RabbitMQ的延迟交换机插件,因为它支持任意延迟时间,实现简单,性能也不错。"
面试官:👍 "很好!你对延迟消息理解很全面!"
本文完 🎬
上一篇: 189-如何设计一个高可用的消息队列架构.md
下一篇: 191-消息队列的推拉模式的优劣对比.md
作者注:写完这篇,我点外卖都知道怎么预约送达时间了!🍔⏰
如果这篇文章对你有帮助,请给我一个Star⭐!