● RabbitMQ底层原理
RabbitMQ其实就是一个类似于缓冲池的作用,用来异步解耦和适配消费者的消费速度的工具
-
异步解耦:
比如有一套业务是发起订单-创建明细-计算折扣优惠-发送短信,这一套业务,如果只用后端的一个接口来实现,那么用户就需要在前端页面等待着四个功能实现完成后才能响应,会等待很久,用户体验差,所以就有异步这一说,就是用户发起订单的时候,直接告诉他创建成功,而不是让他等待,然后剩下的三步操作丢给其他线程或者消息队列去处理
-
缓冲池适配:
比如在高并发场景下,后端处理每秒上百万请求时,显然数据库跟不上这种速度,所以就需要消息队列或者线程池来削峰填谷 ,先把百万请求存起来,按照数据库能适配的速度进行消费,而RabbitMQ的处理是:发送者(后端)->消息队列->消费者(后端处理sql发给mysql),这就类似于一个缓冲池
○ AMQP协议
AMQP(Advanced Message Queuing Protocol)协议就是定义了一套标准 就是一个消息中间件必须要实现有生产者 消费者 交换机 路由键 队列 绑定而构成的一套标准系统,只要是实现了AMQP协议的消息队列中间件,在使用时就可以无缝切换比如RabbitMQ切换到Apache Qpid,因为他们都实现了AMQP协议,但是不能无缝切换kafka、rocketMQ,因为这俩没有实现这个协议
底层流程角色:
| 术语 | 直白说明 |
|---|---|
| Broker | 一个 RabbitMQ 服务节点,负责接收、路由、分发消息,内部可划分多个虚拟主机(vhost)实现逻辑隔离 |
| Connection | 客户端与 Broker 之间建立的 TCP 长连接,用于后续通信 |
| Channel | 在同一个 TCP 连接内复用的轻量级虚拟通道,每个通道独立进行消息收发,开销极小 |
| Exchange | 消息路由组件,接收生产者消息并按绑定规则分发到对应队列 |
| Queue | 消息的实际存储单元,暂存待消费的消息,支持 FIFO、持久化和死信等特性 |
| Binding | 定义 Exchange 到 Queue 的路由规则,决定消息的流向 |
| Message | 传输的消息实体,包含数据内容和可选属性(如路由键、持久化标志、优先级等) |
- 一个数据包的旅程(类似于网络通讯的交换机):
- 前置环境:单机部署RabbitMQ就是一个Broker,集群就是多个Broker,通过Erlang cookie逻辑连接
- 数据包通过发送者发送给RabbitMQ,先用Connection建立连接,这是一个异步长连接(就是IO多路复用+NIO)
- 然后生产者选择一条connection内的channel去发数据包,这个channel起到数据隔离的作用,channel把这个数据包运输到交换机内(broker通过AMQP数据帧内的交换机名称去找对应的交换机)
- 接着交换机根据AMQP协议帧内的路由键和绑定规则去找对应的队列
- 最终交换机把这个数据发到队列内然后通告消费者你有新的任务了(NIO多路复用机制),然后消费者开始消费任务
- broker删除这条消息
○ AMQP数据帧
AMQP数据帧就是生产者和消费者与消息队列中间件收发消息的一种数据格式
每次生产者publish一条任务时他就会往connection里的channel信道发送三个固定的AMQP数据帧
方法帧->内容头帧->内容体帧
AMQP协议帧长相:
erlang
+------+--------+--------+----------+----------+
| type | channel | size | payload | 0xCE |
+------+--------+--------+----------+----------+
- type (1 字节) :帧类型。1 = 方法帧,2 = 内容头帧,3 = 内容体帧,8 = 心跳帧。
channel (2 字节) :通道号。
size (4 字节) :payload 的字节长度。
payload (变长) :不同类型帧有不同的载荷格式。
end (1 字节) :固定值 0xCE,作为帧结束标志。
1 个 Basic.Publish 方法帧(携带交换器、路由键等)
1 个 Content-Header 内容头帧(携带消息属性和总长度)
≥1 个 Content-Body 内容体帧(携带实际负载)
方法帧:
erlang
+------+--------+--------+----------+----------+
| 1| 信道ID| 载荷大小 | payload | 0xCE |
+------+--------+--------+----------+----------+
payload内容:
+-----------+----------+---------------+
| class-id | method-id| arguments ... |
+-----------+----------+---------------+
class-id (2 字节) :对于 Basic 类,固定为 60(十进制)。
method-id (2 字节) :Basic.Publish 固定为 40(十进制)。
arguments:根据 AMQP 方法定义编码的参数列表,对于 basic.publish 包含以下字段(按顺序):
ticket:short,通常为 0。
exchange:shortstr,交换器名称。
routing-key:shortstr,路由键。
mandatory:bit(打包在 octet 中),消息如果无法路由是否返回。
内容头帧:
erlang
+------+--------+--------+----------+----------+
| 2| 信道ID| 载荷大小 | payload | 0xCE |
+------+--------+--------+----------+----------+
payload内容:
+---------+-------+--------+----------+-----------+
| class-id| weight| body-size| prop-flags| properties |
+---------+-------+--------+----------+-----------+
class-id (2 字节) :同样为 60。
weight (2 字节) :消息体大小不太可能用到,通常为 0。
body-size (8 字节) :所有内容体帧 payload 的总字节数(无符号 64 位网络字节序)。
property-flags (2 字节) :一个位掩码,表示后面携带了哪些消息属性。
properties:按 property-flag 位顺序打包的消息属性,常见的有:
content-type (shortstr)
content-encoding (shortstr)
headers (field table)
delivery-mode (octet,1=非持久,2=持久)
priority (octet)
correlation-id (shortstr)
reply-to (shortstr)
expiration (shortstr)
message-id (shortstr)
timestamp (timestamp,8 字节)
type (shortstr)
user-id (shortstr)
app-id (shortstr)
cluster-id (保留,一般不发)
内容体帧:
erlang
+------+--------+--------+----------+----------+
| 3| 信道ID| 载荷大小 | hello world!| 0xCE |
+------+--------+--------+----------+----------+
● Spring AMQP
添加依赖:
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
配置yml:
yml
spring:
rabbitmq:
host: localhost
port: 5672
username: guest
password: guest
listener:
simple:
prefetch: 1 # 预取数量
acknowledge-mode: manual # 手动确认
配置队列名字:
java
@Configuration
public class RabbitConfig {
public static final String QUEUE_NAME = "hello";
//加不加都行 只是让RabbitMQ把这个队列持久化到本地防止崩溃后无法恢复队列内的数据
@Bean
public Queue queue() {
return new Queue(QUEUE_NAME, true); // 持久化队列
}
}
生产者:
java
@Component
public class Sender {
@Autowired
private RabbitTemplate rabbitTemplate;
public void send(String message) {
rabbitTemplate.convertAndSend("", RabbitConfig.QUEUE_NAME, message);
}
}
消费者:
java
@Component
public class Receiver {
@RabbitListener(queues = RabbitConfig.QUEUE_NAME)
public void receive(String message)
throws IOException {
System.out.println("Received: " + message);
}
}
○ Spring AMQP的组件
在生产者于消费者连接RabbitMQ时,springboot启动会默认创建四个单例组件:连接工厂-生产者模板-消费者监听器-管理组件
这些组件可以在yml中作简化配置,详细配置要在java类中
- 连接工厂组件,主要是配置RabbitMQ的broker基本信息比如地址端口连接重置等
java
import org.springframework.amqp.rabbit.connection.CachingConnectionFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
@Bean
public ConnectionFactory connectionFactory() throws Exception {
// 底层 RabbitMQ 客户端连接工厂 (com.rabbitmq.client.ConnectionFactory)
RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean();
factoryBean.setHost("rabbitmq.example.com"); // 主机地址
factoryBean.setPort(5672); // 端口
factoryBean.setUsername("admin"); // 用户名
factoryBean.setPassword("secret"); // 密码
factoryBean.setVirtualHost("/production"); // 虚拟主机
factoryBean.setConnectionTimeout(30000); // 连接超时 30s
factoryBean.setRequestedHeartbeat(60); // 心跳间隔 60s
factoryBean.setHandshakeTimeout(10000); // 握手超时
factoryBean.setAutomaticRecoveryEnabled(true); // 自动恢复连接
factoryBean.setTopologyRecoveryEnabled(true); // 自动恢复拓扑(队列/交换机/绑定)
factoryBean.setNetworkRecoveryInterval(5000); // 恢复重试间隔 5s
// 如果使用集群地址
// factoryBean.setUri("amqp://rabbit1:5672,rabbit2:5672");
factoryBean.afterPropertiesSet();
com.rabbitmq.client.ConnectionFactory rabbitConnectionFactory = factoryBean.getObject();
// 创建 Spring 的缓存连接工厂
CachingConnectionFactory cachingFactory = new CachingConnectionFactory(rabbitConnectionFactory);
cachingFactory.setCacheMode(CachingConnectionFactory.CacheMode.CHANNEL); // 缓存信道
cachingFactory.setChannelCacheSize(50); // 最多缓存 50 个信道
cachingFactory.setChannelCheckoutTimeout(10000); // 获取信道的等待超时
cachingFactory.setConnectionCacheSize(5); // 缓存的最大连接数(默认1,多线程时增加)
cachingFactory.setConnectionLimit(10); // 允许的总连接数
cachingFactory.setPublisherConfirmType(CachingConnectionFactory.ConfirmType.CORRELATED); // 发布确认模式
cachingFactory.setPublisherReturns(true); // 开启发布返回监听
return cachingFactory;
}
}
- 生产者模板:这是一个配置rabbitmqTemplate的配置:
java
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.backoff.ExponentialBackOffPolicy;
import org.springframework.retry.policy.SimpleRetryPolicy;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.amqp.rabbit.connection.CorrelationData;
@Configuration
public class RabbitMQConfig {
// ... connectionFactory bean ...
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
RabbitTemplate template = new RabbitTemplate(connectionFactory);
// 消息转换器(JSON 序列化)
Jackson2JsonMessageConverter jsonConverter = new Jackson2JsonMessageConverter();
// 设置默认编码
template.setMessageConverter(jsonConverter);
// 发布确认回调
template.setConfirmCallback((correlationData, ack, cause) -> {
if (ack) {
System.out.println("消息确认成功: " + correlationData);
} else {
System.err.println("消息确认失败: " + correlationData + ", 原因: " + cause);
// 可进行重发或记录日志
}
});
// 发布返回回调(消息无法路由到队列时触发)
template.setReturnsCallback(returnedMessage -> {
Message message = returnedMessage.getMessage();
int replyCode = returnedMessage.getReplyCode();
String replyText = returnedMessage.getReplyText();
String exchange = returnedMessage.getExchange();
String routingKey = returnedMessage.getRoutingKey();
System.err.printf("消息被退回: %s, 路由键: %s, 回复码: %d, 原因: %s%n",
message, routingKey, replyCode, replyText);
// 处理无法路由的消息
});
// 强制 Mandatory 标志(默认为 true,使退回生效)
template.setMandatory(true);
// 发送重试模板(可选)
RetryTemplate retryTemplate = new RetryTemplate();
SimpleRetryPolicy retryPolicy = new SimpleRetryPolicy();
retryPolicy.setMaxAttempts(3); // 最多重试 3 次
retryTemplate.setRetryPolicy(retryPolicy);
ExponentialBackOffPolicy backOffPolicy = new ExponentialBackOffPolicy();
backOffPolicy.setInitialInterval(1000); // 初始间隔 1s
backOffPolicy.setMultiplier(2.0); // 倍乘因子
backOffPolicy.setMaxInterval(10000); // 最大间隔 10s
retryTemplate.setBackOffPolicy(backOffPolicy);
template.setRetryTemplate(retryTemplate); // 发送失败时自动重试
// 设置默认的交换机与路由键(可选)
template.setExchange("topic.exchange");
template.setRoutingKey("default.key");
// 使用消息后处理器对每条消息操作(如添加全局消息头)
template.setBeforePublishPostProcessors(message -> {
MessageProperties props = message.getMessageProperties();
props.setHeader("source", "order-service");
props.setHeader("timestamp", System.currentTimeMillis());
return message;
});
return template;
}
}
- 消费者监听容器:
java
import org.springframework.amqp.core.AcknowledgeMode;
import org.springframework.amqp.rabbit.config.SimpleRabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.listener.RabbitListenerContainerFactory;
import org.springframework.amqp.rabbit.listener.api.RabbitListenerErrorHandler;
import org.springframework.amqp.rabbit.listener.exception.ListenerExecutionFailedException;
import org.springframework.amqp.support.converter.MessageConverter;
import org.springframework.context.annotation.Bean;
import org.springframework.retry.RecoveryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.amqp.rabbit.listener.ConditionalRejectingErrorHandler;
import org.springframework.amqp.rabbit.listener.FatalExceptionStrategy;
@Configuration
public class RabbitMQConfig {
// ... connectionFactory, rabbitTemplate ...
@Bean
public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
ConnectionFactory connectionFactory,
MessageConverter messageConverter) { // 可复用 Jackson 转换器
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 并发消费者配置
factory.setConcurrentConsumers(5); // 初始消费者线程数
factory.setMaxConcurrentConsumers(20); // 最大消费者线程数
factory.setStartConsumerMinInterval(10000); // 空闲消费者缩减的最小间隔
factory.setStopConsumerMinInterval(60000); // 停止多余消费者的最小间隔
factory.setConsecutiveActiveTrigger(10); // 连续活跃触发扩容
factory.setConsecutiveIdleTrigger(10); // 连续空闲触发缩容
// 预取数量(控制消费者的未确认消息缓存大小)
factory.setPrefetchCount(50); // 每个消费者预取 50 条
// 确认模式
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // 手动确认,需在监听方法中调用 channel.basicAck
// 事务支持(与手动确认互斥)
// factory.setChannelTransacted(false);
// 消息转换器
factory.setMessageConverter(messageConverter);
// 重试模板(消费者内部重试,在异常未抛出到框架之前)
RetryTemplate retryTemplate = new RetryTemplate();
retryTemplate.setRetryPolicy(new SimpleRetryPolicy(3)); // 最多重试 3 次
factory.setRetryTemplate(retryTemplate);
// 错误处理器(决定哪些异常需要 requeue)
factory.setErrorHandler(new ConditionalRejectingErrorHandler(
new FatalExceptionStrategy() {
@Override
public boolean isFatal(Throwable t) {
// 返回 true 表示不重新入队,消息直接丢弃或进入死信
return t instanceof IllegalArgumentException;
}
}
));
// 自定义错误处理器(可覆盖上述逻辑)
factory.setErrorHandler(new ConditionalRejectingErrorHandler() {
@Override
public void handleError(Throwable t) {
// 记录日志,可根据异常类型决定 requeue
super.handleError(t);
}
});
// 全局恢复回调(当重试耗尽后执行,可手动发送到死信队列)
factory.setRecoveryCallback((RecoveryCallback<Object>) context -> {
// context 包含原始消息、异常等信息
System.err.println("消息处理失败,重试耗尽: " + context.getAttribute(RetryContext.EXHAUSTED));
// 可在此发送到死信队列或记录
return null;
});
// 消费者监听器执行超时
factory.setConsumerStartTimeout(60000); // 启动消费者超时
factory.setShutdownTimeout(5000); // 关闭消费者超时
factory.setPhase(100); // 生命周期阶段(在哪个阶段启动)
// 每个消费者线程并发处理的 channel 数量(简单容器默认 1,无需更改)
// factory.setBatchSize(10); // 仅用于 BatchListener
return factory;
}
}
- 管理组件:
java
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.Queue;
import org.springframework.amqp.core.TopicExchange;
import org.springframework.amqp.rabbit.connection.ConnectionFactory;
import org.springframework.amqp.rabbit.core.RabbitAdmin;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class RabbitMQConfig {
// ... 上述 beans ...
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory) {
RabbitAdmin admin = new RabbitAdmin(connectionFactory);
admin.setAutoStartup(true); // 应用启动时自动声明
admin.setIgnoreDeclarationExceptions(false); // 声明异常时是否忽略,生产建议 false
// 声明交换机、队列、绑定等(可通过 declareXxx 方法或自动检测 @Bean)
// 方式1:在配置中显式声明
TopicExchange exchange = new TopicExchange("order.exchange", true, false);
Queue queue = new Queue("order.queue", true, false, false);
Binding binding = BindingBuilder.bind(queue).to(exchange).with("order.#");
admin.declareExchange(exchange);
admin.declareQueue(queue);
admin.declareBinding(binding);
// 方式2:直接定义为 Spring Bean,RabbitAdmin 会自动扫描并声明
// 此处略
return admin;
}
// 独立声明 Queue、Exchange、Binding Bean(推荐方式,RabbitAdmin 会自动处理)
@Bean
public TopicExchange orderExchange() {
return new TopicExchange("order.exchange", true, false);
}
@Bean
public Queue orderQueue() {
return new Queue("order.queue", true, false, false);
}
@Bean
public Binding orderBinding() {
return BindingBuilder.bind(orderQueue()).to(orderExchange()).with("order.#");
}
}
● Work模型
如果生产者一直发任务,数量非常多,比如短信邮箱等,这时候消费者处理不过来,任务就会一直堵在队列中,导致队列冗余
work模型就是多个消费者盯着一个队列,但是RabbitMQ默认是轮询发任务,比如一个队列仨消费者,那么他会把这些任务轮询发给这仨消费者,不管这仨消费者干没干完当前手上的活,这样就会导致消费者客户端的channel缓冲区积压任务量导致内存占用,所以要在配置设置:
yml
spring:
rabbitmq:
listener:
simple:
prefetch: 1 # 每次只推送一条未确认消息
也可以指定容器
java
@Bean
public SimpleRabbitListenerContainerFactory myFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setPrefetchCount(5);
return factory;
}
使用指定容器:
java
@RabbitListener(queues = "task_queue", containerFactory = "myFactory")
public void handle(String msg) { ... }
● 交换机
使用Bean声明交换机和队列,这种在java中声明的交换机和队列会持久化在RabbitMQ服务端的磁盘中,除非显式声明不持久化duration=false
java
import org.springframework.amqp.core.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class AmqpConfig {
// ---------- Fanout ----------
@Bean
public FanoutExchange fanoutExchange() {
return new FanoutExchange("logs.fanout");
}
@Bean
public Queue fanoutQueue1() { return new Queue("fanout.queue1", true); }
@Bean
public Queue fanoutQueue2() { return new Queue("fanout.queue2", true); }
@Bean
public Binding fanoutBinding1(FanoutExchange fanoutExchange, Queue fanoutQueue1) {
return BindingBuilder.bind(fanoutQueue1).to(fanoutExchange);
}
@Bean
public Binding fanoutBinding2(FanoutExchange fanoutExchange, Queue fanoutQueue2) {
return BindingBuilder.bind(fanoutQueue2).to(fanoutExchange);
}
// ---------- Direct ----------
@Bean
public DirectExchange directExchange() {
return new DirectExchange("logs.direct");
}
@Bean
public Queue directErrorQueue() { return new Queue("direct.error", true); }
@Bean
public Queue directInfoQueue() { return new Queue("direct.info", true); }
@Bean
public Binding directErrorBinding(DirectExchange directExchange, Queue directErrorQueue) {
return BindingBuilder.bind(directErrorQueue).to(directExchange).with("error");
}
@Bean
public Binding directInfoBinding(DirectExchange directExchange, Queue directInfoQueue) {
return BindingBuilder.bind(directInfoQueue).to(directExchange).with("info");
}
// ---------- Topic ----------
@Bean
public TopicExchange topicExchange() {
return new TopicExchange("logs.topic");
}
@Bean
public Queue topicAllQueue() { return new Queue("topic.all", true); }
@Bean
public Queue topicCriticalQueue() { return new Queue("topic.critical", true); }
@Bean
public Binding topicAllBinding(TopicExchange topicExchange, Queue topicAllQueue) {
return BindingBuilder.bind(topicAllQueue).to(topicExchange).with("#");
}
@Bean
public Binding topicCriticalBinding(TopicExchange topicExchange, Queue topicCriticalQueue) {
return BindingBuilder.bind(topicCriticalQueue).to(topicExchange).with("*.critical");
}
// ---------- Headers ----------
@Bean
public HeadersExchange headersExchange() {
return new HeadersExchange("logs.headers");
}
@Bean
public Queue headersQueue1() { return new Queue("headers.queue1", true); }
@Bean
public Queue headersQueue2() { return new Queue("headers.queue2", true); }
@Bean
public Binding headersBinding1(HeadersExchange headersExchange, Queue headersQueue1) {
return BindingBuilder.bind(headersQueue1).to(headersExchange)
.whereAll("format", "type").exist(); // 实际需要更具体的匹配值,需用 where() 链式
}
// 注意:Headers 的精确匹配写法较繁琐,可以用 whereAll("header").exist() 或 whereAny(...)
// 更灵活的方式是直接用 BindingBuilder.bind(queue).to(exchange).with(new HashMap<>()) 并设置 arguments
// 此处简化示例,实际可使用匿名 Binding 方式:
@Bean
public Binding headersBinding2() {
return new Binding("headers.queue2",
Binding.DestinationType.QUEUE,
"logs.headers",
"",
Map.of("x-match", "any", "format", "txt", "type", "log"));
}
}
○ Fanout交换机(广播)
当一个业务链关系到很多功能时,就可以把这个业务数据发给不同的模块去执行比如:
- 实时日志广播:一条日志同时发往文件存储、ELK、告警系统,每个系统都要完整日志。
配置热更新:配置中心变更后广播给所有服务实例,大家都需要完整的新配置。
缓存批量失效:商品信息变动,所有缓存节点都需要收到同一个商品ID去清空本地缓存。
聊天室消息:群聊里一个人发言,所有人都要收到相同内容的消息。
使用(假设在RabbitMQ可视化配置好了fanout交换机以及绑定队列之后):
java
@Autowired
private RabbitTemplate rabbitTemplate;
// 直接使用 convertAndSend,第二个参数为路由键(空字符串即可)
rabbitTemplate.convertAndSend("my.fanout", "", "Hello Spring Fanout");
○ Direct交换机(定向路由)
当一个业务链有创建订单-创建明细-发送短信-发送邮箱时这时候就需要定向路由把这四个功能分发到不同的队列由各自的消费者去处理了
这个交换机内部会维护一张路由表,也就是路由键绑定队列的映射关系表,是哈希结构所以是O(1)复杂度
使用(假设在RabbitMQ可视化网页配置好了direct交换机以及绑定好了指定路由键的队列):
java
rabbitTemplate.convertAndSend("my.direct", "order.pay", payEvent);
○ Topic交换机(模糊匹配路由)
Topic交换就是相当于sql语句的like '%*';这样的,他也有自己的通配符(路由键是用.隔离的比如order.pay.create):
erlang
*:代表一个单词
#:代表多个单词
多层次日志收集:比如路由键设计为 <facility>.<severity>,可以绑定 *.critical 来接收所有设施的严重告警,或 auth.* 监听认证服务的所有级别日志。
地理信息分发:路由键 country.city.store,消费者可以用 europe.# 订阅整个欧洲的数据,或用 *.london.* 订阅伦敦所有店铺的事件。
微服务事件总线:各个微服务按自己的领域模型通过通配符订阅感兴趣的事件类型,例如 order.* 监听所有订单相关事件,order.payment.* 监听支付子流程。 核心思想是:当消费者不是按单一字段精确消费,而是按某种分类树订阅时,Topic 交换机就能极大地简化发布端的路由逻辑。
只是在声明队列路由键的时候和声明交换机时语法不同其他都与direct一样
○ 消息转换器
当需要往队列内传一个对象时,他会自动调用MessageConverter接口,而这个接口的默认实现类注入是SimpleMessageConverter,所以在消息转入时在队列内看到的是一串乱码,但是这个乱码是不安全的,消息体也非常大,总之不好用,所以可以自己实现这个接口用Json依赖把这个对象不再用java的序列化而是json格式对象
他底层默认发一个对象时会自动调用MessageConverter接口这个接口的默认实现是SimpleMessageConverter,自己去创建一个实现类Jackson2MessageConverter,spring会通过静态绑定去替换掉这个SimpleMessageConverter
配置消息转换器(只需要声明一个Bean即可):
java
@Bean
public Jackson2JsonMessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
生产者消费者两端都需要配置这个转换器,在微服务内可以通过公共模块组件来统一更新配置
● 可靠性
○ 生产者可靠性
△ 生产者重连配置
生产者在发消息时如果因为网络问题而造成无法连接目标MQ可以配置重连:
yml
spring:
rabbitmq:
template:
retry:
enabled: true # 是否启用 RabbitTemplate 消息发送失败时的自动重试
initial-interval: 1000ms # 第一次重试前的等待时间,此处为 1000 毫秒(1 秒)
multiplier: 1 # 每次重试间隔的递增倍数,设为 1 表示固定间隔,即每次重试都等待相同的 1 秒
max-attempts: 3 # 最大发送尝试次数(包括第一次发送),此处最多发送 3 次,即重试 2 次
△ 生产者确认模式
除了使用生产者模板组件配置setConfirmCallback或者returnCallback还可以通过CorrelationData来针对性的配置确认回调:
如果发送任务到队列成功rabbitmq会返回一个ack,如果发送失败则会返回一个nack,如果发送成功了但是路由失败了也会返回一个ack还有一个return
开启生产者确认模式之后,性能会下降很多,原因是网络回执帧传输与磁盘写入和传入失败后的业务重试
- 网络回执帧:
当生产者发送一个消息后,他经过的channel信道会在内部维护一张deliveryTag表他是从1开始递增的,这个数值和发送的三个AMQP消息帧一三对应,这个的作用就是知到当broker接收到消息后返回basic.ack或者basic.nack帧时能够携带deliveryTag并确认这个回执帧对应的消息是哪条,影响性能就是这个回执帧,有多少发送帧就有三分之一的回执帧,影响网络性能,不过rabbitmq内置了一个multiple机制,当发送大量的AMQP任务帧时,这个默认为false的multiple属性就会变为true,他的作用就是减少回执帧的网络开销,把大量的回执帧变为一次回执帧,而这个回执帧可以确认大量的basic.ack或basic.nack标志,就是这个回执帧判断deliveryTag,小于这个deliveryTag的回执帧代表basic.ack或者baisc.nack这样就可以一次确认大量任务发送标志,这可以大量减少回执帧的网络开销 - 磁盘写入:
如果开启了消息持久化,那么消息被发送到队列内时(交换机和队列都是默认持久化),这个消息被刷盘是异步操作的,但是这个有点特殊,就是这个消息快要进入队列时会有一个刷盘操作这个刷盘和进入队列时异步的,但是消息一但刷盘成功后,broker才会把这个消息成功进入的消息返回给生产者 - 业务重试:
当一个消息没有被入队列时这时候返回一个basic.nack帧这时候生产者业务层面可能会重试发送任务,这就造成了重试性能占用
100万数据测试速度(只用持久化):

