RabbitMQ详细教程(从入门到实战,覆盖基础与高级特性)
第一章:RabbitMQ基础认知
1.1 什么是RabbitMQ
RabbitMQ是基于AMQP(高级消息队列协议)开发的开源消息中间件,由Erlang语言编写,具备高可用、高可靠、功能丰富、易扩展等特点,广泛应用于微服务架构、分布式系统中,核心作用是实现系统间的异步通信、解耦、流量削峰和数据缓冲。
与其他消息队列(如Kafka、RocketMQ)相比,RabbitMQ的优势在于路由机制灵活、支持多种消息模式、延迟队列实现简单,且拥有完善的管理界面,适合中小规模场景及对消息可靠性要求较高的业务(如订单、支付、通知等)。
1.2 核心概念(必掌握)
RabbitMQ的核心组件协同工作,构成完整的消息流转体系,需重点掌握以下组件及关系:
-
生产者(Producer):发送消息的应用程序,负责将业务数据封装为消息,通过RabbitMQ客户端发送到RabbitMQ服务器(交换机),无需关心消息的后续路由和消费逻辑。
-
消费者(Consumer):接收并处理消息的应用程序,通过监听指定队列,获取消息后执行对应的业务逻辑(如订单处理、通知推送),处理完成后向RabbitMQ发送确认信号。
-
交换机(Exchange):消息的"路由器",接收生产者发送的消息,根据绑定规则(Routing Key/Headers)将消息路由到对应的队列中。交换机本身不存储消息,若消息无法匹配任何绑定规则,会被丢弃(或返回给生产者,取决于配置)。
-
队列(Queue):消息的"存储容器",接收交换机路由的消息,等待消费者消费。队列是消息的最终落脚点,支持持久化、过期时间、最大长度等配置,确保消息的可靠存储。
-
绑定(Binding):建立交换机与队列之间的关联,同时指定路由规则(如Routing Key),告诉交换机"如何将消息路由到队列"。一个交换机可以绑定多个队列,一个队列也可以绑定多个交换机。
-
虚拟主机(Virtual Host):RabbitMQ的"隔离环境",用于实现多租户隔离,不同虚拟主机之间的交换机、队列、用户完全独立,避免资源冲突。默认虚拟主机为"/",适合单机开发测试;生产环境建议为不同业务创建独立虚拟主机。
-
连接(Connection):生产者/消费者与RabbitMQ服务器之间的TCP连接,是消息传输的基础。TCP连接建立耗时,通常会复用连接以提升性能。
-
信道(Channel):在TCP连接内部建立的轻量级连接,是RabbitMQ推荐的通信方式。一个TCP连接可以包含多个信道,避免频繁建立/关闭TCP连接带来的性能损耗,所有消息操作(发送、接收、确认)均通过信道完成。
1.3 消息流转核心流程
RabbitMQ的消息流转遵循固定逻辑,理解该流程是掌握RabbitMQ的关键,具体步骤如下:
-
生产者通过信道连接RabbitMQ服务器,发送消息时指定交换机、路由键(Routing Key)和消息内容;
-
交换机接收消息,根据自身类型和绑定规则,将消息路由到一个或多个匹配的队列;
-
队列存储消息(若配置持久化,则写入磁盘;否则仅存于内存),等待消费者监听;
-
消费者通过信道监听指定队列,获取消息并执行业务逻辑;
-
消费者处理完成后,向RabbitMQ发送ACK(确认信号),RabbitMQ收到ACK后,从队列中删除该消息;
-
若消费者未发送ACK(如服务宕机),RabbitMQ会将消息重新放回队列,等待其他消费者消费(或重试)。
第二章:RabbitMQ环境搭建(多平台全覆盖)
RabbitMQ基于Erlang语言开发,因此安装前需先安装Erlang环境,且两者版本需严格兼容(参考RabbitMQ官方兼容表:www.rabbitmq.com/which-erlan... Solutions仓库安装适配版本。以下覆盖Windows、macOS、Ubuntu、Docker四大主流环境,按需选择。
2.1 Windows环境安装(适合开发测试)
2.1.1 安装前准备
-
系统要求:Windows 7/8/10/11(64位),推荐Windows 10+;
-
必备依赖:Erlang(版本需与RabbitMQ兼容);
-
下载链接:
-
Erlang:www.erlang.org/downloads
-
RabbitMQ:www.rabbitmq.com/download.ht...
-
2.1.2 安装Erlang
-
下载对应系统的Erlang安装包(如otp_win64_24.1.7.exe);
-
右键以"管理员身份运行"安装程序,一路点击"Next"(建议默认路径,便于后续配置环境变量);
-
配置环境变量:
-
右键"此电脑"→属性→高级系统设置→环境变量;
-
在系统变量中找到"Path"→编辑→新建,添加Erlang的bin目录(如C:\Program Files\erl{version}\bin);
-
-
验证安装:打开命令提示符(Win+R→cmd),输入
erl -version,显示版本信息即安装成功。
2.1.3 安装RabbitMQ
-
下载Windows安装包(.exe或.zip格式),推荐.exe安装方式;
-
以管理员身份运行安装程序,按提示完成安装(建议默认路径);
-
安装完成后,RabbitMQ会自动注册为Windows服务并启动;
-
(可选)配置环境变量:将RabbitMQ的sbin目录(如C:\Program Files\RabbitMQ Server\rabbitmq_server-{版本}\sbin)添加到Path,便于命令行操作。
2.1.4 启用管理界面(关键)
RabbitMQ提供可视化管理界面,便于查看队列、交换机、消息等信息,步骤如下:
-
打开命令提示符(管理员身份),输入命令:
rabbitmq-plugins enable rabbitmq_management,输出"started 3 plugins"表示启用成功; -
重启RabbitMQ服务:输入
net stop RabbitMQ停止服务,再输入net start RabbitMQ启动服务(或通过服务管理器(Win+R→services.msc)重启); -
访问管理界面:打开浏览器,输入http://localhost:15672,使用默认账号guest/guest登录(默认仅允许本地访问)。
2.1.5 常见问题及解决方案
| 问题 | 解决方案 |
|---|---|
| 服务启动失败 | 检查Erlang和RabbitMQ版本兼容性,参考官方版本矩阵;确认安装路径为纯英文(无中文、空格)。 |
| 管理界面无法访问 | 检查防火墙是否开放15672端口;重启RabbitMQ服务;确认管理插件已成功启用。 |
| 命令行找不到erl/rabbitmqctl | 确认Erlang和RabbitMQ的环境变量配置正确,重启命令提示符。 |
2.2 macOS环境安装
2.2.1 Homebrew安装方式(推荐,简洁高效)
-
安装Homebrew(如已安装,跳过):打开终端,输入命令:
/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)",验证安装:brew --version; -
安装RabbitMQ:终端输入
brew update更新Homebrew,再输入brew install rabbitmq,Homebrew会自动安装依赖的Erlang,无需手动配置版本; -
启动服务:
-
后台启动(开机自启):
brew services start rabbitmq; -
临时启动(关闭终端即停止):
rabbitmq-server;
-
-
验证状态:输入
brew services list,查看RabbitMQ状态为"started"即启动成功; -
启用管理界面:输入
rabbitmq-plugins enable rabbitmq_management,浏览器访问http://localhost:15672,使用guest/guest登录。
2.2.2 Docker安装方式(轻量不污染系统)
-
安装Docker Desktop for Mac(官网下载:www.docker.com/products/do...
-
终端输入命令,运行RabbitMQ容器(自带管理界面):
docker run -d --hostname my-rabbit --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management;
2.3 Ubuntu环境安装(适合生产环境)
-
安装Erlang:
-
添加Erlang仓库:
wget -O - https://packages.erlang-solutions.com/ubuntu/erlang_solutions.asc | sudo apt-key add -; -
添加源:
echo "deb https://packages.erlang-solutions.com/ubuntu focal contrib" | sudo tee /etc/apt/sources.list.d/erlang.list(focal对应Ubuntu 20.04,其他版本替换为对应代号); -
安装Erlang:
sudo apt update && sudo apt install erlang -y;
-
-
安装RabbitMQ:
-
添加RabbitMQ仓库:
curl -s https://packagecloud.io/install/repositories/rabbitmq/rabbitmq-server/script.deb.sh | sudo bash; -
安装RabbitMQ:
sudo apt install rabbitmq-server -y; -
启动服务:
sudo systemctl start rabbitmq-server; -
设置开机自启:
sudo systemctl enable rabbitmq-server; -
查看状态:
sudo systemctl status rabbitmq-server;
-
-
启用管理界面:
sudo rabbitmq-plugins enable rabbitmq_management; -
配置远程访问(生产必备):默认guest账号仅允许本地访问,需创建新用户并授权:
-
创建用户:
sudo rabbitmqctl add_user admin 123456(admin为用户名,123456为密码); -
授权管理员权限:
sudo rabbitmqctl set_user_tags admin administrator; -
设置虚拟主机权限:
sudo rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"; -
远程访问:浏览器输入http://服务器IP:15672,使用admin/123456登录。
-
2.4 Docker安装(跨平台通用,推荐生产测试)
Docker安装方式无需单独配置Erlang,步骤简单,适合快速部署,具体如下:
-
安装Docker(已安装跳过):参考Docker官方文档,根据系统类型安装;
-
拉取RabbitMQ镜像(带管理界面):
docker pull rabbitmq:management; -
运行容器:
docker run -d \ --name rabbitmq \ -p 5672:5672 \ # 消息通信端口 -p 15672:15672 \ # 管理界面端口 -e RABBITMQ_DEFAULT_USER=admin \ # 自定义用户名 -e RABBITMQ_DEFAULT_PASS=123456 \ # 自定义密码 --restart=always \ # 开机自启 rabbitmq:management -
验证:浏览器访问http://localhost:15672(本地)或http://服务器IP:15672(远程),使用设置的用户名密码登录。
第三章:RabbitMQ核心组件详解
3.1 交换机(Exchange)详解
交换机是RabbitMQ的核心路由组件,根据类型不同,路由规则也不同,RabbitMQ支持4种核心交换机类型,覆盖绝大多数业务场景,其中Headers交换机为补充类型,使用频率较低。
3.1.1 交换机类型及特点(重点)
| 交换机类型 | 路由规则 | 特点 | 典型场景 |
|---|---|---|---|
| Direct(直连交换机) | 根据消息的Routing Key与绑定的Routing Key完全匹配,将消息路由到对应队列 | 简单直接、高效,是RabbitMQ默认交换机类型(不声明交换机时,默认使用AMQP default直连交换机) | 按日志级别(info/error)分队列、按用户ID分发订单消息(如user.123路由到用户123的专属队列) |
| Fanout(扇形/广播交换机) | 忽略Routing Key,将消息广播到所有绑定的队列 | 转发速度最快(无匹配逻辑,直接广播),无需关注路由键 | 系统通知(如运维公告推送给所有服务实例)、电商订单创建后,同时通知库存、物流、营销等多个系统 |
| Topic(主题交换机) | 支持Routing Key通配符匹配,用"."分隔单词,*匹配1个单词,#匹配1个或多个单词 | 灵活度高,支持复杂路由规则,适用多维度消息分发 | 日志分类(log.error.#接收所有错误日志)、微服务按模块订阅消息(如user.*接收用户相关所有消息) |
| Headers(头交换机) | 不使用Routing Key,根据消息的头部属性(Key-Value键值对)匹配,通过x-match指定匹配规则(all=所有匹配,any=任意匹配) | 规则灵活,支持多属性筛选,但性能比Direct/Topic低,使用复杂度高 | 需多条件筛选的特殊场景(如消息需同时满足type=order且priority=high才路由) |
3.1.2 交换机的核心属性
-
Durable(持久化):true表示交换机持久化,RabbitMQ宕机重启后交换机依然存在;false表示非持久化,宕机后丢失(建议生产环境设为true)。
-
Auto Delete(自动删除):true表示当最后一个绑定关系解除后,交换机自动删除;false表示不自动删除(默认false)。
-
Internal(内部交换机):true表示交换机仅接收来自其他交换机的消息,不接收生产者直接发送的消息;false表示允许生产者直接发送消息(默认false)。
3.2 队列(Queue)详解
队列是消息的存储载体,所有消息最终都会路由到队列中,等待消费者消费,队列的配置直接影响消息的可靠性和处理效率。
3.2.1 队列的核心属性
-
Durable(持久化):true表示队列持久化,宕机重启后队列及队列中的持久化消息不会丢失;false表示非持久化,宕机后队列和消息全部丢失(生产环境建议设为true)。
-
Auto Delete(自动删除):true表示当最后一个消费者取消订阅后,队列自动删除;false表示不自动删除(默认false)。
-
Exclusive(独占队列):true表示队列仅允许创建它的连接访问,连接关闭后队列自动删除;false表示允许其他连接访问(默认false,适合临时队列)。
-
Arguments(参数):用于配置队列的高级特性,如TTL(消息过期时间)、死信交换机、最大长度等,后续高级特性章节详细讲解。
3.2.2 队列与交换机的绑定
绑定是连接交换机和队列的桥梁,绑定命令需指定3个核心参数:交换机名称、队列名称、路由键(Routing Key/Headers),不同交换机类型的绑定逻辑不同:
-
Direct交换机:绑定的Routing Key必须与消息的Routing Key完全一致,才能路由消息;
-
Fanout交换机:绑定无需指定Routing Key(或指定任意值),消息会广播到所有绑定队列;
-
Topic交换机:绑定的Routing Key支持通配符,消息的Routing Key与绑定的Routing Key匹配即可路由;
-
Headers交换机:绑定需指定一组键值对和匹配规则(x-match),消息的Headers与绑定的键值对匹配即可路由。
3.3 消息(Message)详解
消息是生产者发送给消费者的数据载体,由消息头(Headers)和消息体(Body)两部分组成。
-
消息头(Headers):包含消息的元数据,如消息ID、优先级、过期时间(TTL)、持久化标识等,可自定义键值对,用于Headers交换机路由或业务逻辑判断。
-
消息体(Body):实际的业务数据,通常为JSON格式(如订单信息、通知内容),也可以是字符串、二进制数据等,由生产者和消费者约定格式。
消息的持久化:需同时满足3个条件,消息才能在RabbitMQ宕机后不丢失:① 交换机持久化(Durable=true);② 队列持久化(Durable=true);③ 消息本身持久化(发送消息时指定delivery_mode=2)。
第四章:RabbitMQ常用工作模式(实战重点)
RabbitMQ提供多种工作模式,适配不同的业务场景,以下是最常用的5种模式,结合代码示例(Java+SpringBoot)讲解,便于快速落地。
4.1 环境准备(SpringBoot整合RabbitMQ)
后续所有模式均基于SpringBoot整合RabbitMQ实现,先完成基础配置:
4.1.1 引入依赖(pom.xml)
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
4.1.2 核心配置(application.yml)
yaml
spring:
rabbitmq:
host: localhost # RabbitMQ服务器地址(远程改为服务器IP)
port: 5672 # 消息通信端口
username: admin # 用户名(默认guest,远程需配置)
password: 123456 # 密码
virtual-host: / # 虚拟主机
connection-timeout: 3000ms # 连接超时时间
# 生产者确认配置(后续可靠性投递会详细讲解)
publisher-confirm-type: correlated
publisher-returns: true
# 消费者配置
listener:
simple:
acknowledge-mode: manual # 手动ACK,避免消息丢失
concurrency: 5 # 消费者核心线程数
max-concurrency: 20 # 消费者最大线程数
prefetch: 10 # 每次从队列拉取10条消息,避免过度拉取导致堆积
4.2 简单模式(Hello World)
4.2.1 模式说明
最简单的模式,一个生产者、一个消费者、一个队列,生产者直接将消息发送到队列,消费者监听队列并消费消息,无需交换机(使用RabbitMQ默认的直连交换机)。
适用场景:简单的异步通信,如单一服务的消息通知、日志记录。
4.2.2 代码实现
1. 队列配置(RabbitMQConfig.java)
java
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// 队列名称
public static final String SIMPLE_QUEUE = "simple_queue";
// 声明队列(持久化)
@Bean
public Queue simpleQueue() {
return QueueBuilder.durable(SIMPLE_QUEUE)
.autoDelete(false)
.exclusive(false)
.build();
}
}
2. 生产者(SimpleProducer.java)
java
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class SimpleProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送消息
public void sendMessage(String message) {
// 参数:队列名称、消息内容
rabbitTemplate.convertAndSend(RabbitMQConfig.SIMPLE_QUEUE, message);
System.out.println("生产者发送消息:" + message);
}
}
3. 消费者(SimpleConsumer.java)
java
import com.rabbitmq.client.Channel;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
import java.io.IOException;
@Component
public class SimpleConsumer {
// 监听指定队列
@RabbitListener(queues = RabbitMQConfig.SIMPLE_QUEUE)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
// 处理业务逻辑
System.out.println("消费者接收消息:" + message);
// 手动ACK:确认消息消费成功,参数1:消息标识,参数2:是否批量确认
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 消费失败,拒绝消息并重新入队(或转发到死信队列)
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
4. 测试(Controller)
java
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
@RestController
@RequestMapping("/rabbitmq")
public class RabbitMQController {
@Resource
private SimpleProducer simpleProducer;
@GetMapping("/simple/send/{message}")
public String sendSimpleMessage(@PathVariable String message) {
simpleProducer.sendMessage(message);
return "消息发送成功!";
}
}
4.3 工作队列模式(Work Queue)
4.3.1 模式说明
一个生产者、多个消费者、一个队列,生产者发送消息到队列,多个消费者共同监听该队列,消息会被平均分配给每个消费者(轮询机制),避免单个消费者压力过大。
适用场景:任务分发、负载均衡,如订单处理、报表生成等耗时任务,由多个消费者并行处理。
关键优化:通过prefetch参数设置消费者每次拉取的消息数量,避免消息分配不均(如某个消费者处理速度慢,却分配到大量消息)。
4.3.2 代码实现
队列配置与简单模式一致(可复用simple_queue),只需增加多个消费者,修改消费者监听逻辑即可:
java
// 消费者1
@Component
public class WorkConsumer1 {
@RabbitListener(queues = RabbitMQConfig.SIMPLE_QUEUE)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
// 模拟耗时处理(1秒)
Thread.sleep(1000);
System.out.println("消费者1接收消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
// 消费者2
@Component
public class WorkConsumer2 {
@RabbitListener(queues = RabbitMQConfig.SIMPLE_QUEUE)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
// 模拟耗时处理(2秒)
Thread.sleep(2000);
System.out.println("消费者2接收消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
测试:调用/simple/send接口发送多条消息,观察两个消费者的消息分配情况,会发现消息按轮询方式分配,且消费者1处理速度快,会先完成自身消息的消费。
4.4 发布/订阅模式(Publish/Subscribe)
4.4.1 模式说明
一个生产者、多个消费者、一个Fanout交换机、多个队列,生产者将消息发送到Fanout交换机,交换机将消息广播到所有绑定的队列,每个队列的消费者都能接收并消费同一条消息。
适用场景:广播通知、日志收集,如系统上线通知推送给所有服务实例、用户操作日志同时存储到多个日志队列。
4.4.2 代码实现
1. 队列、交换机、绑定配置
java
@Configuration
public class RabbitMQConfig {
// Fanout交换机名称
public static final String FANOUT_EXCHANGE = "fanout_exchange";
// 队列1
public static final String FANOUT_QUEUE1 = "fanout_queue1";
// 队列2
public static final String FANOUT_QUEUE2 = "fanout_queue2";
// 声明Fanout交换机
@Bean
public FanoutExchange fanoutExchange() {
return FanoutExchangeBuilder.durable(FANOUT_EXCHANGE)
.autoDelete(false)
.build();
}
// 声明队列1
@Bean
public Queue fanoutQueue1() {
return QueueBuilder.durable(FANOUT_QUEUE1).build();
}
// 声明队列2
@Bean
public Queue fanoutQueue2() {
return QueueBuilder.durable(FANOUT_QUEUE2).build();
}
// 绑定队列1到Fanout交换机
@Bean
public Binding fanoutBinding1() {
return BindingBuilder.bind(fanoutQueue1()).to(fanoutExchange());
}
// 绑定队列2到Fanout交换机
@Bean
public Binding fanoutBinding2() {
return BindingBuilder.bind(fanoutQueue2()).to(fanoutExchange());
}
}
2. 生产者(FanoutProducer.java)
java
@Component
public class FanoutProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送消息到Fanout交换机(无需指定Routing Key)
public void sendMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.FANOUT_EXCHANGE, "", message);
System.out.println("生产者发送广播消息:" + message);
}
}
3. 消费者(FanoutConsumer1、FanoutConsumer2)
java
// 消费者1(监听队列1)
@Component
public class FanoutConsumer1 {
@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE1)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者1(队列1)接收广播消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
// 消费者2(监听队列2)
@Component
public class FanoutConsumer2 {
@RabbitListener(queues = RabbitMQConfig.FANOUT_QUEUE2)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者2(队列2)接收广播消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
4.5 路由模式(Routing)
4.5.1 模式说明
一个生产者、多个消费者、一个Direct交换机、多个队列,生产者发送消息时指定Routing Key,交换机根据Routing Key与队列绑定的Routing Key完全匹配,将消息路由到对应的队列,只有匹配的消费者才能接收消息。
适用场景:精准路由,如按消息类型(订单创建、订单支付)分发消息,不同类型的消息由不同消费者处理。
4.5.2 代码实现
1. 配置(新增Direct交换机、队列及绑定)
java
@Configuration
public class RabbitMQConfig {
// Direct交换机名称
public static final String DIRECT_EXCHANGE = "direct_exchange";
// 队列1(接收order.create类型消息)
public static final String DIRECT_QUEUE_CREATE = "direct_queue_create";
// 队列2(接收order.pay类型消息)
public static final String DIRECT_QUEUE_PAY = "direct_queue_pay";
// Routing Key
public static final String ROUTING_KEY_CREATE = "order.create";
public static final String ROUTING_KEY_PAY = "order.pay";
// 声明Direct交换机
@Bean
public DirectExchange directExchange() {
return DirectExchangeBuilder.durable(DIRECT_EXCHANGE).build();
}
// 声明队列
@Bean
public Queue directQueueCreate() {
return QueueBuilder.durable(DIRECT_QUEUE_CREATE).build();
}
@Bean
public Queue directQueuePay() {
return QueueBuilder.durable(DIRECT_QUEUE_PAY).build();
}
// 绑定队列1到Direct交换机,指定Routing Key为order.create
@Bean
public Binding directBindingCreate() {
return BindingBuilder.bind(directQueueCreate())
.to(directExchange())
.with(ROUTING_KEY_CREATE);
}
// 绑定队列2到Direct交换机,指定Routing Key为order.pay
@Bean
public Binding directBindingPay() {
return BindingBuilder.bind(directQueuePay())
.to(directExchange())
.with(ROUTING_KEY_PAY);
}
}
2. 生产者(DirectProducer.java)
java
@Component
public class DirectProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送订单创建消息
public void sendCreateMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY_CREATE, message);
System.out.println("生产者发送订单创建消息:" + message);
}
// 发送订单支付消息
public void sendPayMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.DIRECT_EXCHANGE, RabbitMQConfig.ROUTING_KEY_PAY, message);
System.out.println("生产者发送订单支付消息:" + message);
}
}
3. 消费者(DirectConsumerCreate、DirectConsumerPay)
java
// 消费者1:处理订单创建消息
@Component
public class DirectConsumerCreate {
@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_CREATE)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者处理订单创建消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
// 消费者2:处理订单支付消息
@Component
public class DirectConsumerPay {
@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_PAY)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者处理订单支付消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
4.6 主题模式(Topic)
4.6.1 模式说明
基于Direct模式的扩展,使用Topic交换机,支持Routing Key通配符匹配(*匹配1个单词,#匹配1个或多个单词),实现更灵活的路由规则,是实际开发中最常用的模式。
适用场景:复杂的消息分发,如微服务中按"业务模块.操作类型"分发消息(user.login、user.register、order.create),下游服务可订阅指定模块的所有消息。
4.6.2 代码实现
1. 配置(新增Topic交换机、队列及绑定)
java
@Configuration
public class RabbitMQConfig {
// Topic交换机名称
public static final String TOPIC_EXCHANGE = "topic_exchange";
// 队列1(订阅user相关所有消息,匹配user.#)
public static final String TOPIC_QUEUE_USER = "topic_queue_user";
// 队列2(订阅order.create和order.pay消息,匹配order.*)
public static final String TOPIC_QUEUE_ORDER = "topic_queue_order";
// 声明Topic交换机
@Bean
public TopicExchange topicExchange() {
return TopicExchangeBuilder.durable(TOPIC_EXCHANGE).build();
}
// 声明队列
@Bean
public Queue topicQueueUser() {
return QueueBuilder.durable(TOPIC_QUEUE_USER).build();
}
@Bean
public Queue topicQueueOrder() {
return QueueBuilder.durable(TOPIC_QUEUE_ORDER).build();
}
// 绑定队列1:匹配user.#(所有user开头的消息)
@Bean
public Binding topicBindingUser() {
return BindingBuilder.bind(topicQueueUser())
.to(topicExchange())
.with("user.#");
}
// 绑定队列2:匹配order.*(order开头且后面跟1个单词的消息)
@Bean
public Binding topicBindingOrder() {
return BindingBuilder.bind(topicQueueOrder())
.to(topicExchange())
.with("order.*");
}
}
2. 生产者(TopicProducer.java)
java
@Component
public class TopicProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送user.login消息
public void sendUserLoginMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "user.login", message);
System.out.println("生产者发送user.login消息:" + message);
}
// 发送user.register消息
public void sendUserRegisterMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "user.register", message);
System.out.println("生产者发送user.register消息:" + message);
}
// 发送order.create消息
public void sendOrderCreateMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "order.create", message);
System.out.println("生产者发送order.create消息:" + message);
}
// 发送order.pay.success消息(无法匹配order.*)
public void sendOrderPaySuccessMessage(String message) {
rabbitTemplate.convertAndSend(RabbitMQConfig.TOPIC_EXCHANGE, "order.pay.success", message);
System.out.println("生产者发送order.pay.success消息:" + message);
}
}
3. 消费者(TopicConsumerUser、TopicConsumerOrder)
java
// 消费者1:处理user相关所有消息
@Component
public class TopicConsumerUser {
@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_USER)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者处理user相关消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
// 消费者2:处理order.*消息
@Component
public class TopicConsumerOrder {
@RabbitListener(queues = RabbitMQConfig.TOPIC_QUEUE_ORDER)
public void consumeMessage(String message, Channel channel, Message msg) throws IOException {
try {
System.out.println("消费者处理order.*消息:" + message);
channel.basicAck(msg.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
channel.basicNack(msg.getMessageProperties().getDeliveryTag(), false, true);
e.printStackTrace();
}
}
}
测试说明:发送order.pay.success消息时,由于绑定的Routing Key是order.*,无法匹配(*只能匹配1个单词),因此该消息会被丢弃(或返回给生产者)。
第五章:RabbitMQ高级特性(生产必备)
默认配置下,RabbitMQ存在消息丢失、重复消费、堆积等问题,无法直接适配生产环境。以下高级特性是保障消息可靠性、系统稳定性的核心,必须掌握。
5.1 消息可靠性投递(三端保障)
消息从生产者发送到消费者的整个链路中,任何一个环节出错都可能导致消息丢失,需从生产者、存储、消费者三端入手,实现消息零丢失。
5.1.1 生产者端:投递确认机制
确保生产者发送的消息能成功到达交换机,分为两种确认方式:同步确认(性能低)和异步确认(推荐,性能高)。
核心配置(已在第四章环境准备中配置,此处补充说明):
yaml
spring:
rabbitmq:
publisher-confirm-type: correlated # 异步确认(推荐),可选none(关闭)、simple(同步确认)
publisher-returns: true # 开启消息返回机制(交换机路由到队列失败时回调)
异步确认实现(完善RabbitTemplate配置,实现回调逻辑):
java
@Configuration
public class RabbitMQConfig {
// 注入RabbitTemplate,配置回调
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory);
// 1. 生产者确认回调(确认消息是否到达交换机)
rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
// correlationData:发送消息时携带的唯一标识(消息ID)
// ack:true=消息到达交换机,false=未到达
// cause:未到达时的失败原因
if (ack) {
// 消息成功投递到交换机,可记录日志(如消息ID、发送时间)
System.out.println("消息投递到交换机成功,消息ID:" + (correlationData != null ? correlationData.getId() : "无"));
} else {
// 消息投递失败,核心处理逻辑(生产环境需持久化到数据库,后续重试)
System.out.println("消息投递到交换机失败,原因:" + cause + ",消息ID:" + (correlationData != null ? correlationData.getId() : "无"));
// 示例:调用重试方法或入库补偿
// retrySendMessage(correlationData, cause);
}
});
// 2. 消息返回回调(交换机路由到队列失败时触发)
rabbitTemplate.setReturnsCallback(returnedMessage -> {
// returnedMessage:包含消息、交换机、路由键、失败码等信息
String messageContent = new String(returnedMessage.getMessage().getBody());
System.out.println("消息路由失败:" +
"交换机:" + returnedMessage.getExchange() +
",路由键:" + returnedMessage.getRoutingKey() +
",消息内容:" + messageContent +
",失败码:" + returnedMessage.getReplyCode() +
",失败原因:" + returnedMessage.getReplyText());
// 路由失败处理(如重新路由、入库记录)
});
// 3. 消息持久化配置(确保消息本身可持久化,delivery_mode=2)
rabbitTemplate.setMessageConverter(new Jackson2JsonMessageConverter());
return rabbitTemplate;
}
}
发送消息时携带CorrelationData(消息唯一标识),便于回调时定位消息:
java
@Component
public class ReliableProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送可靠消息,携带消息ID(可使用UUID生成)
public void sendReliableMessage(String exchange, String routingKey, Object message) {
// 生成唯一消息ID,用于回调确认和后续重试
String messageId = UUID.randomUUID().toString().replace("-", "");
CorrelationData correlationData = new CorrelationData(messageId);
try {
// 发送消息(消息会被Jackson2JsonMessageConverter序列化为JSON,默认持久化)
rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
System.out.println("消息发送成功(待确认),消息ID:" + messageId + ",消息内容:" + message);
} catch (Exception e) {
// 发送异常(如连接超时),直接处理失败逻辑
System.out.println("消息发送异常,消息ID:" + messageId + ",异常原因:" + e.getMessage());
e.printStackTrace();
}
}
}
5.1.2 存储端:交换机、队列、消息持久化
即使生产者确认消息到达交换机,若RabbitMQ宕机,未持久化的交换机、队列、消息会全部丢失,因此需开启三端持久化,三者缺一不可:
-
交换机持久化 :声明交换机时指定durable=true(第四章所有示例均已配置),确保宕机重启后交换机存在。
// 示例:Direct交换机持久化 @Bean public DirectExchange directExchange() { return DirectExchangeBuilder.durable(DIRECT_EXCHANGE) // durable=true .autoDelete(false) .build(); } -
队列持久化 :声明队列时指定durable=true,确保宕机重启后队列存在。
@Bean public Queue directQueueCreate() { return QueueBuilder.durable(DIRECT_QUEUE_CREATE) // durable=true .autoDelete(false) .exclusive(false) .build(); } -
消息持久化 :发送消息时指定delivery_mode=2(SpringBoot中,使用Jackson2JsonMessageConverter时默认持久化,无需手动设置)。 手动设置示例(适用于原生API或自定义消息):
// 手动构建消息,设置持久化 Message message = MessageBuilder .withBody("持久化消息内容".getBytes(StandardCharsets.UTF_8)) .setDeliveryMode(MessageDeliveryMode.PERSISTENT) // 持久化 .build(); rabbitTemplate.send(exchange, routingKey, message, correlationData);
注意:持久化会降低RabbitMQ性能(需写入磁盘),生产环境需根据业务需求权衡:核心业务(订单、支付)必须开启持久化,非核心业务(日志、通知)可关闭以提升性能。
5.1.3 消费者端:手动ACK机制
默认情况下,消费者获取消息后会自动ACK(确认消费),若消费者在处理消息时宕机,消息会被丢失(已ACK但未处理完成)。因此需开启手动ACK,确保消息处理完成后再确认。
- 核心配置(已在第四章环境准备中配置):
yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 手动ACK,可选auto(自动)、none(不确认)
concurrency: 5 # 消费者核心线程数
max-concurrency: 20 # 消费者最大线程数
prefetch: 10 # 每次拉取10条消息,避免过度拉取导致堆积
- 手动ACK实现(消费者代码完善):
java
@Component
public class ReliableConsumer {
// 监听队列,手动ACK
@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_CREATE)
public void consumeMessage(Message message, Channel channel) throws IOException {
// 消息标识(用于ACK/NACK)
long deliveryTag = message.getMessageProperties().getDeliveryTag();
// 消息内容(JSON格式,反序列化为实体类)
String messageContent = new String(message.getBody(), StandardCharsets.UTF_8);
try {
// 1. 执行业务逻辑(如订单创建、数据入库)
System.out.println("消费者处理消息,内容:" + messageContent);
// 模拟业务处理(实际开发中替换为真实业务逻辑)
// orderService.createOrder(JSON.parseObject(messageContent, OrderDTO.class));
// 2. 手动ACK:确认消息消费成功(参数2:是否批量确认,false=单条确认)
channel.basicAck(deliveryTag, false);
System.out.println("消息消费成功,已ACK,消息标识:" + deliveryTag);
} catch (Exception e) {
// 3. 消费失败处理:拒绝消息,根据业务需求选择是否重新入队
System.out.println("消息消费失败,消息标识:" + deliveryTag + ",异常原因:" + e.getMessage());
// basicNack参数说明:
// 参数1:deliveryTag:消息标识
// 参数2:multiple:是否批量拒绝
// 参数3:requeue:是否重新入队(true=重新入队,false=丢弃/转发到死信队列)
// 建议:非幂等消息(无法重复处理)设为false,幂等消息设为true
channel.basicNack(deliveryTag, false, false);
// 可选:记录失败日志,后续人工补偿
// log.error("消息消费失败,内容:{}", messageContent, e);
}
}
}
关键说明:
-
ACK(basicAck):确认消息消费成功,RabbitMQ会删除队列中的该消息;
-
NACK(basicNack):拒绝消息,可选择是否重新入队(requeue=true),适用于临时故障(如数据库连接超时),故障恢复后消息可重新消费;
-
拒绝后不重新入队(requeue=false):消息会被丢弃,或转发到死信队列(需配置死信交换机),适用于永久故障(如消息格式错误)。
5.2 消息幂等性(避免重复消费)
5.2.1 什么是消息重复消费
由于网络波动、消费者宕机、ACK超时等原因,RabbitMQ可能会将同一条消息多次投递给消费者,导致消息重复消费(如重复创建订单、重复扣减库存),因此需实现消息幂等性。
核心原则:同一消息被多次消费,最终结果一致,不会产生副作用。
5.2.2 常见幂等性实现方案(实战推荐)
方案1:基于消息ID去重(最常用)
思路:生产者发送消息时,携带唯一消息ID(如UUID),消费者消费前,先判断该消息ID是否已被处理,若已处理则直接ACK,未处理则执行业务并记录消息ID。
实现步骤(结合数据库/Redis):
-
生产者发送消息时,将消息ID放入消息头或消息体;
-
消费者获取消息后,提取消息ID;
-
使用Redis的SETNX命令(或数据库唯一约束),判断消息ID是否已存在:
-
存在:直接ACK,不执行业务;
-
不存在:执行业务,然后将消息ID存入Redis(设置过期时间,避免内存溢出),再ACK。
-
java
// 消费者幂等性实现(基于Redis)
@Component
public class IdempotentConsumer {
@Resource
private StringRedisTemplate redisTemplate;
@Resource
private OrderService orderService;
// 消息ID前缀(避免与其他业务冲突)
private static final String MESSAGE_ID_PREFIX = "rabbitmq:message:id:";
// 消息过期时间(24小时,根据业务调整)
private static final long MESSAGE_EXPIRE_TIME = 86400L;
@RabbitListener(queues = RabbitMQConfig.DIRECT_QUEUE_CREATE)
public void consumeMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String messageId = message.getMessageProperties().getMessageId(); // 提取消息ID
String messageContent = new String(message.getBody(), StandardCharsets.UTF_8);
try {
// 1. 幂等性校验:判断消息是否已处理
String redisKey = MESSAGE_ID_PREFIX + messageId;
Boolean isExists = redisTemplate.opsForValue().setIfAbsent(redisKey, "1", MESSAGE_EXPIRE_TIME, TimeUnit.SECONDS);
if (Boolean.FALSE.equals(isExists)) {
// 消息已处理,直接ACK
channel.basicAck(deliveryTag, false);
System.out.println("消息已重复消费,直接ACK,消息ID:" + messageId);
return;
}
// 2. 执行业务逻辑
OrderDTO orderDTO = JSON.parseObject(messageContent, OrderDTO.class);
orderService.createOrder(orderDTO);
// 3. 业务处理完成,ACK
channel.basicAck(deliveryTag, false);
System.out.println("消息消费成功,消息ID:" + messageId);
} catch (Exception e) {
// 消费失败,拒绝消息(根据业务调整是否重新入队)
channel.basicNack(deliveryTag, false, false);
System.out.println("消息消费失败,消息ID:" + messageId + ",异常:" + e.getMessage());
}
}
}
方案2:基于业务唯一标识去重
思路:无需依赖消息ID,使用业务本身的唯一标识(如订单号、用户ID+操作类型)作为去重依据,适用于消息ID无法获取或业务场景特殊的情况。
示例:订单创建消息,使用订单号作为唯一标识,消费前先查询数据库,若订单已存在则直接ACK,不存在则创建订单。
方案3:数据库唯一约束
思路:在业务表中添加唯一约束(如订单号唯一),当重复消费消息时,数据库会抛出唯一约束异常,消费者捕获异常后直接ACK,不影响业务。
优点:无需额外存储(如Redis),实现简单;缺点:依赖数据库,性能略低,适合并发量不高的场景。
5.3 死信队列(DLQ,Dead-Letter Queue)
5.3.1 什么是死信队列
死信队列是专门用于存储"无法正常消费"的消息的队列,当消息满足以下条件之一时,会被路由到死信队列,避免消息丢失,便于后续排查和补偿:
-
消息被消费者拒绝(basicNack/basicReject),且requeue=false;
-
消息过期(TTL过期);
-
队列达到最大长度,新消息无法入队, oldest的消息被挤入死信队列。
核心价值:避免无效消息堆积在业务队列,同时保留死信消息,便于人工排查问题(如消息格式错误、业务逻辑异常),实现消息补偿。
5.3.2 死信队列配置(实战实现)
死信队列的配置需依赖:死信交换机(DLX,Dead-Letter Exchange)、死信队列、业务队列(指定死信交换机和死信路由键),具体步骤如下:
java
@Configuration
public class RabbitMQConfig {
// 1. 业务队列(普通队列,指定死信相关参数)
public static final String BUSINESS_QUEUE = "business_queue";
// 2. 死信交换机(Direct类型,用于路由死信消息)
public static final String DLX_EXCHANGE = "dlx_exchange";
// 3. 死信队列(存储死信消息)
public static final String DLX_QUEUE = "dlx_queue";
// 4. 死信路由键(业务队列绑定死信交换机时使用)
public static final String DLX_ROUTING_KEY = "dlx.routing.key";
// 声明死信交换机(持久化)
@Bean
public DirectExchange dlxExchange() {
return DirectExchangeBuilder.durable(DLX_EXCHANGE).build();
}
// 声明死信队列(持久化)
@Bean
public Queue dlxQueue() {
return QueueBuilder.durable(DLX_QUEUE).build();
}
// 绑定死信队列到死信交换机(指定路由键)
@Bean
public Binding dlxBinding() {
return BindingBuilder.bind(dlxQueue())
.to(dlxExchange())
.with(DLX_ROUTING_KEY);
}
// 声明业务队列,指定死信交换机和死信路由键
@Bean
public Queue businessQueue() {
return QueueBuilder.durable(BUSINESS_QUEUE)
// 指定死信交换机
.withArgument("x-dead-letter-exchange", DLX_EXCHANGE)
// 指定死信路由键
.withArgument("x-dead-letter-routing-key", DLX_ROUTING_KEY)
// 可选:设置队列最大长度(达到长度后,新消息挤入死信队列)
.withArgument("x-max-length", 1000)
// 可选:设置队列消息过期时间(队列中所有消息统一过期)
.withArgument("x-message-ttl", 60000) // 60秒
.build();
}
// 业务队列绑定普通交换机(此处省略,参考第四章路由模式/主题模式)
}
5.3.3 死信消息处理
死信队列中的消息不会自动消费,需单独编写死信消费者,处理方式分为两种:
-
人工排查+手动补偿:死信消费者仅记录日志,人工查看死信消息内容,排查失败原因(如消息格式错误、业务逻辑异常),修复后手动重新发送消息;
-
自动重试:死信消费者将消息重新发送到业务队列,设置重试次数(如3次),超过次数则记录日志,人工处理。
死信消费者实现示例:
java
@Component
public class DlxConsumer {
@Resource
private RabbitTemplate rabbitTemplate;
// 监听死信队列
@RabbitListener(queues = RabbitMQConfig.DLX_QUEUE)
public void consumeDlxMessage(Message message, Channel channel) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
String messageContent = new String(message.getBody(), StandardCharsets.UTF_8);
String messageId = message.getMessageProperties().getMessageId();
try {
// 1. 记录死信消息日志(便于排查)
System.out.println("死信消息接收,消息ID:" + messageId + ",内容:" + messageContent);
// 2. 自动重试(设置重试次数,此处示例重试1次)
Integer retryCount = message.getMessageProperties().getHeader("retryCount");
if (retryCount == null || retryCount < 1) {
// 第一次重试,设置重试次数,重新发送到业务队列
message.getMessageProperties().setHeader("retryCount", 1);
rabbitTemplate.send(RabbitMQConfig.BUSINESS_QUEUE, message);
System.out.println("死信消息重试发送,消息ID:" + messageId);
} else {
// 重试次数用尽,记录日志,人工处理
System.out.println("死信消息重试次数用尽,消息ID:" + messageId + ",请人工处理");
}
// 3. ACK死信消息(无论是否重试,都需ACK,避免死信队列堆积)
channel.basicAck(deliveryTag, false);
} catch (Exception e) {
// 死信处理异常,直接ACK,避免死信队列阻塞
channel.basicAck(deliveryTag, false);
System.out.println("死信消息处理异常,消息ID:" + messageId + ",异常:" + e.getMessage());
}
}
}
5.4 消息过期时间(TTL)
TTL(Time-To-Live)即消息过期时间,单位为毫秒,当消息超过指定时间未被消费时,会被标记为过期,若配置了死信队列,则过期消息会被路由到死信队列;若未配置,则消息会被丢弃。
RabbitMQ支持两种TTL配置方式:队列级别TTL和消息级别TTL。
5.4.1 队列级别TTL
对队列中所有消息统一设置过期时间,声明队列时通过x-message-ttl参数配置,示例如下:
java
@Bean
public Queue ttlQueue() {
return QueueBuilder.durable("ttl_queue")
// 设置队列中所有消息的过期时间为30秒
.withArgument("x-message-ttl", 30000)
// 可选:绑定死信交换机,过期消息路由到死信队列
.withArgument("x-dead-letter-exchange", DLX_EXCHANGE)
.withArgument("x-dead-letter-routing-key", DLX_ROUTING_KEY)
.build();
}
特点:配置简单,所有消息统一过期,适用于对消息时效性要求一致的场景(如验证码消息,30分钟过期)。
5.4.2 消息级别TTL
对单条消息单独设置过期时间,发送消息时通过消息头配置,优先级高于队列级别TTL(即消息级别TTL会覆盖队列级别TTL)。
实现示例:
java
@Component
public class TtlProducer {
@Resource
private RabbitTemplate rabbitTemplate;
// 发送带TTL的消息(单条消息设置过期时间)
public void sendTtlMessage(String exchange, String routingKey, Object message, long ttl) {
String messageId = UUID.randomUUID().toString().replace("-", "");
CorrelationData correlationData = new CorrelationData(messageId);
// 构建消息,设置TTL
Message rabbitMessage = MessageBuilder
.withBody(JSON.toJSONString(message).getBytes(StandardCharsets.UTF_8))
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.setExpiration(String.valueOf(ttl)) // 设置消息过期时间(毫秒)
.setMessageId(messageId)
.build();
rabbitTemplate.send(exchange, routingKey, rabbitMessage, correlationData);
System.out.println("发送带TTL的消息,消息ID:" + messageId + ",TTL:" + ttl + "ms");
}
}
特点:灵活,可根据单条消息的时效性设置不同过期时间,适用于对消息时效性要求不同的场景(如普通订单1小时过期,VIP订单24小时过期)。
注意:消息过期后不会立即被删除,而是在被消费者拉取时才会判断是否过期,若队列中消息堆积过多,可能导致过期消息无法及时清理,建议结合死信队列和定时任务清理过期消息。
5.5 延迟队列(基于TTL+死信队列实现)
5.5.1 什么是延迟队列
延迟队列用于实现"消息延迟一段时间后再被消费"的场景,如:
-
订单创建后,30分钟未支付,自动取消订单;
-
用户注册后,24小时未激活,发送激活提醒;
-
定时任务(如每天凌晨1点执行数据统计)。
RabbitMQ本身不直接支持延迟队列,但可以通过TTL+死信队列间接实现:消息发送到一个"延迟队列"(无消费者),消息过期后,通过死信交换机路由到业务队列,业务队列的消费者再消费消息,实现延迟效果。
5.5.2 延迟队列实战配置
以"订单30分钟未支付自动取消"为例,配置延迟队列:
java
@Configuration
public class DelayQueueConfig {
// 1. 延迟队列(无消费者,仅用于存储延迟消息)
public static final String DELAY_QUEUE = "delay_queue";
// 2. 死信交换机(用于路由过期的延迟消息)
public static final String DELAY_DLX_EXCHANGE = "delay_dlx_exchange";
// 3. 业务队列(用于消费延迟后的消息,取消订单)
public static final String ORDER_CANCEL_QUEUE = "order_cancel_queue";
// 4. 死信路由键
public static final String DELAY_DLX_ROUTING_KEY = "delay.dlx.routing.key";
// 声明死信交换机
@Bean
public DirectExchange delayDlxExchange() {
return DirectExchangeBuilder.durable(DELAY_DLX_EXCHANGE).build();
}
// 声明业务队列(取消订单队列)
@Bean
public Queue orderCancelQueue() {
return QueueBuilder.durable(ORDER_CANCEL_QUEUE).build();
}
// 绑定业务队列到死信交换机
@Bean
public Binding orderCancelBinding() {
return BindingBuilder.bind(orderCancelQueue())
.to(delayDlxExchange())
.with(DELAY_DLX_ROUTING_KEY);
}
// 声明延迟队列(无消费者,设置TTL为30分钟=1800000毫秒)
@Bean
public Queue delayQueue() {
return QueueBuilder.durable(DELAY_QUEUE)
// 设置队列TTL(30分钟),所有消息统一延迟30分钟
.withArgument("x-message-ttl", 1800000)
// 绑定死信交换机,消息过期后路由到业务队列
.withArgument("x-dead-letter-exchange", DELAY_DLX_EXCHANGE)
.withArgument("x-dead-letter-routing-key", DELAY_DLX_ROUTING_KEY)
.build();
}
}
5.5.3 延迟队列实现逻辑
-
生产者发送订单消息到延迟队列(delay_queue),该队列无消费者,消息会在队列中等待30分钟;
-
30分钟后,消息过期,触发死信机制,被路由到死信交换机(delay_dlx_exchange);
-
死信交换机根据路由键,将消息路由到业务队列(order_cancel_queue);
-
业务队列的消费者监听消息,接收消息后执行"取消订单"业务逻辑。
生产者和消费者实现示例:
java
// 延迟队列生产者(发送订单消息)
@Component
public class DelayProducer {
@Resource
private RabbitTemplate rabbitTemplate;
public void sendDelayMessage(OrderDTO orderDTO) {
String messageId = UUID.randomUUID().toString().replace("-", "");
CorrelationData correlationData = new CorrelationData(messageId);
// 发送消息到延迟队列(无需指定路由键,延迟队列绑定默认交换机)
rabbitTemplate.convertAndSend(DelayQueueConfig.DELAY_QUEUE, orderDTO, correlationData);
System.out.println("发送延迟订单消息,订单号:" + orderDTO.getOrderNo() + ",延迟时间:30分钟");
}
}
// 延迟队列消费者(处理取消订单)
@Component
public class DelayConsumer {
@Resource
private OrderService orderService;
@RabbitListener(queues = DelayQueueConfig.ORDER_CANCEL_QUEUE)
public void consumeDelayMessage(OrderDTO orderDTO, Channel channel, Message message) throws IOException {
long deliveryTag = message.getMessageProperties().getDeliveryTag();
try {
// 执行业务逻辑:取消未支付订单
System.out.println("开始处理延迟订单取消,订单号:" + orderDTO.getOrderNo());
orderService.cancelUnpaidOrder(orderDTO.getOrderNo());
// 手动ACK
channel.basicAck(deliveryTag, false);
System.out.println("订单取消成功,订单号:" + orderDTO.getOrderNo());
} catch (Exception e) {
// 处理失败,拒绝消息,不重新入队(订单取消失败,人工处理)
channel.basicNack(deliveryTag, false, false);
System.out.println("订单取消失败,订单号:" + orderDTO.getOrderNo() + ",异常:" + e.getMessage());
}
}
}
注意:若需要不同延迟时间(如10分钟、30分钟、1小时),可创建多个不同TTL的延迟队列,或使用消息级别TTL(单条消息设置不同延迟时间)。
5.6 消息堆积处理
5.6.1 什么是消息堆积
当生产者发送消息的速度大于消费者消费消息的速度时,消息会在队列中大量堆积,若堆积过多,会导致队列内存溢出、消息过期、业务延迟等问题,严重影响系统稳定性。
常见原因:
-
消费者处理速度慢(如业务逻辑复杂、数据库瓶颈);
-
消费者宕机或数量不足;
-
突发流量(如秒杀、活动期间,生产者发送消息量激增)。
5.6.2 消息堆积解决方案(实战推荐)
方案1:增加消费者数量,提高消费能力
通过增加消费者实例或提高消费者线程数,并行处理消息,适用于消费者处理速度慢但业务逻辑简单的场景。
配置方式(application.yml):
yaml
spring:
rabbitmq:
listener:
simple:
concurrency: 10 # 核心线程数(默认5)
max-concurrency: 50 # 最大线程数(默认20)
prefetch: 20 # 每次拉取20条消息,提高消费效率
注意:消费者数量不宜过多(需结合服务器性能),避免过多线程竞争资源,导致性能下降。
方案2:优化消费者业务逻辑,提高处理速度
-
减少数据库操作(如批量入库、缓存热点数据);
-
异步处理非核心业务(如消息日志记录、通知推送);
-
避免同步调用外部服务(如调用第三方接口,改为异步调用)。
方案3:使用消息分片,拆分队列
将一个大队列拆分为多个小队列(如order_queue_1、order_queue_2、order_queue_3),每个队列绑定同一个交换机,生产者发送消息时,通过路由键将消息均匀分发到各个小队列,每个小队列对应一个消费者,实现并行消费。
示例:使用Topic交换机,路由键为order.1、order.2、order.3,三个小队列分别绑定对应的路由键,生产者发送消息时随机选择路由键,实现消息分片。
方案4:临时扩容,处理堆积消息
当出现大量消息堆积时,可临时启动多个消费者实例(如通过容器化部署,快速扩容),专门处理堆积消息,待消息堆积清理完成后,再缩减消费者数量。
方案5:监控预警,提前预防
通过RabbitMQ管理界面或监控工具(如Prometheus+Grafana),监控队列消息堆积情况,设置预警阈值(如队列消息数超过1000条触发预警),及时发现并处理问题。
5.7 集群部署(高可用)
单机部署的RabbitMQ存在单点故障风险(如服务器宕机,整个消息队列服务不可用),生产环境必须部署集群,确保高可用。
RabbitMQ集群核心概念:
-
节点:每个RabbitMQ服务器就是一个节点,分为磁盘节点(存储数据,至少1个)和内存节点(不存储数据,仅提供路由功能,提升性能);
-
集群模式:默认是普通集群(消息仅存储在一个节点,其他节点同步元数据),需结合镜像队列实现消息高可用;
-
镜像队列:将队列镜像到多个节点,当主节点宕机时,从节点自动接管队列,确保消息不丢失、服务不中断。
5.7.1 集群部署步骤(Ubuntu环境,3节点)
-
准备3台服务器,分别命名为node1(磁盘节点)、node2(磁盘节点)、node3(内存节点),确保3台服务器能相互通信(关闭防火墙或开放5672、15672、25672端口);
-
安装RabbitMQ:3台服务器均按照第二章Ubuntu环境安装步骤,安装RabbitMQ(版本一致);
-
配置节点间通信:
-
修改3台服务器的/etc/hosts文件,添加节点映射:
192.168.1.101 node1 192.168.1.102 node2 192.168.1.103 node3 -
同步3台节点的Erlang Cookie(RabbitMQ节点通信的密钥,必须一致): `# 在node1上查看Cookie cat /var/lib/rabbitmq/.erlang.cookie
将node1的Cookie复制到node2和node3,覆盖原有Cookie
scp /var/lib/rabbitmq/.erlang.cookie root@node2:/var/lib/rabbitmq/ scp /var/lib/rabbitmq/.erlang.cookie root@node3:/var/lib/rabbitmq/
修改Cookie权限(确保RabbitMQ能读取)
chmod 600 /var/lib/rabbitmq/.erlang.cookie chown rabbitmq:rabbitmq /var/lib/rabbitmq/.erlang.cookie`
-
-
组建集群:
-
启动所有节点的RabbitMQ服务:
sudo systemctl start rabbitmq-server; -
在node2上,将node2加入node1集群:
sudo rabbitmqctl stop_app sudo rabbitmqctl reset sudo rabbitmqctl join_cluster rabbit@node1 sudo rabbitmqctl start_app -
在node3上,将node3加入node1集群(设置为内存节点):
sudo rabbitmqctl stop_app sudo rabbitmqctl reset sudo rabbitmqctl join_cluster --ram rabbit@node1 sudo rabbitmqctl start_app -
查看集群状态:
sudo rabbitmqctl cluster_status,显示3个节点即组建成功。
-
-
配置镜像队列 :
# 设置所有队列都镜像到所有节点(生产环境可根据需求调整) sudo rabbitmqctl set_policy ha-all "^" '{"ha-mode":"all"}' 查看镜像队列策略:sudo rabbitmqctl list_policies说明:ha-mode=all表示队列镜像到集群中所有节点;ha-mode=exactly表示镜像到指定数量的节点;ha-mode=nodes表示镜像到指定节点。
5.7.2 集群负载均衡
集群部署后,需配置负载均衡(如Nginx、HAProxy),将生产者/消费者的请求均匀分发到各个节点,避免单个节点压力过大。
Nginx负载均衡配置示例(nginx.conf):
nginx
http {
upstream rabbitmq_cluster {
server 192.168.1.101:5672; # node1消息端口
server 192.168.1.102:5672; # node2消息端口
server 192.168.1.103:5672; # node3消息端口
ip_hash; # 会话保持,避免同一连接切换节点
}
server {
listen 5672;
server_name localhost;
location / {
proxy_pass http://rabbitmq_cluster;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
# 管理界面负载均衡(可选)
upstream rabbitmq_management {
server 192.168.1.101:15672;
server 192.168.1.102:15672;
server 192.168.1.103:15672;
}
server {
listen 15672;
server_name localhost;
location / {
proxy_pass http://rabbitmq_management;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
}
第六章:RabbitMQ实战问题排查与优化
6.1 常见问题排查(生产高频)
6.1.1 消息丢失问题
排查思路(从链路顺序排查):
-
生产者端:查看生产者确认回调日志,确认消息是否成功投递到交换机(ack=true);若ack=false,检查交换机是否存在、网络是否正常;
-
存储端:检查交换机、队列、消息是否开启持久化,若未开启,RabbitMQ宕机后消息会丢失;
-
路由端:检查交换机与队列的绑定规则(Routing Key/Headers),确认消息是否能正确路由到队列;可通过RabbitMQ管理界面查看"Exchange"→"Bindings",或查看消息返回回调日志;
-
消费者端:检查消费者是否开启手动ACK,若未开启,消费者宕机后消息会被重复投递或丢失;检查消费者是否有异常日志,确认消息是否处理完成。
解决方案:严格按照5.1节"消息可靠性投递"配置,开启三端持久化、生产者确认、手动ACK。
6.1.2 消息重复消费问题
排查思路:
-
查看消费者日志,确认是否有重复的消息ID;
-
检查消费者ACK是否正常,若ACK超时(如消费者处理时间过长,超过RabbitMQ的ACK超时时间),RabbitMQ会重新投递消息;
-
检查生产者是否开启重试机制,若生产者投递失败后重试,可能导致消息重复发送。
解决方案:实现消息幂等性(参考5.2节),结合消息ID去重或业务唯一标识去重。
6.1.3 消息堆积问题
排查思路:
-
通过RabbitMQ管理界面,查看队列的"Ready"消息数(待消费消息数),确认是否存在堆积;
-
查看消费者日志,确认消费者是否正常运行、处理速度是否正常;
-
查看生产者发送消息的速度,确认是否存在突发流量。
解决方案:参考5.6节"消息堆积处理",增加消费者数量、优化业务逻辑、拆分队列等。
6.1.4 消费者无法接收消息
排查思路:
-
检查消费者是否正常启动,是否监听了正确的队列;
-
检查队列是否存在,交换机与队列的绑定是否正确;
-
检查消息是否路由到队列(通过管理界面查看队列的"Ready"消息数);
-
检查消费者是否有权限访问队列(通过管理界面查看用户权限);
-
检查消费者的prefetch参数是否过大,导致消息被消费者拉取但未处理,显示为"Unacked"状态。
6.2 RabbitMQ性能优化
6.2.1 生产者优化
-
使用信道复用:避免频繁创建/关闭TCP连接,一个TCP连接复用多个信道;
-
批量发送消息:通过rabbitTemplate的convertAndSend批量发送接口,减少网络请求次数(适用于大量消息发送场景);
-
异步发送消息:避免同步发送消息阻塞业务线程,提升生产者吞吐量;
-
合理设置消息持久化:非核心业务消息可关闭持久化,提升发送性能。
6.2.2 消费者优化
-
合理设置消费者线程数:根据服务器性能和业务处理速度,设置concurrency和max-concurrency参数;
-
优化prefetch参数:prefetch值过大可能导致消息堆积在消费者端,过小则频繁拉取消息,建议设置为10-50;
-
批量ACK:若消费者处理速度快,可开启批量ACK(basicAck的multiple=true),减少ACK次数;
-
异步处理业务:将非核心业务(如日志、通知)异步处理,提升消费者处理速度。
6.2.3 队列与交换机优化
-
避免使用Fanout交换机广播大量消息:Fanout交换机会将消息广播到所有绑定队列,若队列过多,会导致性能下降;
-
合理设置队列TTL和最大长度:避免队列中堆积大量过期消息或无用消息;
-
使用内存节点提升路由性能:集群中配置多个内存节点,负责消息路由,磁盘节点仅负责数据存储;
-
定期清理无用队列和交换机:避免资源浪费,提升RabbitMQ整体性能。
6.2.4 服务器优化
-
调整Erlang虚拟机参数:根据服务器内存,调整Erlang的heap_size、process_limit等参数,避免内存溢出;
-
使用SSD磁盘:持久化消息需要写入磁盘,SSD磁盘的读写速度远快于机械硬盘,提升持久化性能;
-
关闭不必要的插件:仅开启必要的插件(如rabbitmq_management),避免插件占用资源;