RabbitMQ的消息模式和高级特性
一、RabbitMQ 概述
RabbitMQ 是基于 AMQP(高级消息队列协议)实现的开源消息中间件,采用 Erlang 语言开发。其核心作用是实现系统间的异步通信、解耦、削峰填谷。它广泛应用于分布式系统、微服务架构中,作为连接不同服务的"神经中枢",有效避免因系统间直接调用导致的耦合过高、峰值压力过大及单点故障问题。
1.1 适用场景
- 订单处理:用户下单后,订单系统发送消息,库存系统、积分系统、物流系统并行处理
- 日志采集:多服务日志统一发送到日志队列,由日志中心异步处理
- 邮件/短信通知:注册成功、密码找回等非即时性通知,异步发送
- 数据同步:主数据变更后发送消息,通知各业务系统更新缓存或索引
二、RabbitMQ 核心作用
- 解耦:打破系统间直接调用的依赖。生产者只需发送消息,无需关心消费者的实现及状态;消费者只需监听消息,无需与生产者直接交互。降低系统耦合度,便于后续扩展和维护。
- 异步通信:生产者发送消息后无需等待消费者处理完成,可立即返回,显著提升系统响应速度(吞吐量)。消费者在后台异步处理消息,避免同步调用导致的线程阻塞。
- 削峰填谷:当系统面临突发流量(如秒杀、大促)时,消息队列可暂存大量请求,消费者按自身处理能力逐步消费,避免后端数据库或服务因瞬间压力过大而崩溃。
- 消息缓冲:当下游服务暂时不可用(如重启、故障、网络波动)时,消息可在队列中持久化暂存,待服务恢复后继续消费,保证数据不丢失,维持最终一致性。
- 流量控制:通过调节消费者并发数量或预取计数(Prefetch Count),控制消息处理节奏,防止下游服务过载。
- 顺序保证:单队列下可保证消息顺序性,满足特定业务需求(如状态机变更)。
- 最终一致性:结合本地事务和消息表,可实现分布式事务的最终一致性。
三、RabbitMQ 的优点与缺点
3.1 优点
- 高可靠性:支持消息持久化、事务机制、多种确认机制(Publisher Confirm、Consumer ACK)、死信队列(DLX)等,确保消息在极端情况下不丢失。
- 灵活路由:提供多种交换机类型(Direct, Fanout, Topic, Headers),支持复杂的路由规则,满足多样化的业务分发需求。
- 多语言支持:提供 Java, Python, Go, .NET, Node.js 等几乎所有主流语言的客户端库,集成成本低。
- 管理便捷:内置强大的 Web 管理界面,可实时监控队列长度、连接数、消息速率,并支持在线配置交换机和队列。
- 插件生态:拥有丰富的插件体系(如
rabbitmq_delayed_message_exchange实现延迟队列,rabbitmq_shovel实现数据同步,rabbitmq_federation实现跨集群同步),可扩展性强。 - 社区活跃:作为老牌开源项目,文档齐全,Stack Overflow 和 GitHub 上问题解决方案丰富,学习资源充足。
- 支持多种协议:原生支持 AMQP,通过插件支持 MQTT、STOMP,满足物联网和 WebSocket 场景。
- 可视化运维:Web 管理界面功能强大,可在线创建队列、交换机,查看消息速率,管理用户权限,无需编写脚本即可完成大部分运维操作。
3.2 缺点
- 吞吐量瓶颈:相比 Kafka 等日志型消息队列,RabbitMQ 的吞吐量较低(通常在万级到十万级 TPS),不适合海量日志采集(日均 TB 级别)或大数据流处理场景。
- Erlang 门槛:底层基于 Erlang 开发,若需进行深度定制、源码级排查或二次开发,学习曲线较陡峭。大部分运维和开发人员不具备 Erlang 背景,排查深层次问题时可能遇到困难。
- 集群运维复杂:虽然支持集群,但镜像队列(Mirrored Queues)配置、脑裂处理及版本升级对运维人员要求较高。集群扩容时需要谨慎操作,可能导致服务抖动。
- 资源消耗:大量未消费的消息堆积会占用大量内存和磁盘空间,若未合理配置 TTL 或配额,可能导致节点 OOM 或磁盘写满宕机。
- 消息顺序性保障较弱:多消费者并发消费时无法保证顺序,若需严格顺序需使用单消费者或分区(需借助插件)。
- 分布式事务支持有限:RabbitMQ 本身不提供完整的分布式事务方案,需借助本地事务表+重试机制实现最终一致性。
四、RabbitMQ 核心组件详解
4.1 生产者(Producer)
消息的发送方。负责创建消息(包含消息体、属性、路由键),建立连接与通道,将消息投递至交换机,并处理发送确认(Confirm)以确保消息到达 Broker。
最佳实践:
- 生产者应复用连接和通道,避免频繁创建销毁
- 高并发场景下建议启用 Publisher Confirm 确保消息不丢
- 对于重要业务消息,建议设置消息持久化并配合 Confirm 机制
4.2 通道(Channel)
建立在 TCP 连接之上的虚拟连接。由于 TCP 连接创建和销毁开销大,RabbitMQ 推荐复用 TCP 连接,在其上创建多个 Channel 进行并发通信。Channel 是线程不安全的,多线程环境下需为每个线程创建独立的 Channel,或使用线程池管理。
注意事项:
- 单个连接可创建数百个 Channel,但不宜过多(建议 ≤ 100)
- Channel 使用完毕后应关闭释放资源,但在复用连接的模式下可缓存复用
- Spring AMQP 的
CachingConnectionFactory已封装好 Channel 的缓存管理
4.3 交换机(Exchange)
消息的"路由器"。接收生产者消息,根据绑定键(Binding Key)和路由算法将消息分发到队列。交换机属性包括:名称、持久化、自动删除、内部(Internal,仅用于交换机间绑定)。
4.3.1 交换机类型详解
| 类型 | 匹配规则 | 适用场景 | 性能 | 备注 |
|---|---|---|---|---|
| Direct Exchange | 精确匹配 Routing Key | 任务分发、日志级别分类 | 高 | 最常用,简单直接 |
| Fanout Exchange | 广播所有队列 | 事件广播、配置更新 | 最高 | 忽略 Routing Key |
| Topic Exchange | 通配符匹配:*(一个单词),#(零或多个单词) | 复杂的消息路由、按维度过滤 | 中 | 最灵活,规则丰富 |
| Headers Exchange | 根据消息头属性匹配(x-match: all/any) | 多条件过滤 | 低 | 性能较差,少用 |
默认交换机(Default Exchange):RabbitMQ 预置的 Direct 类型交换机,名称是空字符串。所有队列都会自动绑定到该交换机,Binding Key 即为队列名称。生产者发送消息时指定 routingKey 为队列名,即可直接发送到队列,无需显式绑定。
4.4 队列(Queue)
消息的缓冲区。存储被路由过来的消息,直到被消费者消费。队列属性包括:
- 持久化(Durable):队列元数据存盘,Broker 重启后队列不丢失。注意:队列持久化不保证消息不丢失,消息本身也需设置持久化。
- 独占(Exclusive):仅当前连接可见,连接断开后自动删除。通常用于临时队列。
- 自动删除(Auto-delete):当最后一个消费者断开后自动删除。
- 消息 TTL:设置消息在队列中的存活时间,过期自动丢弃或进入死信队列。
- 队列 TTL:队列空闲指定时间后自动删除。
- 最大长度:队列可容纳的最大消息数或最大字节数,超限后根据策略丢弃或进入死信。
- 溢出策略:
drop-head(丢弃头部)、reject-publish(拒绝新消息)。 - 惰性队列(Lazy Queue):RabbitMQ 3.6+ 引入,消息直接写入磁盘,减少内存消耗,适用于长队列场景。
4.5 消费者(Consumer)
消息的接收方。订阅队列,拉取或推送接收消息,处理业务逻辑后发送确认(ACK)。若未 ACK 且连接断开,消息会重新入队。
消费模式:
- 推模式(Push):订阅队列后,Broker 主动推送消息,需实现
Consumer接口或使用@RabbitListener。实时性好。 - 拉模式(Pull):主动调用
basicGet拉取消息,类似轮询。适用于批量处理或控制消费速率。
4.6 虚拟主机(Virtual Host)
逻辑隔离单元。类似于 MySQL 中的 Database,用于隔离不同项目或环境的交换机、队列和用户权限,默认名为 /。不同 VHost 间完全隔离,名称空间独立。
最佳实践:
- 按环境划分:
/dev、/test、/prod - 按业务线划分:
/order、/user、/payment - 每个 VHost 分配独立用户,最小权限原则
4.7 绑定(Binding)
交换机和队列之间的关联关系。绑定包含 Binding Key,决定了消息如何从交换机路由到队列。一个队列可绑定多个交换机,一个交换机也可绑定多个队列。
五、RabbitMQ 核心消息模式
结合 Spring Boot (Spring AMQP) 框架,提供六种核心模式的完整代码示例。
前置准备
环境要求:
- JDK 8+
- Spring Boot 2.x
- RabbitMQ Server 3.8+(建议 3.9+)
Maven 依赖 (pom.xml):
<dependencies>
<!-- Spring Boot Starter for RabbitMQ -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<!-- 测试依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- JSON 序列化(可选) -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
</dependencies>
基础配置 (application.yml):
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
virtual-host: /
# 连接池配置
connection-timeout: 15000
# 生产者确认
publisher-confirm-type: correlated # 开启 Confirm 回调
publisher-returns: true # 开启 Return 回调
template:
mandatory: true # 开启强制回调
# 消费者配置
listener:
simple:
acknowledge-mode: manual # 手动 ACK
prefetch: 1 # 预取数量
concurrency: 1 # 最小消费者数
max-concurrency: 5 # 最大消费者数
retry:
enabled: true # 开启重试
max-attempts: 3 # 最大重试次数
initial-interval: 1000 # 重试间隔
5.1 简单模式 (Simple Mode)
原理:无交换机(使用 Default Exchange),生产者直接发送消息到指定队列,一对一消费。
1. 配置类
@Configuration
public class SimpleConfig {
public static final String SIMPLE_QUEUE = "simple.queue";
@Bean
public Queue simpleQueue() {
// durable=true: 队列持久化,重启后存在
// exclusive=false: 非独占
// auto-delete=false: 不自动删除
return QueueBuilder.durable(SIMPLE_QUEUE).build();
}
}
2. 生产者
@Component
@Slf4j
public class SimpleProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String msg) {
// 默认使用 default exchange,routingKey 即为队列名
rabbitTemplate.convertAndSend(SimpleConfig.SIMPLE_QUEUE, msg);
log.info(" [x] Sent: {}", msg);
}
// 发送带自定义属性的消息
public void sendWithProperties(String msg) {
MessageProperties properties = new MessageProperties();
properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化
properties.setContentType("text/plain");
properties.setMessageId(UUID.randomUUID().toString());
Message message = new Message(msg.getBytes(StandardCharsets.UTF_8), properties);
rabbitTemplate.send(SimpleConfig.SIMPLE_QUEUE, message);
log.info(" [x] Sent with properties: {}", msg);
}
}
3. 消费者
@Component
@Slf4j
public class SimpleConsumer {
@RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
public void receive(String msg) {
log.info(" [.] Received: {}", msg);
// 业务逻辑处理...
// 默认自动 ACK,方法执行完毕即 ACK
}
// 手动 ACK 示例(需配置 acknowledge-mode: manual)
@RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
public void receiveManual(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
try {
log.info(" [.] Received: {}", msg);
// 业务处理...
// 手动确认
channel.basicAck(deliveryTag, false);
log.debug(" [.] Acked: {}", deliveryTag);
} catch (Exception e) {
log.error("Process failed: {}", msg, e);
// 拒绝并重新入队(requeue=true)
channel.basicNack(deliveryTag, false, true);
// 或拒绝并不重新入队(进入死信)
// channel.basicNack(deliveryTag, false, false);
}
}
}
4. 测试类
@SpringBootTest
public class SimpleModeTest {
@Autowired
private SimpleProducer producer;
@Test
public void testSend() throws InterruptedException {
producer.send("Hello RabbitMQ!");
producer.sendWithProperties("Message with properties");
// 等待消费者处理
Thread.sleep(2000);
}
}
5.2 工作队列模式 (Work Queue Mode)
原理:多个消费者监听同一队列,消息轮询分发。需开启手动 ACK和预取限制以实现公平分发(能者多劳)。
1. 配置文件(已在基础配置中开启)
acknowledge-mode: manual 和 prefetch: 1 已在 application.yml 中配置。
2. 消费者 A (慢速处理)
@Component
@Slf4j
public class WorkConsumerA {
@RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
public void receive(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
log.info(" [Worker A] Received: {}", msg);
try {
// 模拟耗时操作
Thread.sleep(3000);
// 业务处理...
// 手动 ACK
channel.basicAck(deliveryTag, false);
log.info(" [Worker A] Done & ACKed: {}", deliveryTag);
} catch (Exception e) {
log.error(" [Worker A] Processing failed: {}", msg, e);
// 处理失败,拒绝消息且不重新入队 (requeue=false)
// 可根据业务决定是否 requeue,避免无限循环
channel.basicNack(deliveryTag, false, false);
log.info(" [Worker A] Nacked, not requeue");
}
}
}
3. 消费者 B (快速处理)
@Component
@Slf4j
public class WorkConsumerB {
@RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
public void receive(Message message, Channel channel) throws Exception {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
log.info(" [Worker B] Received: {}", msg);
try {
// 模拟快速处理
Thread.sleep(500);
// 业务处理...
channel.basicAck(deliveryTag, false);
log.info(" [Worker B] Done & ACKed: {}", deliveryTag);
} catch (Exception e) {
log.error(" [Worker B] Processing failed: {}", msg, e);
channel.basicNack(deliveryTag, false, false);
}
}
}
4. 批量发送测试
@Component
public class WorkQueueTest {
@Autowired
private SimpleProducer producer;
@PostConstruct
public void testWorkQueue() {
// 模拟发送 10 条消息
for (int i = 1; i <= 10; i++) {
producer.send("Task " + i + ": " + LocalDateTime.now());
try {
Thread.sleep(100); // 间隔 100ms
} catch (InterruptedException ignored) {}
}
}
}
由于 prefetch=1,消费者 B 处理快会消费更多消息,实现"能者多劳"。
5.3 发布订阅模式 (Publish/Subscribe)
原理:使用 Fanout Exchange。消息广播给所有绑定该交换机的队列。通常队列是临时且独占的,确保每个消费者实例都能收到全量消息。
1. 配置类
@Configuration
public class FanoutConfig {
public static final String FANOUT_EXCHANGE = "fanout.exchange";
@Bean
public FanoutExchange fanoutExchange() {
// durable=true: 交换机持久化
// auto-delete=false: 不自动删除
return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE)
.durable(true)
.build();
}
// 也可以不定义具体队列,由消费者动态创建
}
2. 生产者
@Component
@Slf4j
public class FanoutProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String msg) {
// 第二个参数 routingKey 在 Fanout 模式下被忽略,通常传空字符串
rabbitTemplate.convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", msg);
log.info(" [x] Sent Broadcast: {}", msg);
}
public void sendEvent(String eventType, Object payload) {
// 发送 JSON 格式事件
Map<String, Object> event = new HashMap<>();
event.put("type", eventType);
event.put("data", payload);
event.put("timestamp", Instant.now().toEpochMilli());
rabbitTemplate.convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", event);
log.info(" [x] Sent Event: {} - {}", eventType, payload);
}
}
3. 消费者 A
@Component
@Slf4j
public class FanoutConsumerA {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
))
public void receive(String msg) {
log.info(" [Consumer A] Received: {}", msg);
}
// 接收 Map 类型
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
))
public void receiveEvent(Map<String, Object> event) {
log.info(" [Consumer A] Received Event: {}", event);
}
}
4. 消费者 B
@Component
@Slf4j
public class FanoutConsumerB {
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
))
public void receive(String msg) {
log.info(" [Consumer B] Received: {}", msg);
}
}
5. 测试
@SpringBootTest
public class FanoutModeTest {
@Autowired
private FanoutProducer producer;
@Test
public void testBroadcast() throws InterruptedException {
producer.send("System configuration updated!");
producer.sendEvent("USER_REGISTER", Map.of("userId", 1001, "username", "张三"));
// 所有消费者都会收到相同消息
Thread.sleep(2000);
}
}
5.4 路由模式 (Routing Mode)
原理:使用 Direct Exchange。Routing Key 必须与 Binding Key 完全匹配。
1. 配置类
@Configuration
public class DirectConfig {
public static final String DIRECT_EXCHANGE = "direct.exchange";
public static final String QUEUE_INFO = "queue.info";
public static final String QUEUE_WARN = "queue.warn";
public static final String QUEUE_ERROR = "queue.error";
@Bean
public DirectExchange directExchange() {
return ExchangeBuilder.directExchange(DIRECT_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Queue infoQueue() {
return QueueBuilder.durable(QUEUE_INFO).build();
}
@Bean
public Queue warnQueue() {
return QueueBuilder.durable(QUEUE_WARN).build();
}
@Bean
public Queue errorQueue() {
return QueueBuilder.durable(QUEUE_ERROR).build();
}
@Bean
public Binding bindingInfo() {
return BindingBuilder.bind(infoQueue())
.to(directExchange())
.with("info");
}
@Bean
public Binding bindingWarn() {
return BindingBuilder.bind(warnQueue())
.to(directExchange())
.with("warn");
}
@Bean
public Binding bindingError() {
return BindingBuilder.bind(errorQueue())
.to(directExchange())
.with("error");
}
// 也可以让 error 队列同时接收 error 和 fatal 级别
@Bean
public Binding bindingFatal() {
return BindingBuilder.bind(errorQueue())
.to(directExchange())
.with("fatal");
}
}
2. 生产者
@Component
@Slf4j
public class DirectProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String level, String msg) {
// routingKey 为 "info"、"warn"、"error"、"fatal" 等
rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE, level, msg);
log.info(" [x] Sent [{}]: {}", level, msg);
}
public void sendLog(LogEntry logEntry) {
rabbitTemplate.convertAndSend(
DirectConfig.DIRECT_EXCHANGE,
logEntry.getLevel(),
logEntry
);
log.info(" [x] Sent Log [{}]: {}", logEntry.getLevel(), logEntry.getMessage());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class LogEntry {
private String level;
private String message;
private String service;
private long timestamp;
}
}
3. 消费者
@Component
@Slf4j
public class DirectConsumer {
@RabbitListener(queues = DirectConfig.QUEUE_INFO)
public void receiveInfo(String msg) {
log.info(" [Info Log] Received: {}", msg);
}
@RabbitListener(queues = DirectConfig.QUEUE_INFO)
public void receiveInfo(DirectProducer.LogEntry logEntry) {
log.info(" [Info Log] {} - {}", logEntry.getService(), logEntry.getMessage());
}
@RabbitListener(queues = DirectConfig.QUEUE_WARN)
public void receiveWarn(String msg) {
log.info(" [Warn Log] Received: {}", msg);
}
@RabbitListener(queues = DirectConfig.QUEUE_ERROR)
public void receiveError(String msg) {
log.info(" [Error Log] Received: {}", msg);
}
}
4. 测试
@SpringBootTest
public class DirectModeTest {
@Autowired
private DirectProducer producer;
@Test
public void testRouting() throws InterruptedException {
producer.send("info", "User login success");
producer.send("warn", "Slow database query detected");
producer.send("error", "Payment service timeout");
producer.send("fatal", "Database connection lost"); // error 队列也会收到
Thread.sleep(2000);
}
}
5.5 主题模式 (Topic Mode)
原理:使用 Topic Exchange。支持通配符 *(匹配一个单词)和 #(匹配零个或多个单词)。单词由点号分隔。
通配符规则:
*:匹配一个单词,如user.*匹配user.info、user.error,不匹配user.info.detail#:匹配零个或多个单词,如log.#匹配log、log.info、log.error.db- 示例:
*.error.#匹配user.error、system.error.db.timeout
1. 配置类
@Configuration
public class TopicConfig {
public static final String TOPIC_EXCHANGE = "topic.exchange";
public static final String QUEUE_ALL = "queue.all"; // 接收所有日志
public static final String QUEUE_ERROR = "queue.error"; // 仅接收 error 级别
public static final String QUEUE_USER = "queue.user"; // 接收用户相关日志
@Bean
public TopicExchange topicExchange() {
return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Queue allQueue() {
return QueueBuilder.durable(QUEUE_ALL).build();
}
@Bean
public Queue errorQueue() {
return QueueBuilder.durable(QUEUE_ERROR).build();
}
@Bean
public Queue userQueue() {
return QueueBuilder.durable(QUEUE_USER).build();
}
// log.# 匹配所有以 log. 开头的 routingKey
@Bean
public Binding bindingAll() {
return BindingBuilder.bind(allQueue())
.to(topicExchange())
.with("log.#");
}
// *.error 匹配 user.error、system.error,不匹配 log.error.info
@Bean
public Binding bindingError() {
return BindingBuilder.bind(errorQueue())
.to(topicExchange())
.with("*.error");
}
// user.* 匹配 user.info、user.error、user.debug
@Bean
public Binding bindingUser() {
return BindingBuilder.bind(userQueue())
.to(topicExchange())
.with("user.*");
}
// 也可同时匹配多个模式
@Bean
public Binding bindingErrorUser() {
return BindingBuilder.bind(errorQueue())
.to(topicExchange())
.with("user.error");
}
}
2. 生产者
@Component
@Slf4j
public class TopicProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String routingKey, String msg) {
rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
log.info(" [x] Sent [{}]: {}", routingKey, msg);
}
public void sendSystemLog(String module, String level, String msg) {
String routingKey = String.format("system.%s.%s", module, level);
rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
log.info(" [x] Sent [{}]: {}", routingKey, msg);
}
public void sendUserLog(String action, String level, String msg) {
String routingKey = String.format("user.%s.%s", action, level);
rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
log.info(" [x] Sent [{}]: {}", routingKey, msg);
}
}
3. 消费者
@Component
@Slf4j
public class TopicConsumer {
@RabbitListener(queues = TopicConfig.QUEUE_ALL)
public void receiveAll(String msg) {
log.info(" [All Logger] {}", msg);
}
@RabbitListener(queues = TopicConfig.QUEUE_ERROR)
public void receiveError(String msg) {
log.info(" [Error Logger] {}", msg);
}
@RabbitListener(queues = TopicConfig.QUEUE_USER)
public void receiveUser(String msg) {
log.info(" [User Logger] {}", msg);
}
}
4. 测试
@SpringBootTest
public class TopicModeTest {
@Autowired
private TopicProducer producer;
@Test
public void testTopic() throws InterruptedException {
// 发送不同 routingKey 的消息
producer.send("log.info", "System started"); // all 收到
producer.send("log.error", "DB connection failed"); // all 收到
producer.send("user.error", "User login failed"); // all、error、user 都收到
producer.send("user.info", "User profile updated"); // all、user 收到
producer.send("system.error", "CPU overload"); // all、error 收到
producer.send("payment.success", "Order paid"); // 无队列匹配,消息丢失(需配置备份交换机)
Thread.sleep(3000);
}
}
5.6 RPC 模式 (RPC Mode)
原理:客户端发送请求并携带 replyTo(回调队列)和 correlationId,服务端处理后回复。Spring AMQP 提供了 RabbitTemplate 的 convertSendAndReceive 方法封装这一模式。
1. 配置类
@Configuration
public class RpcConfig {
public static final String RPC_QUEUE = "rpc.request.queue";
public static final String RPC_EXCHANGE = ""; // 使用默认交换机
@Bean
public Queue rpcQueue() {
return QueueBuilder.durable(RPC_QUEUE).build();
}
// 配置 RabbitTemplate 支持 RPC
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 设置 reply 超时时间(毫秒)
template.setReplyTimeout(60000);
// 设置默认的 reply 队列(可选)
template.setReplyAddress(RPC_QUEUE + ".reply");
return template;
}
}
2. 服务端 (Server)
@Component
@Slf4j
public class RpcServer {
@RabbitListener(queues = RpcConfig.RPC_QUEUE)
public String process(String message) {
log.info(" [Server] Received request: {}", message);
// 模拟业务处理
String result = handleRequest(message);
log.info(" [Server] Returning response: {}", result);
return result; // 返回值会自动发送到 replyTo 队列
}
// 复杂处理示例,访问 correlationId 和 replyTo
@RabbitListener(queues = RpcConfig.RPC_QUEUE)
public void processComplex(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String correlationId = message.getMessageProperties().getCorrelationId();
String replyTo = message.getMessageProperties().getReplyTo();
String request = new String(message.getBody(), StandardCharsets.UTF_8);
log.info(" [Server] Received: {}, correlationId: {}, replyTo: {}", request, correlationId, replyTo);
try {
String result = "Processed: " + request;
// 手动发送响应
MessageProperties replyProps = new MessageProperties();
replyProps.setCorrelationId(correlationId);
Message reply = new Message(result.getBytes(StandardCharsets.UTF_8), replyProps);
channel.basicPublish("", replyTo, null, reply.getBody());
channel.basicAck(deliveryTag, false);
log.info(" [Server] Replied: {}", result);
} catch (Exception e) {
log.error("Processing error", e);
channel.basicNack(deliveryTag, false, true);
}
}
private String handleRequest(String request) {
// 模拟计算或数据库查询
try {
Thread.sleep(1000); // 模拟耗时
} catch (InterruptedException ignored) {}
return "Result for: " + request;
}
}
3. 客户端 (Client)
@Component
@Slf4j
public class RpcClient {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 同步调用 RPC 服务
*/
public String call(String message) {
log.info(" [Client] Sending request: {}", message);
// convertSendAndReceive 自动处理回调队列和 correlationId
// 参数:exchange, routingKey, message, correlationData
String response = (String) rabbitTemplate.convertSendAndReceive(
"", // 默认交换机
RpcConfig.RPC_QUEUE,
message
);
log.info(" [Client] Received response: {}", response);
return response;
}
/**
* 异步调用,带回调
*/
public void callAsync(String message, RpcCallback callback) {
// 发送消息
rabbitTemplate.convertAndSend("", RpcConfig.RPC_QUEUE, message, m -> {
// 设置消息属性
m.getMessageProperties().setReplyTo(RpcConfig.RPC_QUEUE + ".reply");
m.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
return m;
});
// 监听响应(需单独实现一个消费者监听 reply 队列)
// 这里简化,实际项目中可使用 CompletableFuture
log.info(" [Client] Async request sent: {}", message);
}
public interface RpcCallback {
void onSuccess(String result);
void onFailure(Throwable t);
}
}
// 使用 CompletableFuture 的高级版本
@Component
public class AsyncRpcClient {
@Autowired
private RabbitTemplate rabbitTemplate;
private final ConcurrentHashMap<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>();
@PostConstruct
public void init() {
// 启动时创建 reply 队列监听器
rabbitTemplate.execute(channel -> {
String replyQueue = channel.queueDeclare().getQueue();
// 这里简化,实际需绑定消费者到 replyQueue
return null;
});
}
public CompletableFuture<String> callAsync(String message) {
CompletableFuture<String> future = new CompletableFuture<>();
String correlationId = UUID.randomUUID().toString();
pendingRequests.put(correlationId, future);
rabbitTemplate.convertAndSend("", RpcConfig.RPC_QUEUE, message, m -> {
m.getMessageProperties().setCorrelationId(correlationId);
m.getMessageProperties().setReplyTo("amq.rabbitmq.reply-to"); // 使用 Direct reply-to
return m;
});
return future;
}
}
4. 测试
@SpringBootTest
public class RpcModeTest {
@Autowired
private RpcClient rpcClient;
@Test
public void testRpc() {
String result = rpcClient.call("Calculate: 10 + 20");
assertEquals("Result for: Calculate: 10 + 20", result);
}
@Test
public void testMultipleCalls() {
for (int i = 0; i < 5; i++) {
String response = rpcClient.call("Task " + i);
log.info("Response {}: {}", i, response);
}
}
}
六、消息可靠性与高级特性
6.1 确保消息不丢失(三重保险代码示例)
消息丢失可能发生在三个环节:
- 生产者发送到交换机
- 交换机路由到队列
- 队列存储后消费者处理
A. 生产者确认 (Publisher Confirm)
配置类:
@Configuration
@Slf4j
public class ReliabilityConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 1. 开启 Confirm 模式(消息到达 Exchange 的确认)
template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
@Override
public void confirm(CorrelationData correlationData, boolean ack, String cause) {
if (ack) {
log.debug("Message sent to exchange successfully, correlationId: {}",
correlationData != null ? correlationData.getId() : null);
// 如果消息有回调数据,可更新消息状态
if (correlationData != null && correlationData.getReturned() != null) {
// 处理 returned 消息
}
} else {
log.error("Message failed to reach exchange, correlationId: {}, cause: {}",
correlationData != null ? correlationData.getId() : null, cause);
// 保存失败消息到数据库,定时重发
if (correlationData != null && correlationData.getReturned() != null) {
Message failedMessage = correlationData.getReturned().getMessage();
saveFailedMessage(failedMessage, cause);
}
}
}
});
// 2. 开启 Return 回调(消息从 Exchange 路由到 Queue 失败)
template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
@Override
public void returnedMessage(ReturnedMessage returned) {
log.error("Message routing failed: exchange={}, routingKey={}, replyCode={}, replyText={}, message={}",
returned.getExchange(), returned.getRoutingKey(),
returned.getReplyCode(), returned.getReplyText(),
returned.getMessage());
// 处理无法路由的消息(保存到数据库或发送到告警)
handleUnroutableMessage(returned);
}
});
return template;
}
private void saveFailedMessage(Message message, String cause) {
// 实际项目可存入数据库失败消息表
log.info("Saving failed message to DB: {}, cause: {}", message, cause);
}
private void handleUnroutableMessage(ReturnedMessage returned) {
// 可将无法路由的消息重新发送到备份交换机
log.info("Handling unroutable message: {}", returned);
}
}
生产者使用 CorrelationData:
@Component
public class ReliableProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendWithConfirm(String exchange, String routingKey, Object message) {
CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
correlationData.setReturned(new ReturnedMessage(
new Message(message.toString().getBytes(), new MessageProperties()),
-1,
"Pending",
exchange,
routingKey
));
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
log.info("Message sent with correlationId: {}", correlationData.getId());
}
// 批量发送,等待确认
public void sendBatchWithConfirm(List<String> messages) {
List<CorrelationData> correlationList = new ArrayList<>();
for (String msg : messages) {
CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
correlationList.add(cd);
rabbitTemplate.convertAndSend("batch.exchange", "batch.key", msg, cd);
}
// 等待所有确认(需根据实际需求决定等待时间)
for (CorrelationData cd : correlationList) {
try {
if (cd.getFuture().get(5, TimeUnit.SECONDS).isAck()) {
log.info("Message {} confirmed", cd.getId());
} else {
log.error("Message {} nacked", cd.getId());
}
} catch (Exception e) {
log.error("Wait for confirm timeout", e);
}
}
}
}
B. 消费者手动 ACK 与重试
手动 ACK 消费者:
@Component
@Slf4j
public class ReliableConsumer {
@RabbitListener(queues = "reliable.queue", ackMode = "MANUAL")
public void processMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
try {
log.info("Processing message: {}", msg);
// 业务处理
businessProcess(msg);
// 成功确认
channel.basicAck(deliveryTag, false);
log.debug("ACKed message: {}", deliveryTag);
} catch (BusinessException e) {
log.error("Business processing failed, will not requeue: {}", msg, e);
// 业务异常,拒绝并不重新入队(进入死信)
channel.basicNack(deliveryTag, false, false);
} catch (TransientException e) {
log.error("Transient error, will requeue: {}", msg, e);
// 临时异常,拒绝并重新入队(等待下次消费)
channel.basicNack(deliveryTag, false, true);
} catch (Exception e) {
log.error("Unknown error, check and decide: {}", msg, e);
// 未知错误,根据业务决定是否 requeue
channel.basicNack(deliveryTag, false, false);
}
}
private void businessProcess(String msg) {
// 业务逻辑
}
// 业务异常(无需重试)
static class BusinessException extends RuntimeException {}
// 临时异常(可重试)
static class TransientException extends RuntimeException {}
}
Spring Retry 配置:
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true
max-attempts: 3
initial-interval: 1000
multiplier: 2.0
max-interval: 10000
stateless: true # 无状态重试
结合重试的消费者:
@Component
@Slf4j
public class RetryConsumer {
@RabbitListener(queues = "retry.queue")
public void processWithRetry(String message) {
log.info("Processing message: {}, attempt: {}", message,
RetrySynchronizationManager.getContext().getRetryCount());
// 业务逻辑可能抛出异常,触发重试
if (message.contains("error")) {
throw new RuntimeException("Simulated error");
}
log.info("Processed successfully: {}", message);
}
}
C. 消息与队列持久化
声明持久化队列和交换机:
@Configuration
public class PersistentConfig {
// 持久化队列
@Bean
public Queue persistentQueue() {
return QueueBuilder.durable("persistent.queue")
.withArgument("x-queue-type", "quorum") // 使用仲裁队列,强一致
.withArgument("x-max-length", 10000) // 最大长度
.withArgument("x-overflow", "reject-publish") // 溢出策略
.build();
}
// 持久化交换机
@Bean
public DirectExchange persistentExchange() {
return ExchangeBuilder.directExchange("persistent.exchange")
.durable(true)
.build();
}
// 持久化绑定
@Bean
public Binding persistentBinding() {
return BindingBuilder.bind(persistentQueue())
.to(persistentExchange())
.with("persistent.key");
}
}
发送持久化消息:
public void sendPersistentMessage(String msg) {
MessageProperties props = new MessageProperties();
props.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化
props.setContentType("text/plain");
props.setPriority(5); // 优先级
Message message = new Message(msg.getBytes(StandardCharsets.UTF_8), props);
rabbitTemplate.send("persistent.exchange", "persistent.key", message);
}
6.2 死信队列 (DLX) 实现
当消息被拒(requeue=false)、过期或队列满时,自动转入死信队列。
完整死信配置:
@Configuration
public class DlqConfig {
// 死信交换机
public static final String DLX_EXCHANGE = "dlx.exchange";
public static final String DLQ_QUEUE = "dlq.queue";
// 业务交换机
public static final String BUSINESS_EXCHANGE = "business.exchange";
public static final String BUSINESS_QUEUE = "business.queue";
// 延迟消息交换机(使用插件)
public static final String DELAYED_EXCHANGE = "delayed.exchange";
// 1. 定义死信交换机和队列
@Bean
public DirectExchange dlxExchange() {
return ExchangeBuilder.directExchange(DLX_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Queue dlqQueue() {
return QueueBuilder.durable(DLQ_QUEUE)
.withArgument("x-queue-type", "classic")
.build();
}
@Bean
public Binding dlqBinding() {
return BindingBuilder.bind(dlqQueue())
.to(dlxExchange())
.with("dead");
}
// 2. 定义业务队列,配置死信参数
@Bean
public Queue businessQueue() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", DLX_EXCHANGE); // 死信交换机
args.put("x-dead-letter-routing-key", "dead"); // 死信路由键
args.put("x-message-ttl", 60000); // 消息 TTL:60秒过期变死信
args.put("x-max-length", 1000); // 最大消息数
args.put("x-overflow", "reject-publish"); // 满时拒绝新消息
return QueueBuilder.durable(BUSINESS_QUEUE)
.withArguments(args)
.build();
}
@Bean
public DirectExchange businessExchange() {
return ExchangeBuilder.directExchange(BUSINESS_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Binding businessBinding() {
return BindingBuilder.bind(businessQueue())
.to(businessExchange())
.with("business");
}
// 3. 为死信队列设置过期时间,可二次死信
@Bean
public Queue dlqWithTtl() {
Map<String, Object> args = new HashMap<>();
args.put("x-dead-letter-exchange", DLX_EXCHANGE); // 可指向另一个死信交换机
args.put("x-dead-letter-routing-key", "dead.again");
args.put("x-message-ttl", 3600000); // 1小时后再次死信
return QueueBuilder.durable("dlq.ttl")
.withArguments(args)
.build();
}
}
死信消费者:
@Component
@Slf4j
public class DlqConsumer {
@RabbitListener(queues = DlqConfig.DLQ_QUEUE)
public void processDeadLetter(Message message) {
String originalMsg = new String(message.getBody(), StandardCharsets.UTF_8);
MessageProperties props = message.getMessageProperties();
log.error("Received dead letter message: {}", originalMsg);
log.error("Dead letter reason: {}", props.getHeader("x-first-death-reason"));
log.error("Original exchange: {}", props.getHeader("x-first-death-exchange"));
log.error("Original queue: {}", props.getHeader("x-first-death-queue"));
// 记录死信消息,人工干预或自动修复后重新发送
saveToDeadLetterTable(originalMsg, props);
// 可尝试重新处理(修改后重新发送)
// retryProcess(originalMsg);
}
private void saveToDeadLetterTable(String msg, MessageProperties props) {
// 保存到数据库,用于监控和排查
log.info("Dead letter saved: {}", msg);
}
}
生产者触发死信:
@Component
public class BusinessProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendWithExpiration(String msg, long ttl) {
MessagePostProcessor processor = m -> {
m.getMessageProperties().setExpiration(String.valueOf(ttl));
return m;
};
rabbitTemplate.convertAndSend(
DlqConfig.BUSINESS_EXCHANGE,
"business",
msg,
processor
);
}
}
6.3 延迟队列(基于插件)
需要安装 rabbitmq_delayed_message_exchange 插件:
rabbitmq-plugins enable rabbitmq_delayed_message_exchange
配置延迟交换机:
@Configuration
public class DelayedConfig {
public static final String DELAYED_EXCHANGE = "delayed.exchange";
public static final String DELAYED_QUEUE = "delayed.queue";
@Bean
public CustomExchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct"); // 内部转发类型:direct/topic/fanout
return new CustomExchange(
DELAYED_EXCHANGE,
"x-delayed-message", // 交换机类型
true, // durable
false, // auto-delete
args
);
}
@Bean
public Queue delayedQueue() {
return QueueBuilder.durable(DELAYED_QUEUE).build();
}
@Bean
public Binding delayedBinding() {
return BindingBuilder.bind(delayedQueue())
.to(delayedExchange())
.with("delayed.routing.key")
.noargs(); // 注意:CustomExchange 的 binding 方式
}
}
延迟消息生产者:
@Component
@Slf4j
public class DelayedProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
/**
* 发送延迟消息
* @param msg 消息内容
* @param delayMs 延迟毫秒数
*/
public void sendDelayed(String msg, int delayMs) {
MessagePostProcessor processor = message -> {
message.getMessageProperties().setHeader("x-delay", delayMs);
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
};
rabbitTemplate.convertAndSend(
DelayedConfig.DELAYED_EXCHANGE,
"delayed.routing.key",
msg,
processor
);
log.info("Sent delayed message: {}, delay: {}ms", msg, delayMs);
}
/**
* 发送延迟对象
*/
public void sendDelayedObject(DelayedTask task) {
MessagePostProcessor processor = message -> {
message.getMessageProperties().setHeader("x-delay", task.getDelayMs());
message.getMessageProperties().setHeader("x-task-type", task.getType());
return message;
};
rabbitTemplate.convertAndSend(
DelayedConfig.DELAYED_EXCHANGE,
"delayed.routing.key",
task,
processor
);
log.info("Sent delayed task: {}, delay: {}ms", task, task.getDelayMs());
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class DelayedTask {
private String id;
private String type;
private int delayMs;
private Map<String, Object> payload;
}
}
延迟消息消费者:
@Component
@Slf4j
public class DelayedConsumer {
@RabbitListener(queues = DelayedConfig.DELAYED_QUEUE)
public void receiveDelayed(String msg) {
log.info("Received delayed message at {}: {}", LocalDateTime.now(), msg);
}
@RabbitListener(queues = DelayedConfig.DELAYED_QUEUE)
public void receiveDelayedTask(DelayedProducer.DelayedTask task) {
log.info("Received delayed task at {}: {}", LocalDateTime.now(), task);
// 根据任务类型执行不同逻辑
switch (task.getType()) {
case "order.cancel":
cancelOrder(task);
break;
case "payment.timeout":
handlePaymentTimeout(task);
break;
default:
log.warn("Unknown task type: {}", task.getType());
}
}
private void cancelOrder(DelayedProducer.DelayedTask task) {
String orderId = (String) task.getPayload().get("orderId");
log.info("Cancelling order: {}", orderId);
// 取消订单逻辑
}
private void handlePaymentTimeout(DelayedProducer.DelayedTask task) {
String paymentId = (String) task.getPayload().get("paymentId");
log.info("Payment timeout: {}", paymentId);
// 超时处理逻辑
}
}
测试:
@SpringBootTest
public class DelayedModeTest {
@Autowired
private DelayedProducer producer;
@Test
public void testDelayed() throws InterruptedException {
log.info("Start time: {}", LocalDateTime.now());
producer.sendDelayed("Message with 5s delay", 5000);
producer.sendDelayed("Message with 10s delay", 10000);
// 等待消费者处理
Thread.sleep(15000);
}
@Test
public void testDelayedTask() throws InterruptedException {
DelayedProducer.DelayedTask task = new DelayedProducer.DelayedTask(
UUID.randomUUID().toString(),
"order.cancel",
8000,
Map.of("orderId", "12345", "userId", 67890)
);
producer.sendDelayedObject(task);
Thread.sleep(10000);
}
}
6.4 备份交换机 (Alternate Exchange)
当消息无法路由到任何队列时,可发送到备份交换机处理。
@Configuration
public class AlternateExchangeConfig {
public static final String MAIN_EXCHANGE = "main.exchange";
public static final String ALTERNATE_EXCHANGE = "alternate.exchange";
public static final String UNROUTABLE_QUEUE = "unroutable.queue";
@Bean
public DirectExchange alternateExchange() {
return ExchangeBuilder.directExchange(ALTERNATE_EXCHANGE)
.durable(true)
.build();
}
@Bean
public Queue unroutableQueue() {
return QueueBuilder.durable(UNROUTABLE_QUEUE).build();
}
@Bean
public Binding alternateBinding() {
return BindingBuilder.bind(unroutableQueue())
.to(alternateExchange())
.with("unroutable");
}
@Bean
public DirectExchange mainExchange() {
Map<String, Object> args = new HashMap<>();
args.put("alternate-exchange", ALTERNATE_EXCHANGE); // 设置备份交换机
return ExchangeBuilder.directExchange(MAIN_EXCHANGE)
.durable(true)
.withArguments(args)
.build();
}
// 主队列
@Bean
public Queue mainQueue() {
return QueueBuilder.durable("main.queue").build();
}
@Bean
public Binding mainBinding() {
return BindingBuilder.bind(mainQueue())
.to(mainExchange())
.with("valid.key");
}
}
消费者:
@Component
public class UnroutableConsumer {
@RabbitListener(queues = AlternateExchangeConfig.UNROUTABLE_QUEUE)
public void handleUnroutable(Message message) {
String msg = new String(message.getBody(), StandardCharsets.UTF_8);
String originalRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
log.warn("Received unroutable message, original routingKey: {}, content: {}",
originalRoutingKey, msg);
// 记录并告警
alertUnroutableMessage(msg, originalRoutingKey);
}
private void alertUnroutableMessage(String msg, String routingKey) {
// 发送告警
}
}
七、性能优化与实践总结
7.1 连接管理优化
配置优化:
spring:
rabbitmq:
# 连接池配置
cache:
channel:
size: 50 # 缓存 Channel 数量
checkout-timeout: 10000 # 获取 Channel 超时
connection:
mode: channel # 缓存模式
size: 10 # 缓存连接数
# 连接超时
connection-timeout: 15000
# 心跳超时
requested-heartbeat: 60
代码层面优化:
@Configuration
public class OptimizedConfig {
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 使用 JSON 序列化
template.setMessageConverter(new Jackson2JsonMessageConverter());
// 开启批量发送
template.setConfirmCallback((correlationData, ack, cause) -> {
// 批量确认处理
});
return template;
}
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 并发消费者配置
factory.setConcurrentConsumers(5);
factory.setMaxConcurrentConsumers(20);
// 预取数量
factory.setPrefetchCount(10);
// 批量消费
factory.setBatchListener(true);
factory.setBatchSize(100);
factory.setReceiveTimeout(3000L);
// 消息转换器
factory.setMessageConverter(new Jackson2JsonMessageConverter());
// 异常处理
factory.setErrorHandler(t -> {
log.error("Consumer error", t);
});
return factory;
}
}
7.2 批量处理优化
批量生产者:
@Component
public class BatchProducer {
@Autowired
private RabbitTemplate rabbitTemplate;
private final List<Object> batch = new ArrayList<>();
private final int BATCH_SIZE = 100;
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
@PostConstruct
public void init() {
// 定时批量发送(每 5 秒发送一次)
scheduler.scheduleAtFixedRate(this::flush, 5, 5, TimeUnit.SECONDS);
}
public void addToBatch(Object message) {
synchronized (batch) {
batch.add(message);
if (batch.size() >= BATCH_SIZE) {
flush();
}
}
}
private void flush() {
List<Object> toSend;
synchronized (batch) {
if (batch.isEmpty()) return;
toSend = new ArrayList<>(batch);
batch.clear();
}
// 批量发送
rabbitTemplate.invoke(operations -> {
for (Object msg : toSend) {
operations.convertAndSend("batch.exchange", "batch.key", msg);
}
return null;
});
log.info("Flushed {} messages", toSend.size());
}
}
批量消费者:
@Component
@Slf4j
public class BatchConsumer {
@RabbitListener(queues = "batch.queue", containerFactory = "rabbitListenerContainerFactory")
public void receiveBatch(List<String> messages) {
log.info("Received batch of {} messages", messages.size());
// 批量处理(如批量插入数据库)
batchInsertToDatabase(messages);
}
private void batchInsertToDatabase(List<String> messages) {
// JDBC batch insert
// 或 Redis pipeline
}
}
7.3 监控告警配置
Spring Boot Actuator 配置:
management:
endpoints:
web:
exposure:
include: health,info,metrics,rabbitmq
metrics:
export:
prometheus:
enabled: true
endpoint:
rabbitmq:
enabled: true
自定义监控指标:
@Component
public class RabbitMQMonitor {
@Autowired
private RabbitManagementTemplate managementTemplate;
private final MeterRegistry meterRegistry;
public RabbitMQMonitor(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
}
@Scheduled(fixedDelay = 60000) // 每分钟采集一次
public void collectMetrics() {
// 获取所有队列
List<QueueInfo> queues = managementTemplate.getQueues();
for (QueueInfo queue : queues) {
// 就绪消息数
meterRegistry.gauge("rabbitmq.queue.messages.ready",
Tags.of("queue", queue.getName()),
queue, QueueInfo::getMessagesReady);
// 未确认消息数
meterRegistry.gauge("rabbitmq.queue.messages.unacked",
Tags.of("queue", queue.getName()),
queue, QueueInfo::getMessagesUnacknowledged);
// 消费者数量
meterRegistry.gauge("rabbitmq.queue.consumers",
Tags.of("queue", queue.getName()),
queue, QueueInfo::getConsumers);
}
// 监控堆积队列
queues.stream()
.filter(q -> q.getMessagesReady() > 1000)
.forEach(q -> {
log.warn("Queue {} is backed up: {} messages",
q.getName(), q.getMessagesReady());
// 发送告警
alertQueueBacklog(q);
});
}
private void alertQueueBacklog(QueueInfo queue) {
// 发送邮件、钉钉、企业微信通知
}
}
7.4 集群与高可用选择
仲裁队列 (Quorum Queues) 配置
@Configuration
public class QuorumQueueConfig {
@Bean
public Queue quorumQueue() {
return QueueBuilder.durable("quorum.queue")
.withArgument("x-queue-type", "quorum") // 仲裁队列
.withArgument("x-quorum-initial-group-size", 3) // 初始集群大小
.withArgument("x-max-length", 1000000) // 最大长度
.withArgument("x-overflow", "reject-publish") // 溢出策略
.withArgument("x-delivery-limit", 5) // 最大投递次数
.build();
}
@Bean
public DirectExchange quorumExchange() {
return ExchangeBuilder.directExchange("quorum.exchange")
.durable(true)
.build();
}
@Bean
public Binding quorumBinding() {
return BindingBuilder.bind(quorumQueue())
.to(quorumExchange())
.with("quorum.key");
}
}
多租户配置
@Configuration
public class MultiVHostConfig {
@Bean
@Primary
public ConnectionFactory defaultConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory("localhost");
factory.setVirtualHost("/");
factory.setUsername("guest");
factory.setPassword("guest");
return factory;
}
@Bean
public ConnectionFactory orderVHostConnectionFactory() {
CachingConnectionFactory factory = new CachingConnectionFactory("localhost");
factory.setVirtualHost("/order");
factory.setUsername("order_user");
factory.setPassword("order_pass");
return factory;
}
@Bean(name = "orderRabbitTemplate")
public RabbitTemplate orderRabbitTemplate() {
return new RabbitTemplate(orderVHostConnectionFactory());
}
}
7.5 模式选择指南
| 业务场景 | 推荐模式 | 交换机类型 | 说明 |
|---|---|---|---|
| 点对点通信 | 简单模式 | Default Exchange | 简单直接,一个生产者对应一个消费者 |
| 任务分发/负载均衡 | 工作队列 | Default Exchange | 多个消费者处理同一队列任务 |
| 广播通知 | 发布订阅 | Fanout | 所有消费者收到相同消息 |
| 日志分级 | 路由模式 | Direct | 按级别分发到不同队列 |
| 复杂路由 | 主题模式 | Topic | 通配符匹配,灵活过滤 |
| 远程调用 | RPC模式 | Direct + ReplyTo | 同步等待返回结果 |
| 定时任务 | 延迟队列 | x-delayed-message | 需要延迟处理的消息 |
| 异常处理 | 死信队列 | Direct + DLX | 处理失败消息 |
| 强一致性 | 仲裁队列 | Quorum | 数据不丢不重 |
| 高吞吐 | 惰性队列 | Lazy | 大量消息堆积 |
7.6 常见问题与解决方案
消息积压处理
@Component
public class BacklogHandler {
@Autowired
private RabbitTemplate rabbitTemplate;
// 动态增加消费者
public void scaleUp(String queueName, int additionalConsumers) {
// 实际需通过管理 API 或动态注册监听器
log.info("Scaling up queue {} by {} consumers", queueName, additionalConsumers);
}
// 临时将消息转发到新队列
public void redirectBacklog(String sourceQueue, String targetQueue, int count) {
// 从源队列拉取消息
for (int i = 0; i < count; i++) {
Message message = rabbitTemplate.receive(sourceQueue, 1000);
if (message != null) {
// 转发到新队列
rabbitTemplate.send(targetQueue, message);
}
}
}
// 批量重新发布(重新入队)
public void republish(String queueName) {
// 获取队列消息数量
int messageCount = getMessageCount(queueName);
for (int i = 0; i < messageCount; i++) {
Message message = rabbitTemplate.receive(queueName, 100);
if (message != null) {
// 修改消息属性后重新发送
rabbitTemplate.send(queueName, message);
}
}
}
private int getMessageCount(String queueName) {
// 通过管理 API 获取
return 0;
}
}
消息重复消费
@Component
public class IdempotentConsumer {
@Autowired
private StringRedisTemplate redisTemplate;
@RabbitListener(queues = "idempotent.queue")
public void process(Message message) {
String messageId = message.getMessageProperties().getMessageId();
if (messageId == null) {
messageId = UUID.randomUUID().toString();
}
// 使用 Redis 做幂等判断
String key = "msg:" + messageId;
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "processed", Duration.ofHours(1));
if (Boolean.TRUE.equals(success)) {
// 首次消费
String content = new String(message.getBody(), StandardCharsets.UTF_8);
businessProcess(content);
log.info("Processed message: {}", messageId);
} else {
// 重复消息,直接 ACK 但不处理
log.info("Duplicate message ignored: {}", messageId);
}
}
private void businessProcess(String content) {
// 业务逻辑
}
}
八、生产环境实践清单
8.1 部署配置
- RabbitMQ 版本使用 3.9+,推荐 3.12+
- 集群节点数至少 3 个(仲裁队列要求)
- 磁盘监控:可用空间 > 20%
- 内存监控:使用率 < 70%
- 文件描述符:ulimit -n 至少 65536
- 开启防火墙,仅暴露 5672(AMQP) 和 15672(管理)
8.2 消息可靠性
- 生产者开启 Confirm 模式
- 生产者开启 Return 回调
- 消息设置持久化(PERSISTENT)
- 队列设置 durable=true
- 消费者开启手动 ACK
- 配置死信队列处理异常消息
- 重要业务设置消息幂等处理
8.3 性能优化
- 合理设置 prefetch 值(根据业务处理时间)
- 使用连接池,复用 Connection 和 Channel
- 批量发送/消费(高频小消息)
- 消息大小控制在 4MB 以内(过大可压缩或存 OSS)
- 避免大量消息堆积,设置队列 max-length
- 使用惰性队列处理长堆积场景
8.4 监控告警
- 队列堆积告警(> 阈值)
- 消费者离线告警(消费者数量 < 预期)
- 节点内存/磁盘告警
- 消息处理延迟告警
- 死信队列增长告警
8.5 安全加固
- 生产环境禁用 guest 用户
- 每个应用独立 VHost 和用户
- 最小权限原则(仅授予必要权限)
- TLS/SSL 加密传输
- 管理界面绑定内网 IP 或使用 VPN