100万数据测试(确认者模式+持久化处理):

配置一个CorrelationData的实现类
java
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
@Slf4j
public class DetailedCorrelationData extends CorrelationData {
/**
* 用于失败后重新发送的模板(注意:不能在这里直接用 @Autowired 注入,需外部传入)
*/
private final RabbitTemplate rabbitTemplate;
/**
* 待发送的交换机、路由键和原始消息(convertAndSend 中的最后一个参数无法直接获取原生 Message,
* 通常需要自己保存,这里为了示例保存 exchange 和 routingKey,消息体可通过 convertAndSend 重载传入)
*/
private final String exchange;
private final String routingKey;
private final Object message; // 原始业务对象(可序列化)
private final Message rawMessage; // 可选,实际 RabbitMQ 的 Message 对象
/**
* 最大重试次数
*/
private final int maxRetries;
private int currentRetry = 0;
/**
* 构造方法,除了必要的 id 和回调,还携带了重新发送所需的所有信息
*/
public DetailedCorrelationData(String id,
RabbitTemplate rabbitTemplate,
String exchange,
String routingKey,
Object message,
Message rawMessage,
int maxRetries) {
super(id);
this.rabbitTemplate = rabbitTemplate;
this.exchange = exchange;
this.routingKey = routingKey;
this.message = message;
this.rawMessage = rawMessage;
this.maxRetries = maxRetries;
}
/**
* 成功回调(例如更新数据库状态)
*/
public void onSuccess() {
log.info("消息发送成功, id: {}, exchange: {}, routingKey: {}", getId(), exchange, routingKey);
// 可在此更新业务状态
}
/**
* 失败回调(支持重试)
*/
public void onFailure() {
log.warn("消息发送失败, id: {}, 当前重试次数: {}/{}", getId(), currentRetry, maxRetries);
if (currentRetry < maxRetries) {
currentRetry++;
log.info("准备第{}次重发消息", currentRetry);
// 延迟一段时间或直接重发(生产中建议异步延迟)
if (rawMessage != null) {
// 使用原始 Message 重发,保证所有属性一致
rabbitTemplate.send(exchange, routingKey, rawMessage, this);
} else {
// 否则使用业务对象重发
rabbitTemplate.convertAndSend(exchange, routingKey, message, this);
}
} else {
log.error("消息最终发送失败,已超过最大重试次数, id: {}, exchange: {}, routingKey: {}, 消息体: {}",
getId(), exchange, routingKey, message);
// 持久化到数据库、发送告警或移入死信表
saveToDeadLetterTable();
}
}
private void saveToDeadLetterTable() {
// 实现持久化逻辑
}
}
生产者使用这个实现类:
java
@Service
public class MessageService {
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrderMessage(Order order) {
String messageId = order.getId().toString(); // 使用有业务含义的 ID
// 构造详细 CorrelationData,携带所有重试需要的信息
DetailedCorrelationData cd = new DetailedCorrelationData(
messageId,
rabbitTemplate, // 模板用于失败重发
"order.exchange", // 交换机
"order.new", // 路由键
order, // 业务对象
null, // 本次不使用原始 Message
3 // 最大重试次数
);
rabbitTemplate.convertAndSend("order.exchange", "order.new", order, cd);
}
}
○ MQ可靠性
△ 消息持久化的方式
使用 MessageBuilder 直接构建持久化消息
java
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageBuilder;
import org.springframework.amqp.core.MessageDeliveryMode;
Message msg = MessageBuilder.withBody("content".getBytes())
.setDeliveryMode(MessageDeliveryMode.PERSISTENT)
.build();
rabbitTemplate.send("myDurableQueue", msg);
通过 RabbitTemplate 的 send 重载传入 MessagePostProcessor
java
rabbitTemplate.send("myDurableQueue",
new org.springframework.amqp.core.Message("content".getBytes(),
new org.springframework.amqp.core.MessageProperties()),
message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
});
全局设置默认投递模式(影响所有通过该 RabbitTemplate 发送的消息)
java
rabbitTemplate.setMessageConverter(new SimpleMessageConverter());
rabbitTemplate.setBeforePublishPostProcessors(message -> {
message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
return message;
});
△ LazyQueue
他的原理就是消息一入队列就立马写入磁盘而不是进内存,当有消费者需要消费时再从内存中读取消息
这种功能适合用于生产速度远远大于消费速度场景,因为不这样做他内存会堆积任务导致故障
使用:
java
@Bean
public Queue lazyQueue() {
return QueueBuilder.durable("lazy.queue")
.withArgument("x-queue-mode", "lazy")
.build();
}
○ 消费者可靠性
△ 消费者确认模式
他就是确认处理成功消息之后再把消息从队列删除不然就重试或者扔给死信交换机
原理就是当rabbitmq给消费者发消息时,消费者接收到之后进行处理,处理之后给rabbitmq发三个东西:
basic.ack(成功处理)、basic.reject(单条拒绝)或 basic.nack(可批量拒绝)
basic.reject 有一个 requeue 参数:若为 true,消息会重新放回队列原点;若为 false,消息将被丢弃或进入配置的死信队列。basic.nack 还支持 multiple 字段,可一次性拒绝多条消息。
使用(yml配置):
yml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: auto # 可选 none, auto, manual
prefetch: 10 # 预取数量,需配合确认模式使用
default-requeue-rejected: false # 仅 AUTO 模式下生效,false 表示异常时不重新入队
消费者重试机制:
ps:如果配置了重试机制,那么消费者会在自己内部重试这个业务,重试几次之后不行再返回basic.nack
yml
spring:
rabbitmq:
listener:
simple:
retry:
enabled: true # 开启重试机制
max-attempts: 3 # 最大尝试次数(包含首次消费)
initial-interval: 2000ms # 首次重试间隔
multiplier: 2.0 # 间隔乘数(指数退避)
max-interval: 10000ms # 最大重试间隔
stateless: true # 无状态重试,适用于幂等消费
none模式
(消息发出去就立马从队列内删除 不管消费者处理的怎么样):
java
@RabbitListener(queues = "demo.queue")
public void handle(MyMessage message) {
// 处理业务
service.process(message);
// 无论成功或异常,消息均已确认,无法回退
}
auto模式
(如果业务抛出异常则消费者会返回basic.nack):
如果抛出的异常是AmqpRejectAndDontRequeueException那么消息不会重放队列而是丢弃
java
if (criticalError) {
throw new AmqpRejectAndDontRequeueException("Critical", cause);
}
MANUAL模式(手动确认)
这是手动指定返回ack、nack、reject,他需要消费者接收信道以及deliveryTag参数
java
@RabbitListener(queues = "demo.queue")
public void handle(MyMessage message, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
try {
// 执行业务
service.process(message);
// 成功:单条确认
channel.basicAck(tag, false);
} catch (BusinessException e) {
// 业务逻辑错误:拒绝消息且不重新入队(通常进入死信)
channel.basicNack(tag, false, false);
} catch (TemporaryException e) {
// 临时故障(如网络抖动):拒绝并重新入队
channel.basicNack(tag, false, true);
} catch (Exception e) {
// 其他未知异常,也可根据情况决定
channel.basicNack(tag, false, false); // 不重入
}
}
channel.basicAck(deliveryTag, false):确认单条消息。
channel.basicNack(deliveryTag, false, true):拒绝单条,requeue=true 让消息重回队列,稍后再次投递。
channel.basicNack(deliveryTag, false, false):拒绝且不重回队列,消息被丢弃或进入 DLX。
channel.basicReject(deliveryTag, false):与 nack 类似,但只支持单条,不推荐批量。
配置类配置消费者确认模式
确认模式:
java
@Bean
public SimpleRabbitListenerContainerFactory myFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 设置确认模式
factory.setAcknowledgeMode(AcknowledgeMode.MANUAL); // MANUAL, AUTO, NONE
// 预取数量
factory.setPrefetchCount(10);
// 仅 AUTO 模式相关的 requeue 拒绝策略
factory.setDefaultRequeueRejected(false);
return factory;
}
重试机制:
java
@Bean
public SimpleRabbitListenerContainerFactory retryFactory(ConnectionFactory cf,
RetryOperationsInterceptor retryInterceptor) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(cf);
factory.setAcknowledgeMode(AcknowledgeMode.AUTO); // 推荐 AUTO 配合重试
factory.setDefaultRequeueRejected(false); // 重试耗尽后不重新入队
// 将重试拦截器添加为 advice
factory.setAdviceChain(Collections.singletonList(retryInterceptor));
return factory;
}
@Bean
public RetryOperationsInterceptor retryInterceptor() {
return RetryInterceptorBuilder.stateless()
.maxAttempts(3) // 最多尝试 3 次
.backOffOptions(2000, 2.0, 10000) // 初始2秒,乘数2,最大10秒
.recoverer(new RejectAndDontRequeueRecoverer()) // 重试耗尽后拒绝且不重新入队
.build();
}
△ 幂等性
幂等性就是多次消息重试后,业务的执行结果与第一次一致,F(x)=F(F(F(X))),就是执行N次后结果不会变
说白了就是当消费者成功处理了一次消息后,准备返回ack,但这时候消费者崩了,他并没有成功发送给rabbitmq的ack,这时候rabbitmq看到消费者崩了,他就会自动requeue=true把消息重新入队列,等你复活再发给你,那这时候我已经处理成功了就不需要在处理第二遍了,就需要消费者做幂等性操作,让这个业务跳过或者直接返回ack,如果没处理成功就执行业务,这个幂等性操作完全是消费者应用层干的事,自己搞一个id或者业务标识来标志这个业务完没完成就可以了
可以在生产者阶段设置MessageId然后在消费者端通过数据库检查这个id是否存在:
java
@Autowired
private RabbitTemplate rabbitTemplate;
public void sendOrder(String orderData) {
String messageId = UUID.randomUUID().toString();
MessageProperties props = new MessageProperties();
props.setMessageId(messageId);
// 可开启发送端确认,保证消息不丢失
Message message = new Message(orderData.getBytes(), props);
rabbitTemplate.convertAndSend("order.exchange", "order.routingkey", message);
}
最优解依然是利用业务天然的唯一键去判断业务幂等
● 延迟消息
○ 死信交换机
死信交换机就是一条消息未被消费而丢到人工干预通道处理或者什么处理的交换机
消息成为死信有三种情况:例如消费者明确拒绝、消息在队列中存活超时、或队列积压数量超过上限
死信交换机是通过在一个队列内设置x-dead-letter-exchange指定死信交换机和x-dead-letter-routing-key(死信路由键:可选)后的队列,这个队列内的消息如果明确拒绝不再处理或者超时或者超过这个队列的存储数量后,那么这个消息就会被这个队列转发到对应的交换机
其实死信交换机就是一个普通的direct、fanout、topic交换机而已,只是逻辑上做了区分
实现(定义死信交换机):
java
@Configuration
public class DeadLetterConfig {
// 死信交换机与死信队列
@Bean
public DirectExchange deadLetterExchange() {
return new DirectExchange("dlx.exchange");
}
@Bean
public Queue deadLetterQueue() {
return new Queue("dlq.queue");
}
@Bean
public Binding deadLetterBinding() {
return BindingBuilder.bind(deadLetterQueue())
.to(deadLetterExchange())
.with("dlq.routingkey");
}
// 业务队列,绑定死信交换机
@Bean
public Queue businessQueue() {
return QueueBuilder.durable("business.queue")
.withArgument("x-dead-letter-exchange", "dlx.exchange")
.withArgument("x-dead-letter-routing-key", "dlq.routingkey")
.withArgument("x-message-ttl", 60000) // 可选,消息60秒过期
.withArgument("x-max-length", 1000) // 可选,队列最大长度
.build();
}
// 业务交换机和绑定略...
}
消费者处理:
java
@RabbitListener(queues = "business.queue")
public void handleBusinessMessage(Message message, Channel channel) {
try {
// 业务处理逻辑
throw new RuntimeException("处理失败");
} catch (Exception e) {
// 将消息拒绝且不重新入队,使其成为死信
channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, false);
}
}
死信队列处理:
这个x-death参数是代表详细记录死信的来因(例如队列名、拒绝原因、过期原因等)
java
@RabbitListener(queues = "dlq.queue")
public void handleDeadMessage(Message message, @Header(name = "x-death", required = false) List<Map<String, Object>> deathInfo) {
// 从 deathInfo 中获取原始队列名、死因等,进行相应处理
System.out.println("Received dead message: " + new String(message.getBody()));
}
他的适用场景:创建订单后检查支付状态,
- 下下策就是创建完订单了,后端创一个@Schedule定时扫一遍全部订单检查支没支付然后更新状态
- 下策就是创建完一个订单然后给这个订单造个定时器,比如打个线程睡30s之后再检查
- 中策就是用java的DelayedQueue队列,上面两种一定性能爆炸,这种只适合于低并发场景
- 上策:死信交换机,让这个死信队列充当那个定时器,消息放到延迟队列内,比如延迟30s然后发给死信队列,这个死信交换机不设置任何正常消费队列,死信消费者拿到消息后就判断支付状态,死信它只是充当一个定时器的功能
延迟消息插件
延迟消息插件可以让普通的交换机拥有延迟判断的能力,生产者可以发送一个定时的任务到交换机,交换机监听TTL过期后再把消息进行路由
【插件下载网址】
docker镜像使用:
bash
FROM rabbitmq:3-management
COPY rabbitmq_delayed_message_exchange-3.12.0.ez /opt/rabbitmq/plugins/
RUN rabbitmq-plugins enable rabbitmq_delayed_message_exchange
java使用:
java
/**
* 声明一个延迟消息交换机。
*
* CustomExchange 是 Spring AMQP 用于声明非标准交换机类型(如 x-delayed-message)的类。
* 构造参数依次为:
* 1. 交换机名称: "order.delayed.exchange"
* 2. 交换机类型: "x-delayed-message" → 启用 RabbitMQ 延迟消息插件
* 3. 是否持久化: true → 交换机在 Broker 重启后仍然存在
* 4. 是否自动删除: false → 当没有队列绑定时不会自动删除该交换机
* 5. 额外参数: args,其中 "x-delayed-type" 指定底层路由交换类型,这里设为 "direct",
* 表示该延迟交换机像一个 direct 交换机,根据路由键将消息投递到匹配的队列,
* 区别仅在于投递前会根据消息头 "x-delay" 的值(单位毫秒)进行延迟等待。
*/
@Bean
public Exchange delayedExchange() {
Map<String, Object> args = new HashMap<>();
args.put("x-delayed-type", "direct");
return new CustomExchange("order.delayed.exchange", "x-delayed-message", true, false, args);
}
生产者:
java
/**
* 使用 RabbitTemplate 向延迟交换机发送消息。
*
* convertAndSend 参数依次为:
* 1. 交换机名称: "order.delayed.exchange" → 目标延迟交换机
* 2. 路由键: "order.create" → 与交换机绑定的队列匹配
* 3. 消息体: order → 要发送的业务对象,会被序列化
* 4. MessagePostProcessor: 一个 lambda 表达式,用于在消息发送前修改消息的属性。
* msg.getMessageProperties().setDelay(30_000) 设置消息头 x-delay 为 30000 毫秒,
* 表示该消息将在延迟交换机中暂存 30 秒,然后才被投递到目标队列。
*/
rabbitTemplate.convertAndSend("order.delayed.exchange", "order.create", order, msg -> {
msg.getMessageProperties().setDelay(30_000);
return msg;
});
在真实的高并发环境下,比如亿级的并发量下,让一个订单每次创建加一个24小时的定时器会严重增加mq的cpu性能,因为他也是每秒都在计时的所以可以利用重发机制改成先10秒判断一次、然后30分钟、1小时、6小时。。。。24小时,因为大量用户都会在下单后固定时段内支付,所以这样做可以大量减少cpu消耗,但是最好的方法还是把定时逻辑拿出去给别的应用去干比如Redis ZSET什么的
java实现:
java
/**
* 订单超时检查消费者
*
* 消息体为 OrderDelayCheckMessage,包含订单ID、当前检查级别、已重试次数、最大重试次数等信息。
* 延迟交换机为 "order.delay.exchange",底层 direct 类型。
*/
@RabbitListener(queues = "order.delay.check.queue")
public void handleOrderDelayCheck(OrderDelayCheckMessage message, Message amqpMessage, Channel channel) {
try {
String orderId = message.getOrderId();
int retryCount = message.getRetryCount();
// 查询支付状态(伪代码)
boolean isPaid = paymentService.isOrderPaid(orderId);
if (isPaid) {
// 已支付,正常结束
channel.basicAck(amqpMessage.getMessageProperties().getDeliveryTag(), false);
return;
}
// 未支付,判断是否达到最大检查次数或超时
if (message.isMaxRetryReached()) {
// 最终确认超时,执行关单逻辑
orderService.cancelOrder(orderId);
channel.basicAck(amqpMessage.getMessageProperties().getDeliveryTag(), false);
return;
}
// 计算下一级延迟时间(毫秒)
long nextDelay = calculateNextDelay(retryCount);
// 重新发送到延迟交换机
OrderDelayCheckMessage nextMsg = message.nextRetry(); // 增加重试次数
rabbitTemplate.convertAndSend(
"order.delay.exchange",
"order.check",
nextMsg,
msg -> {
msg.getMessageProperties().setDelay((int) nextDelay);
return msg;
}
);
// 确认原消息(已经重新投递)
channel.basicAck(amqpMessage.getMessageProperties().getDeliveryTag(), false);
} catch (Exception e) {
// 异常处理:记录日志、拒绝重新入队或进入死信
channel.basicNack(amqpMessage.getMessageProperties().getDeliveryTag(), false, false);
}
}
/**
* 根据重试次数计算下一级延迟时间
* 检查间隔:10s, 1min, 30min, 1h, 4h, 8h, 16h, 24h(最后一级直接超时)
*/
private long calculateNextDelay(int retryCount) {
switch (retryCount) {
case 0: return 10_000L; // 10秒
case 1: return 60_000L; // 1分钟
case 2: return 1_800_000L; // 30分钟
case 3: return 3_600_000L; // 1小时
case 4: return 14_400_000L; // 4小时
case 5: return 28_800_000L; // 8小时
case 6: return 57_600_000L; // 16小时
case 7: return 86_400_000L; // 24小时(实际为最后一级,可以不重新发送,直接关单)
default: return 86_400_000L;
}
}
