消息队列(Message Queue)详解
本文深入讲解消息队列核心原理与实战,涵盖 Kafka、RabbitMQ、RocketMQ 三大主流MQ,配合完整 Java 代码示例。
目录
- 消息队列概述与应用场景
- 消息模式(点对点/发布订阅)
- [Kafka架构(Broker/Topic/Partition/Consumer Group)](#Kafka架构(Broker/Topic/Partition/Consumer Group))
- Kafka生产者(acks/retries/幂等)
- Kafka消费者(offset管理/rebalance)
- RabbitMQ(Exchange类型/Queue/Binding/ACK)
- RocketMQ(Topic/Tag/事务消息/延迟消息)
- 消息可靠性保证(生产端/Broker/消费端)
- 顺序消息
- 消息幂等
- 死信队列
- 消息积压处理
- 事务消息
- [Java客户端(KafkaTemplate/Spring AMQP/RocketMQ Client)](#Java客户端(KafkaTemplate/Spring AMQP/RocketMQ Client))
- 消息队列选型对比
- 最佳实践
一、消息队列概述与应用场景
1.1 什么是消息队列
消息队列(Message Queue,MQ)是一种分布式通信中间件,用于在应用程序之间传递消息。它实现了生产者与消费者的解耦,提供异步通信、流量削峰、最终一致性等能力。
1.2 核心应用场景
| 场景 |
说明 |
示例 |
| 异步处理 |
将耗时操作异步化,提升响应速度 |
注册后异步发送邮件/短信 |
| 流量削峰 |
缓冲突发流量,保护下游系统 |
秒杀活动,MQ缓冲请求 |
| 系统解耦 |
上下游系统通过MQ通信,降低耦合 |
订单系统通知库存/物流/积分 |
| 数据同步 |
异构系统间数据同步 |
Canal监听binlog同步到ES |
| 日志收集 |
高吞吐日志采集与传输 |
Filebeat → Kafka → ELK |
| 分布式事务 |
跨服务最终一致性 |
下单扣库存扣积分 |
1.3 引入MQ的代价
优点:异步、解耦、削峰
代价:
├── 系统复杂度增加(运维、监控)
├── 可用性降低(MQ宕机影响全链路)
├── 一致性问题(消息丢失、重复、乱序)
└── 延迟增加(异步处理非实时)
二、消息模式(点对点/发布订阅)
2.1 点对点模式(Point-to-Point)
一条消息只被一个消费者消费,多个消费者竞争消费同一队列。
// 点对点模式示意(RabbitMQ)
// 生产者发送消息到队列,只有一个消费者能收到
public class P2PDemo {
// 生产者
public void produce(Channel channel, String message) throws Exception {
channel.queueDeclare("task_queue", true, false, false, null);
channel.basicPublish("", "task_queue",
MessageProperties.PERSISTENT_TEXT_PLAIN,
message.getBytes());
}
// 消费者(多个实例竞争消费)
public void consume(Channel channel) throws Exception {
channel.basicQos(1); // 每次只预取1条,公平分发
channel.basicConsume("task_queue", false, (consumerTag, delivery) -> {
String msg = new String(delivery.getBody());
System.out.println("处理任务: " + msg);
channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
}, consumerTag -> {});
}
}
2.2 发布订阅模式(Publish/Subscribe)
一条消息被多个消费者(订阅者)同时消费。
// 发布订阅模式示意(RabbitMQ Fanout)
public class PubSubDemo {
// 生产者发送到Exchange
public void publish(Channel channel, String event) throws Exception {
channel.exchangeDeclare("order_events", "fanout", true);
channel.basicPublish("order_events", "", null, event.getBytes());
}
// 每个消费者绑定自己的队列到Exchange
public void subscribe(Channel channel, String serviceName) throws Exception {
channel.exchangeDeclare("order_events", "fanout", true);
String queueName = channel.queueDeclare().getQueue(); // 临时队列
channel.queueBind(queueName, "order_events", "");
channel.basicConsume(queueName, true, (consumerTag, delivery) -> {
System.out.println(serviceName + " 收到事件: " + new String(delivery.getBody()));
}, consumerTag -> {});
}
}
2.3 两种模式对比
| 维度 |
点对点 |
发布订阅 |
| 消费关系 |
1对1(竞争) |
1对多(广播) |
| 消息保留 |
消费后删除 |
各订阅者独立消费 |
| 典型实现 |
RabbitMQ Queue / Kafka Group |
RabbitMQ Fanout / Kafka多Group |
| 适用场景 |
任务分发 |
事件通知 |
三、Kafka架构(Broker/Topic/Partition/Consumer Group)
3.1 整体架构
Producer ──→ Kafka Cluster ──→ Consumer Group
│
├── Broker 0 (Controller)
│ ├── Topic-A, Partition 0 (Leader)
│ └── Topic-B, Partition 1 (Follower)
│
├── Broker 1
│ ├── Topic-A, Partition 1 (Leader)
│ └── Topic-A, Partition 0 (Follower)
│
└── Broker 2
├── Topic-B, Partition 0 (Leader)
└── Topic-A, Partition 1 (Follower)
3.2 核心组件
| 组件 |
职责 |
| Broker |
Kafka服务节点,负责消息存储与转发 |
| Controller |
集群控制器,管理分区Leader选举 |
| Topic |
消息的逻辑分类 |
| Partition |
Topic的物理分片,并行度的基本单位 |
| Replica |
分区副本(Leader + Follower) |
| ISR |
In-Sync Replicas,与Leader保持同步的副本集 |
| Consumer Group |
消费者组,组内竞争消费,组间广播 |
| Offset |
消息在分区内的唯一编号,消费位点 |
3.3 Partition 详解
// Partition 是 Kafka 并行处理的核心
// 一个 Topic 可以有多个 Partition,分布在不同 Broker 上
// 关键特性:
// 1. 分区内消息有序(追加写入)
// 2. 分区是并行消费的最小单位
// 3. 一个分区在同一时刻只能被组内一个消费者消费
// 分区数设置建议:
// partition数 = max(期望吞吐量/单分区吞吐量, 消费者实例数)
3.4 Consumer Group 工作机制
// Consumer Group 核心规则:
// 1. 同一 Group 内,一个 Partition 只能被一个 Consumer 消费
// 2. 不同 Group 之间,各自独立消费(广播效果)
// 3. Consumer 数量 > Partition 数量时,多余的 Consumer 空闲
// 示例:Topic有3个分区,Group有2个消费者
// Partition 0 → Consumer A
// Partition 1 → Consumer A
// Partition 2 → Consumer B
四、Kafka生产者(acks/retries/幂等)
4.1 acks 确认机制
| acks值 |
含义 |
可靠性 |
性能 |
0 |
不等待任何确认 |
最低(可能丢消息) |
最高 |
1 |
Leader写入即确认 |
中等(Leader宕机可能丢) |
中等 |
-1/all |
ISR全部写入确认 |
最高 |
最低 |
4.2 生产者配置与代码
import org.apache.kafka.clients.producer.*;
import org.apache.kafka.common.serialization.StringSerializer;
import java.util.Properties;
public class KafkaProducerExample {
public static KafkaProducer<String, String> createProducer() {
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class.getName());
// === 可靠性配置 ===
props.put(ProducerConfig.ACKS_CONFIG, "all"); // ISR全部确认
props.put(ProducerConfig.RETRIES_CONFIG, 3); // 失败重试3次
props.put(ProducerConfig.RETRY_BACKOFF_MS_CONFIG, 100); // 重试间隔
// === 幂等性配置 ===
props.put(ProducerConfig.ENABLE_IDEMPOTENCE_CONFIG, true); // 开启幂等
props.put(ProducerConfig.MAX_IN_FLIGHT_REQUESTS_PER_CONNECTION, 5); // 幂等下最大5
// === 性能配置 ===
props.put(ProducerConfig.BATCH_SIZE_CONFIG, 16384); // 批量大小16KB
props.put(ProducerConfig.LINGER_MS_CONFIG, 5); // 等待5ms凑批
props.put(ProducerConfig.BUFFER_MEMORY_CONFIG, 33554432); // 缓冲区32MB
return new KafkaProducer<>(props);
}
public static void main(String[] args) {
KafkaProducer<String, String> producer = createProducer();
try {
for (int i = 0; i < 100; i++) {
ProducerRecord<String, String> record = new ProducerRecord<>(
"order-topic", // topic
"order-key-" + i % 10, // key(决定分区)
"{\"orderId\":" + i + "}" // value
);
// 异步发送 + 回调
producer.send(record, (metadata, exception) -> {
if (exception != null) {
System.err.println("发送失败: " + exception.getMessage());
} else {
System.out.printf("发送成功: partition=%d, offset=%d%n",
metadata.partition(), metadata.offset());
}
});
}
producer.flush(); // 确保所有消息发送完成
} finally {
producer.close();
}
}
}
4.3 幂等生产者原理
幂等性(Idempotent Producer):
开启 enable.idempotence=true 后:
├── Broker 为每个 Producer 分配 PID(Producer ID)
├── 每条消息携带 SequenceNumber(按分区递增)
├── Broker 端去重:相同 PID + Partition + SeqNum 只写入一次
└── 保证:单分区、单会话内 Exactly-Once
局限性:
├── 仅限单分区(跨分区需事务)
└── 仅限单会话(Producer重启后PID变化)
五、Kafka消费者(offset管理/rebalance)
5.1 消费者代码示例
import org.apache.kafka.clients.consumer.*;
import org.apache.kafka.common.serialization.StringDeserializer;
import java.time.Duration;
import java.util.Collections;
import java.util.Properties;
public class KafkaConsumerExample {
public static void main(String[] args) {
Properties props = new Properties();
props.put(ConsumerConfig.BOOTSTRAP_SERVERS_CONFIG, "localhost:9092");
props.put(ConsumerConfig.GROUP_ID_CONFIG, "order-service-group");
props.put(ConsumerConfig.KEY_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
props.put(ConsumerConfig.VALUE_DESERIALIZER_CLASS_CONFIG, StringDeserializer.class.getName());
// === Offset 管理 ===
props.put(ConsumerConfig.ENABLE_AUTO_COMMIT_CONFIG, false); // 关闭自动提交
props.put(ConsumerConfig.AUTO_OFFSET_RESET_CONFIG, "earliest"); // 无offset时从头消费
// === Rebalance 配置 ===
props.put(ConsumerConfig.SESSION_TIMEOUT_MS_CONFIG, 30000);
props.put(ConsumerConfig.HEARTBEAT_INTERVAL_MS_CONFIG, 10000);
props.put(ConsumerConfig.MAX_POLL_INTERVAL_MS_CONFIG, 300000);
props.put(ConsumerConfig.MAX_POLL_RECORDS_CONFIG, 500);
KafkaConsumer<String, String> consumer = new KafkaConsumer<>(props);
consumer.subscribe(Collections.singletonList("order-topic"));
try {
while (true) {
ConsumerRecords<String, String> records = consumer.poll(Duration.ofMillis(100));
for (ConsumerRecord<String, String> record : records) {
try {
// 业务处理
processOrder(record.value());
// 手动提交offset(逐条)
consumer.commitSync();
} catch (Exception e) {
System.err.println("消费失败: " + e.getMessage());
// 可选:记录到死信Topic / 重试
}
}
}
} finally {
consumer.close();
}
}
private static void processOrder(String orderJson) {
System.out.println("处理订单: " + orderJson);
}
}
5.2 Offset 提交策略对比
| 策略 |
配置/方法 |
优点 |
缺点 |
| 自动提交 |
enable.auto.commit=true |
简单 |
可能重复消费/丢失 |
| 同步手动 |
commitSync() |
可靠 |
阻塞,性能低 |
| 异步手动 |
commitAsync() |
性能好 |
失败不重试 |
| 混合策略 |
异步+关闭时同步 |
兼顾性能与可靠 |
稍复杂 |
5.3 Rebalance 机制
触发条件:
├── 消费者加入/离开 Group
├── 消费者被踢出(超过 max.poll.interval.ms 未poll)
├── Topic 分区数变化
└── 订阅的 Topic 列表变化
分配策略:
├── Range(默认):按Topic范围分配,可能不均匀
├── RoundRobin:轮询分配,较均匀
├── Sticky:尽量保持上次分配,减少迁移
└── CooperativeSticky(推荐):增量式Rebalance,避免STW
Rebalance 影响:
├── STW(Stop The World):所有消费者暂停
├── 重复消费:offset未提交的消息被重新分配
└── 解决:减少Rebalance频率 + 消费幂等
六、RabbitMQ(Exchange类型/Queue/Binding/ACK)
6.1 Exchange 类型详解
| 类型 |
路由规则 |
场景 |
| Direct |
routing key 精确匹配 |
点对点,日志级别分发 |
| Fanout |
广播到所有绑定队列 |
事件通知,系统广播 |
| Topic |
routing key 模式匹配(*一个词,#多个词) |
多维度路由 |
| Headers |
消息头属性匹配 |
复杂条件匹配(少用) |
6.2 Spring AMQP 完整示例
import org.springframework.amqp.core.*;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Service;
// === 配置类:声明 Exchange、Queue、Binding ===
@Configuration
public class RabbitMQConfig {
public static final String ORDER_EXCHANGE = "order.exchange";
public static final String ORDER_QUEUE = "order.queue";
public static final String ORDER_ROUTING_KEY = "order.create";
// 死信交换机和队列
public static final String DLX_EXCHANGE = "order.dlx.exchange";
public static final String DLX_QUEUE = "order.dlx.queue";
@Bean
public DirectExchange orderExchange() {
return ExchangeBuilder.directExchange(ORDER_EXCHANGE)
.durable(true) // 持久化
.build();
}
@Bean
public Queue orderQueue() {
return QueueBuilder.durable(ORDER_QUEUE)
.withArgument("x-dead-letter-exchange", DLX_EXCHANGE) // 死信交换机
.withArgument("x-dead-letter-routing-key", "dlx.order") // 死信路由键
.withArgument("x-message-ttl", 60000) // TTL 60秒
.build();
}
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue())
.to(orderExchange())
.with(ORDER_ROUTING_KEY);
}
// 死信队列配置
@Bean
public DirectExchange dlxExchange() {
return ExchangeBuilder.directExchange(DLX_EXCHANGE).durable(true).build();
}
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.order");
}
}
// === 生产者 ===
@Service
public class OrderMessageProducer {
private final RabbitTemplate rabbitTemplate;
public OrderMessageProducer(RabbitTemplate rabbitTemplate) {
this.rabbitTemplate = rabbitTemplate;
// 开启 Publisher Confirm
this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.println("消息确认到达Broker: " + correlationData);
} else {
System.err.println("消息未到达Broker: " + cause);
// 重试或记录
}
});
// 开启 Return(消息无法路由到队列时回调)
this.rabbitTemplate.setReturnsCallback(returned -> {
System.err.println("消息被退回: " + returned.getMessage());
});
this.rabbitTemplate.setMandatory(true);
}
public void sendOrderMessage(String orderId) {
rabbitTemplate.convertAndSend(
RabbitMQConfig.ORDER_EXCHANGE,
RabbitMQConfig.ORDER_ROUTING_KEY,
orderId,
message -> {
// 消息持久化(默认已开启)
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
}
);
}
}
// === 消费者 ===
@Service
public class OrderMessageConsumer {
@RabbitListener(queues = RabbitMQConfig.ORDER_QUEUE)
public void handleOrderMessage(String orderId, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag) throws Exception {
try {
// 业务处理
System.out.println("处理订单: " + orderId);
// 手动ACK
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 拒绝消息,不重回队列(进入死信队列)
channel.basicNack(deliveryTag, false, false);
}
}
// 死信队列消费者
@RabbitListener(queues = RabbitMQConfig.DLX_QUEUE)
public void handleDeadLetter(String orderId) {
System.err.println("死信消息,需人工处理: " + orderId);
// 告警、记录、人工介入
}
}
6.3 ACK 机制
ACK 流程:
├── autoAck=true:消息投递即确认(不安全,消费失败则丢失)
└── autoAck=false(推荐):
├── basicAck(tag, multiple) → 确认消费成功
├── basicNack(tag, multiple, requeue) → 拒绝
│ ├── requeue=true → 重回队列(可能死循环)
│ └── requeue=false → 进入死信队列
└── basicReject(tag, requeue) → 拒绝单条
注意事项:
├── 必须处理所有消息(ack/nack),否则Channel阻塞
├── 避免无限requeue(设置重试次数)
└── 批量ack提升性能:basicAck(tag, multiple=true)
七、RocketMQ(Topic/Tag/事务消息/延迟消息)
7.1 核心概念
| 概念 |
说明 |
| NameServer |
轻量注册中心,管理Topic路由 |
| Broker |
消息存储与转发(Master/Slave) |
| Topic |
消息一级分类 |
| Tag |
消息二级分类(过滤) |
| MessageQueue |
Topic下的分区(默认4个) |
| ConsumerGroup |
消费者组(集群/广播模式) |
7.2 生产者与消费者
import org.apache.rocketmq.client.producer.DefaultMQProducer;
import org.apache.rocketmq.client.producer.SendResult;
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.common.message.Message;
import java.nio.charset.StandardCharsets;
// === 生产者 ===
public class RocketMQProducerExample {
public static void main(String[] args) throws Exception {
DefaultMQProducer producer = new DefaultMQProducer("order-producer-group");
producer.setNamesrvAddr("localhost:9876");
producer.setRetryTimesWhenSendFailed(3); // 同步发送重试
producer.setSendMsgTimeout(5000);
producer.start();
// 普通消息
Message msg = new Message(
"OrderTopic", // Topic
"TagCreate", // Tag(二级分类)
"OrderID_001", // Keys(业务唯一标识)
"{\"orderId\":\"001\",\"amount\":99.9}".getBytes(StandardCharsets.UTF_8)
);
SendResult result = producer.send(msg);
System.out.printf("发送结果: status=%s, msgId=%s, queueId=%d%n",
result.getSendStatus(), result.getMsgId(), result.getMessageQueue().getQueueId());
producer.shutdown();
}
}
// === 消费者 ===
public class RocketMQConsumerExample {
public static void main(String[] args) throws Exception {
DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("order-consumer-group");
consumer.setNamesrvAddr("localhost:9876");
// 订阅 Topic + Tag过滤(多个Tag用 || 分隔)
consumer.subscribe("OrderTopic", "TagCreate || TagPay");
// 集群消费(默认):组内竞争
// consumer.setMessageModel(MessageModel.CLUSTERING);
consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (var msg : msgs) {
try {
String body = new String(msg.getBody(), StandardCharsets.UTF_8);
System.out.printf("消费消息: topic=%s, tag=%s, body=%s%n",
msg.getTopic(), msg.getTags(), body);
} catch (Exception e) {
// 返回 RECONSUME_LATER 触发重试
return ConsumeConcurrentlyStatus.RECONSUME_LATER;
}
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
consumer.start();
System.out.println("Consumer Started.");
}
}
7.3 延迟消息
// RocketMQ 支持18个延迟级别:
// 1s 5s 10s 30s 1m 2m 3m 4m 5m 6m 7m 8m 9m 10m 20m 30m 1h 2h
// 对应 level: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public class DelayMessageExample {
public static void sendDelayMessage(DefaultMQProducer producer) throws Exception {
Message msg = new Message("DelayTopic", "延迟30分钟执行".getBytes(StandardCharsets.UTF_8));
msg.setDelayTimeLevel(16); // 30分钟
producer.send(msg);
}
}
八、消息可靠性保证(生产端/Broker/消费端)
8.1 三个环节
Producer ──[环节1]──→ Broker ──[环节2]──→ Consumer
│ │ │
发送确认 存储持久化 消费确认
8.2 各环节保障措施
| 环节 |
Kafka |
RabbitMQ |
RocketMQ |
| 生产端 |
acks=all + retries + 幂等 |
Publisher Confirm + Return |
同步发送 + 重试 |
| Broker |
副本(replication.factor≥3) + ISR |
持久化 + 镜像队列 |
同步刷盘 + 主从同步 |
| 消费端 |
手动提交offset |
手动ACK |
返回CONSUME_SUCCESS |
8.3 Kafka 可靠性配置
// 生产端
props.put("acks", "all");
props.put("retries", Integer.MAX_VALUE);
props.put("enable.idempotence", true);
// Broker端(server.properties)
// replication.factor=3 # 副本数
// min.insync.replicas=2 # 最小同步副本数
// unclean.leader.election.enable=false # 禁止非ISR副本成为Leader
// 消费端
props.put("enable.auto.commit", false);
// 业务处理成功后手动 commitSync()
8.4 RabbitMQ 可靠性配置
// application.yml
spring:
rabbitmq:
publisher-confirm-type: correlated # 开启Confirm
publisher-returns: true # 开启Return
listener:
simple:
acknowledge-mode: manual # 手动ACK
retry:
enabled: true
max-attempts: 3
initial-interval: 1000ms
九、顺序消息
9.1 问题本质
消息队列的分区/队列机制天然导致跨分区无序。要保证顺序,核心原则:同一业务key的消息路由到同一分区,且单线程消费。
9.2 Kafka 顺序消息
// 生产端:相同orderId路由到同一分区
public class OrderPartitioner implements Partitioner {
@Override
public int partition(String topic, Object key, byte[] keyBytes,
Object value, byte[] valueBytes, Cluster cluster) {
int partitionCount = cluster.partitionCountForTopic(topic);
// 按orderId hash,保证同一订单到同一分区
return Math.abs(key.hashCode()) % partitionCount;
}
@Override
public void close() {}
@Override
public void configure(Map<String, ?> configs) {}
}
// 消费端:单线程处理同一分区的消息(Kafka默认保证)
// 注意:不要在消费者内部开多线程处理同一分区的消息
9.3 RocketMQ 顺序消息
// 生产端:MessageQueueSelector 保证同一订单到同一队列
SendResult result = producer.send(msg, (mqs, msg, arg) -> {
String orderId = (String) arg;
int index = Math.abs(orderId.hashCode()) % mqs.size();
return mqs.get(index);
}, orderId);
// 消费端:使用 MessageListenerOrderly(单线程消费同一队列)
consumer.registerMessageListener((MessageListenerOrderly) (msgs, context) -> {
for (var msg : msgs) {
System.out.println("顺序消费: " + new String(msg.getBody()));
}
return ConsumeOrderlyStatus.SUCCESS;
});
十、消息幂等
10.1 为什么会重复
重复消费场景:
├── 生产端重试:网络抖动,Broker已收到但ACK丢失,Producer重发
├── Rebalance:消费者处理完但未提交offset,分区被重新分配
├── 消费端重试:处理超时,Broker认为失败重新投递
└── 手动重发:运维操作/补偿机制
10.2 幂等方案
// 方案一:Redis去重(推荐,高性能)
@Service
public class IdempotentService {
@Autowired
private StringRedisTemplate redisTemplate;
private static final long EXPIRE_HOURS = 24;
public boolean isDuplicate(String messageId) {
Boolean success = redisTemplate.opsForValue()
.setIfAbsent("mq:idempotent:" + messageId, "1", EXPIRE_HOURS, TimeUnit.HOURS);
return Boolean.FALSE.equals(success); // 返回true表示重复
}
}
// 方案二:数据库去重表
// CREATE TABLE mq_dedup (
// message_id VARCHAR(64) PRIMARY KEY,
// consumer_group VARCHAR(64),
// consume_time DATETIME,
// status TINYINT
// );
// 方案三:状态机(适合订单场景)
public boolean updateOrderStatus(String orderId, int fromStatus, int toStatus) {
// UPDATE orders SET status = #{toStatus}
// WHERE order_id = #{orderId} AND status = #{fromStatus}
// 状态只能单向流转,天然幂等
int rows = orderMapper.updateStatus(orderId, fromStatus, toStatus);
return rows > 0;
}
10.3 消费者幂等集成
@RabbitListener(queues = "order.queue")
public void consume(Message message, Channel channel) throws Exception {
String messageId = message.getMessageProperties().getMessageId();
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 幂等检查
if (idempotentService.isDuplicate(messageId)) {
System.out.println("重复消息,跳过: " + messageId);
channel.basicAck(deliveryTag, false);
return;
}
try {
// 业务处理
processBusiness(message);
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
channel.basicNack(deliveryTag, false, false);
}
}
十一、死信队列
11.1 死信产生条件(RabbitMQ)
消息变为死信的三种情况:
├── 消息被拒绝(basicReject/basicNack)且 requeue=false
├── 消息TTL过期(队列级或消息级)
└── 队列达到最大长度(x-max-length)
11.2 RabbitMQ 死信队列实现
@Configuration
public class DeadLetterConfig {
// 业务队列 → 绑定死信交换机
@Bean
public Queue businessQueue() {
return QueueBuilder.durable("business.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "dlx.key")
.withArgument("x-message-ttl", 30000) // 30秒过期
.withArgument("x-max-length", 10000) // 最大长度
.build();
}
// 死信交换机
@Bean
public DirectExchange dlxExchange() {
return new DirectExchange("dlx.exchange", true, false);
}
// 死信队列
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable("dlx.queue").build();
}
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue()).to(dlxExchange()).with("dlx.key");
}
}
// 死信消费者:告警 + 记录 + 人工处理
@RabbitListener(queues = "dlx.queue")
public void handleDeadLetter(Message message) {
String body = new String(message.getBody());
String reason = message.getMessageProperties()
.getHeader("x-death") != null ? "rejected/expired/overflow" : "unknown";
log.error("死信消息告警: body={}, reason={}", body, reason);
// 1. 发送告警通知(钉钉/邮件)
// 2. 持久化到数据库供人工排查
// 3. 修复后可重新投递到业务队列
}
11.3 RocketMQ 死信队列
// RocketMQ:消费重试16次后自动进入死信队列
// 死信Topic命名:%DLQ%ConsumerGroupName
// 需要单独订阅死信Topic进行处理
DefaultMQPushConsumer dlqConsumer = new DefaultMQPushConsumer("dlq-handler-group");
dlqConsumer.subscribe("%DLQ%order-consumer-group", "*");
dlqConsumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
for (var msg : msgs) {
log.error("RocketMQ死信: msgId={}, body={}", msg.getMsgId(),
new String(msg.getBody()));
// 告警 + 人工处理
}
return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
});
dlqConsumer.start();
十二、消息积压处理
12.1 应急处理方案
紧急处理流程:
├── 第一步:快速扩容
│ ├── 增加消费者实例数(不超过分区/队列数)
│ ├── 增加Topic分区数(Kafka: kafka-topics --alter --partitions)
│ └── 增加队列数(RocketMQ控制台)
│
├── 第二步:临时转发(分区数不够时)
│ ├── 新建临时Topic(分区数扩大N倍)
│ ├── 临时消费者:只做转发,不做业务处理
│ └── 大量消费者消费临时Topic
│
└── 第三步:降级非核心逻辑
├── 跳过日志记录、通知等非核心操作
└── 批量入库代替逐条入库
12.2 临时转发方案代码
// 临时消费者:快速转发到新Topic
@KafkaListener(topics = "original-topic", groupId = "temp-forwarder")
public void forwardToNewTopic(ConsumerRecord<String, String> record) {
// 不做任何业务处理,直接转发
kafkaTemplate.send("temp-topic-expanded", record.key(), record.value());
}
// 大量消费者消费临时Topic(分区数扩大10倍)
@KafkaListener(topics = "temp-topic-expanded", groupId = "real-consumer",
concurrency = "20") // 20个消费线程
public void realConsume(ConsumerRecord<String, String> record) {
processBusiness(record.value());
}
12.3 预防措施
// 监控积压(Kafka JMX / 命令行)
// kafka-consumer-groups.sh --describe --group order-service-group
// 关注 LAG 列(积压量)
// 告警规则:
// LAG > 10000 → 警告
// LAG > 100000 → 严重
// LAG 持续增长 → 紧急
// 消费端性能优化:
// 1. 批量消费(max.poll.records=500)
// 2. 批量入库(JDBC batch insert)
// 3. 异步处理非核心逻辑
// 4. 连接池复用(DB/Redis/HTTP)
十三、事务消息
13.1 RocketMQ 事务消息(核心)
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;
// === 事务消息生产者 ===
public class TransactionMessageExample {
public static void main(String[] args) throws Exception {
TransactionMQProducer producer = new TransactionMQProducer("tx-producer-group");
producer.setNamesrvAddr("localhost:9876");
// 设置事务监听器
producer.setTransactionListener(new OrderTransactionListener());
producer.start();
// 发送事务消息
Message msg = new Message("OrderTopic", "TagCreate",
"{\"orderId\":\"TX001\",\"amount\":199.9}".getBytes());
// 发送半消息 + 执行本地事务
producer.sendMessageInTransaction(msg, "orderId=TX001");
}
}
// === 事务监听器 ===
class OrderTransactionListener implements TransactionListener {
/**
* 执行本地事务(半消息发送成功后回调)
*/
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
String orderId = (String) arg;
try {
// 执行本地事务:创建订单 + 扣减库存
orderService.createOrder(orderId);
inventoryService.deductStock(orderId);
// 本地事务成功 → 提交消息
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
// 本地事务失败 → 回滚消息
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
/**
* 事务回查(Broker长时间未收到确认时回调)
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId = msg.getKeys();
// 查询本地事务状态(如查数据库订单是否存在)
boolean exists = orderService.orderExists(orderId);
if (exists) {
return LocalTransactionState.COMMIT_MESSAGE;
}
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
13.2 事务消息流程
┌──────────┐ ┌──────────┐ ┌──────────┐
│ Producer │ │ Broker │ │ Consumer │
└────┬─────┘ └────┬─────┘ └────┬─────┘
│ ① 发送半消息 │ │
│────────────────────→│ │
│ ② 半消息存储成功 │ │
│←────────────────────│ │
│ │ │
│ ③ 执行本地事务 │ │
│ (DB操作等) │ │
│ │ │
│ ④ Commit/Rollback │ │
│────────────────────→│ │
│ │ ⑤ 投递消息 │
│ │───────────────────→│
│ │ │
│ (若④超时) │ │
│ ⑥ 事务回查 │ │
│←────────────────────│ │
│ ⑦ 返回事务状态 │ │
│────────────────────→│ │
13.3 Kafka 事务
// Kafka 事务:跨分区原子写入 + 消费-处理-提交原子操作
Properties props = new Properties();
props.put("transactional.id", "order-tx-001"); // 唯一事务ID
props.put("enable.idempotence", true);
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
producer.initTransactions();
try {
producer.beginTransaction();
// 跨分区写入(原子性)
producer.send(new ProducerRecord<>("topic-a", "key1", "value1"));
producer.send(new ProducerRecord<>("topic-b", "key2", "value2"));
// 消费-处理-提交(Exactly-Once)
producer.sendOffsetsToTransaction(offsets, consumerGroupId);
producer.commitTransaction();
} catch (Exception e) {
producer.abortTransaction();
}
十四、Java客户端(KafkaTemplate/Spring AMQP/RocketMQ Client)
14.1 Spring Kafka(KafkaTemplate)
// === Maven依赖 ===
// <dependency>
// <groupId>org.springframework.kafka</groupId>
// <artifactId>spring-kafka</artifactId>
// </dependency>
// === application.yml ===
// spring:
// kafka:
// bootstrap-servers: localhost:9092
// producer:
// acks: all
// retries: 3
// key-serializer: org.apache.kafka.common.serialization.StringSerializer
// value-serializer: org.apache.kafka.common.serialization.StringSerializer
// consumer:
// group-id: order-group
// auto-offset-reset: earliest
// enable-auto-commit: false
// key-deserializer: org.apache.kafka.common.serialization.StringDeserializer
// value-deserializer: org.apache.kafka.common.serialization.StringDeserializer
// listener:
// ack-mode: manual_immediate
// === 生产者 ===
@Service
public class KafkaOrderProducer {
@Autowired
private KafkaTemplate<String, String> kafkaTemplate;
public void sendOrder(OrderDTO order) {
CompletableFuture<SendResult<String, String>> future =
kafkaTemplate.send("order-topic", order.getOrderId(), JSON.toJSONString(order));
future.whenComplete((result, ex) -> {
if (ex != null) {
log.error("发送失败: orderId={}", order.getOrderId(), ex);
} else {
log.info("发送成功: partition={}, offset={}",
result.getRecordMetadata().partition(),
result.getRecordMetadata().offset());
}
});
}
}
// === 消费者 ===
@Service
public class KafkaOrderConsumer {
@KafkaListener(topics = "order-topic", groupId = "order-group", concurrency = "3")
public void consume(ConsumerRecord<String, String> record, Acknowledgment ack) {
try {
OrderDTO order = JSON.parseObject(record.value(), OrderDTO.class);
log.info("消费订单: {}", order.getOrderId());
// 业务处理...
ack.acknowledge(); // 手动提交offset
} catch (Exception e) {
log.error("消费异常", e);
// 不ack → 消息会被重新投递(需幂等)
}
}
}
14.2 Spring AMQP(RabbitMQ)
// === Maven依赖 ===
// <dependency>
// <groupId>org.springframework.boot</groupId>
// <artifactId>spring-boot-starter-amqp</artifactId>
// </dependency>
// === 生产者(使用RabbitTemplate)===
@Service
public class RabbitOrderProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(OrderDTO order) {
rabbitTemplate.convertAndSend(
"order.exchange",
"order.create",
order,
message -> {
message.getMessageProperties().setMessageId(UUID.randomUUID().toString());
message.getMessageProperties().setTimestamp(new Date());
return message;
}
);
}
}
// === 消费者(注解方式)===
@Service
public class RabbitOrderConsumer {
@RabbitListener(queues = "order.queue", concurrency = "3-5")
public void consume(OrderDTO order, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws Exception {
try {
log.info("处理订单: {}", order.getOrderId());
channel.basicAck(tag, false);
} catch (Exception e) {
channel.basicNack(tag, false, false); // 进入死信队列
}
}
}
14.3 RocketMQ Spring Boot Starter
// === Maven依赖 ===
// <dependency>
// <groupId>org.apache.rocketmq</groupId>
// <artifactId>rocketmq-spring-boot-starter</artifactId>
// <version>2.2.3</version>
// </dependency>
// === application.yml ===
// rocketmq:
// name-server: localhost:9876
// producer:
// group: order-producer-group
// send-message-timeout: 5000
// retry-times-when-send-failed: 3
// === 生产者 ===
@Service
public class RocketMQOrderProducer {
@Autowired
private RocketMQTemplate rocketMQTemplate;
public void sendOrder(OrderDTO order) {
// 同步发送
SendResult result = rocketMQTemplate.syncSend(
"OrderTopic:TagCreate", // Topic:Tag
MessageBuilder.withPayload(order)
.setHeader("KEYS", order.getOrderId())
.build()
);
log.info("发送结果: {}", result.getSendStatus());
}
public void sendDelayOrder(OrderDTO order) {
// 延迟消息(level 3 = 10秒)
rocketMQTemplate.syncSend("OrderTopic:TagDelay",
MessageBuilder.withPayload(order).build(),
5000, // 超时
3); // 延迟级别
}
}
// === 消费者 ===
@Service
@RocketMQMessageListener(
topic = "OrderTopic",
selectorExpression = "TagCreate || TagPay", // Tag过滤
consumerGroup = "order-consumer-group",
consumeMode = ConsumeMode.CONCURRENTLY
)
public class RocketMQOrderConsumer implements RocketMQListener<OrderDTO> {
@Override
public void onMessage(OrderDTO order) {
log.info("消费订单: {}", order.getOrderId());
// 抛出异常会触发重试
}
}
十五、消息队列选型对比
15.1 综合对比表
| 维度 |
Kafka |
RabbitMQ |
RocketMQ |
| 吞吐量 |
百万级/s |
万级/s |
十万级/s |
| 延迟 |
ms级(批量) |
μs级 |
ms级 |
| 可靠性 |
高 |
高 |
非常高 |
| 事务消息 |
支持 |
不原生支持 |
原生支持 |
| 延迟消息 |
不支持 |
TTL+DLX |
18个级别 |
| 顺序消息 |
分区内有序 |
单队列有序 |
队列内有序 |
| 消息回溯 |
支持 |
不支持 |
支持 |
| 消息过滤 |
不支持(消费端) |
routing key |
Tag + SQL |
| 协议 |
自定义 |
AMQP/MQTT |
自定义 |
| 开发语言 |
Scala/Java |
Erlang |
Java |
| 社区生态 |
极活跃 |
活跃 |
国内活跃 |
| 运维难度 |
中(依赖ZK/KRaft) |
低 |
中 |
15.2 选型建议
选 Kafka:
├── 日志收集(ELK/EFK)
├── 大数据/流处理(Flink/Spark)
├── 高吞吐场景(百万级TPS)
└── 消息回溯需求
选 RabbitMQ:
├── 企业级应用集成
├── 复杂路由需求(多种Exchange)
├── 中小规模系统(万级TPS)
└── 需要AMQP协议兼容
选 RocketMQ:
├── 电商/金融(事务消息)
├── 延迟消息需求
├── 国内生态(阿里系)
└── 需要消息过滤(Tag)
十六、最佳实践
16.1 生产端最佳实践
// 1. 必须设置key(用于分区路由和消息追踪)
ProducerRecord<String, String> record = new ProducerRecord<>(
"topic", orderId, payload); // key = orderId
// 2. 异步发送 + 回调处理失败
producer.send(record, (metadata, ex) -> {
if (ex != null) {
// 记录失败消息到本地表,定时补偿重发
failedMessageService.save(record, ex);
}
});
// 3. 批量发送提升吞吐
props.put("batch.size", 32768); // 32KB
props.put("linger.ms", 10); // 等待10ms凑批
// 4. 消息体不要过大(建议 < 1MB)
// 大消息考虑压缩或存OSS只传URL
16.2 消费端最佳实践
// 1. 手动提交offset(业务成功后)
// 2. 消费幂等(必须!)
// 3. 合理设置并发度
@KafkaListener(topics = "order-topic", concurrency = "6") // 6个消费线程
// 4. 批量消费提升性能
@KafkaListener(topics = "order-topic", batch = "true")
public void batchConsume(List<ConsumerRecord<String, String>> records) {
List<OrderDTO> orders = records.stream()
.map(r -> JSON.parseObject(r.value(), OrderDTO.class))
.collect(Collectors.toList());
// 批量入库
orderService.batchInsert(orders);
}
// 5. 消费失败处理策略
// 重试N次 → 死信队列 → 告警 → 人工处理
// 不要无限重试!
16.3 运维最佳实践
1. 监控指标:
├── 消费积压(LAG)
├── 生产/消费TPS
├── Broker磁盘使用率
├── 网络IO
└── 消费者Rebalance次数
2. 容量规划:
├── 分区数 = 消费者数 × 每消费者分区数
├── 副本数 ≥ 3
├── 磁盘保留策略(时间/大小)
└── 预留30%容量buffer
3. 高可用:
├── Broker集群 ≥ 3节点
├── 跨机架/可用区部署
├── min.insync.replicas = 2
└── 定期演练故障切换
4. 安全:
├── 认证(SASL/SSL)
├── 授权(ACL)
├── 消息加密(敏感数据)
└── 网络隔离
16.4 常见踩坑总结
| 问题 |
原因 |
解决方案 |
| 消息丢失 |
acks=0/自动ACK |
acks=all + 手动ACK |
| 重复消费 |
Rebalance/重试 |
幂等(去重表/Redis) |
| 消息乱序 |
多分区/多线程 |
同key同分区 + 单线程消费 |
| 消费积压 |
消费慢/消费者少 |
扩容 + 批量 + 降级 |
| Rebalance频繁 |
处理超时/心跳超时 |
调大max.poll.interval |
| 内存溢出 |
无界队列/消息过大 |
有界队列 + 消息压缩 |
| 死循环重试 |
消费异常无限requeue |
限制重试次数 + 死信队列 |