前章内容在"苍穹外卖优化-续"笔记中
交换机
种类
RabbitMQ 官方提供了 4 种原生交换机类型,外加 1 种"兜底"的备用类型,一共 5 种:
类型常量(AMQP 协议名) | Spring 枚举类 | 中文习惯叫法 | 路由规则一句话 | 典型场景 |
---|---|---|---|---|
direct | DirectExchange | 直连交换机 | 路由键 完全匹配 才转发 | 单点精准投递(命令、通知) |
topic | TopicExchange | 主题交换机 | 路由键 通配符匹配 (* 单层,# 多层) |
多类消息分组订阅(日志、监控) |
fanout | FanoutExchange | 广播交换机 | 忽略路由键,直接复制到所有绑定队列 | 群发公告、配置刷新 |
headers | HeadersExchange | 头交换机 | 忽略 routingKey,按 消息头中的 KV 匹配 | 复杂多条件匹配(较少用) |
system 预留值 | 无 | 系统交换机 | 客户端不能声明,仅做内部回调 | 几乎见不到 |
1. direct(直连)
-
队列绑定到交换机时必须指定一个 精确的 routingKey。
-
消息只被转发到 routingKey 完全相同 的队列。
-
最简单、最快速,默认交换机 (名字为空字符串
""
)就是 direct 型,路由键=队列名。
2. topic(主题)
-
绑定队列时用 模式字符串 做 routingKey,支持 2 个通配符:
--
*
匹配 一个单词 (用.
分隔)--
#
匹配 零个或多个单词 -
可以实现"发布-订阅"的 多级分类过滤 ,例如
order.pay.success
、log.system.error
。
3. fanout(广播)
-
不处理 routingKey ,只要队列绑定了这个交换机,就 每人一份。
-
场景:配置刷新、价格推送、广告广播。
4. headers(头)
-
绑定队列时给一组
x-match=any/all
的 KV 条件;发消息时把条件写在 消息头 里。 -
路由键完全忽略,适合 多属性组合过滤;但性能比前三种差,生产环境用得最少。
5. system(系统)
- AMQP 协议保留值,客户端无法声明,可忽略。
速记口诀
direct 点对点,topic 带通配,fanout 全广播,headers 看头信息。
在 Spring AMQP 里分别对应:
DirectExchange
、TopicExchange
、FanoutExchange
、HeadersExchange
,用 BindingBuilder
链式绑定即可。
java
// new DirectExchange(交换机名, 是否持久化, 是否自动删除)
return new DirectExchange(RabbitConstant.EXCHANGE_ORDER,true,false);
配置选项
durable
场景 | durable=true | durable=false |
---|---|---|
RabbitMQ 正常跑着 | 交换机一直在,无差别 | 交换机一直在,无差别 |
RabbitMQ 服务重启 | 交换机会 自动重建 ,绑定关系丢失(需应用重新声明绑定) | 交换机 消失 ,必须等应用重新声明交换机 + 绑定,否则生产者投递会报错 NOT_FOUND |
控制台手动删除 | 重启后仍恢复(除非手动删) | 重启后不存在 |
autoDelete
1、当"没人用"时,是否自动把自己从 RabbitMQ 里删掉。
2、autoDelete
高于 durable
;就算 durable=true
,一旦触发"无人使用"条件,照样被删。
场景 | autoDelete=true | autoDelete=false |
---|---|---|
交换机 | 最后一个队列解绑 → 交换机立即删除 | 即使无队列绑定,仍保留 |
队列 | 最后一个消费者断开连接 → 队列立即删除 | 即使无消费者,仍保留 |
生产环境 :一律写 false
,防止重启后"找不到交换机/队列"而报错
临时/测试 :可设 true
,跑完自动清理,避免残留。
消息队列
创建
Spring AMQP 两种 Builder
方式 | 示例 | 特点 |
---|---|---|
QueueBuilder(推荐) | QueueBuilder.durable("q").ttl(60_000).build() |
链式、可读性高、支持所有参数 |
直接 new | new Queue("q", true, false, false, args) |
需手动拼 Map,底层相同 |
java
// new 写法
Queue q = new Queue("risk.calc.queue", // name
true, // durable
false, // exclusive
false, // autoDelete
null); // arguments
// Builder 写法(推荐)
Queue q = QueueBuilder
.durable("risk.calc.queue") // 等价 durable=true
.build();
必背参数
参数 | 类型 | 作用 | 常用值 |
---|---|---|---|
name | String | 队列名 | 业务语义,如 order.pay.queue |
durable | boolean | 重启后队列定义是否保留 | 生产 true |
exclusive | boolean | 仅当前连接可见,连接断即删 | 几乎永远 false (除 RPC Reply-To) |
autoDelete | boolean | 最后一个消费者断开后自动删 | 生产 false |
arguments | Map<String,Object> | 高级特性 | 见第 4 节 |
常用 x-* 扩展参数
参数 | 示例值 | 效果 |
---|---|---|
x-message-ttl |
60_000 |
消息入队后 60 s 未消费 → 自动丢弃或进死信 |
x-max-length |
1_000 |
队列最多 1 000 条,超限队头被丢弃或进死信 |
x-max-length-bytes |
10_485_760 |
队列总字节上限(10 MB) |
x-overflow |
"reject-publish" / "drop-head" |
达到上限后 拒绝新消息 或 删除老消息 |
x-dead-letter-exchange |
"dlx" |
死信交换机(DLX) |
x-dead-letter-routing-key |
"dlq.routing" |
死信消息的路由键 |
x-single-active-consumer |
true |
仅 一个消费者 能消费,故障自动切换 |
x-queue-type |
"quorum" / "classic" |
默认 classic ;quorum 为仲裁队列(高可用) |
x-max-priority |
10 |
开启 优先级队列,消息 0-10 级 |
x-queue-mode |
"lazy" |
消息直接落盘,百万级堆积场景 CPU 更平稳 |
x-queue-master-locator |
"min-masters" |
集群下选择 最少主节点 的机子建队 |
绑定
是什么
-
"桥" :把 Exchange 和 Queue 按 路由规则 连接起来;
-
消息能否到达队列,取决于有没有桥;
-
无绑定 → 消息直接丢弃 ,零报错。
参数
片段 | 含义 |
---|---|
.bind(queue) |
要投送的 队列 实例 |
.to(exchange) |
源 交换机 实例 |
.with(key) |
binding key(Direct/Topic) |
.noargs() |
Fanout 专用,无路由键 |
.where(...).match() |
Headers 专用,KV 匹配 |
核心 API
java
new BindingBuilder
.bind(Queue/Exchange) // 目标(队列或交换机)
.to(Exchange) // 源交换机
.with("routing.key") // 路由键/模式/条件
4 种交换机对应写法
交换机类型 | 路由规则 | 代码示例 |
---|---|---|
Direct | 完全匹配 | .with("email.sent") |
Topic | 通配符 | .with("notify.*.push") 或 .with("order.#") |
Fanout | 忽略键 | .noargs() (可省) |
Headers | KV 头 | .where("sys", "push").match() |
RabbitMQ 消费端"监听容器"
作用
-
MessageListenerContainer 是 Spring AMQP 的核心调度器
-
负责:
① 建立/保持 TCP 连接(Connection)
② 创建 Channel
③ 向 Broker 发送
basic.consume
④ 收到消息后反序列化 → 反射调注解方法
⑤ 处理 ACK、事务、并发、异常、重试
开启方式(二选一)
① 注解式(最常用)
java
@RabbitListener(queues = "order.queue", concurrency = "3-5")
public void handle(MessageEntity messageEntity) { ... }
-
一注解 ⇒ Spring 自动注册 SimpleMessageListenerContainer
-
concurrency
:核心-最大线程数(单队列并发)
@RabbitListener 生效的前提
1. 必须成为 Spring Bean
-
@RabbitListener
由RabbitListenerAnnotationBeanPostProcessor
扫描并注册监听容器 -
仅对 Spring 容器管理的 Bean 生效
→ 所在类必须加:
java
@Component / @Service / @Configuration / @RestController ...
② 显式 Bean(高级定制)
java
@Bean
public SimpleMessageListenerContainer container(ConnectionFactory cf,
MessageListenerAdapter listener) {
SimpleMessageListenerContainer c = new SimpleMessageListenerContainer(cf);
c.setQueueNames("order.queue");
c.setConcurrentConsumers(3);
c.setMaxConcurrentConsumers(5);
c.setDefaultRequeueRejected(false); // 拒收不再重排队
c.setAdviceChain(RetryInterceptorBuilder.stateless()
.maxAttempts(3)
.backOffOptions(1000, 2, 5000)
.build());
return c;
}
RabbitMQ 生产者
作用
-
负责 创建/发送 消息到 Exchange;
-
只关心 Exchange + RoutingKey ,不直接面对队列;
-
发送过程 = 序列化 → 设置属性 → 通道 basicPublish → Broker 路由 → 队列。
三种 API 风格
风格 | 示例 | 场景 |
---|---|---|
AmqpTemplate | template.convertAndSend("ex", "rk", obj) |
最早、最简 |
RabbitTemplate | 同上,功能最全 | 推荐(底层就是 AmqpTemplate 实现) |
StreamBridge (Spring Cloud Stream) | bridge.send("output", obj) |
屏蔽 MQ 差异,云原生 |
日常开发:RabbitTemplate 即可,Boot 自动配置单例。
自动配置速用
yaml
XML
spring:
rabbitmq:
host: 127.0.0.1
port: 5672
username: guest
password: guest
virtual-host: /
注入
java
@Autowired
private RabbitTemplate rabbitTemplate;
零配置直接注入即可发消息。或者自定义RabbitTemplate
最简发送(JSON 默认)
java
rabbitTemplate.convertAndSend(
"order.exchange", // exchange
"order.created", // routingKey
new MyMessage myMessage(...) // 任何 POJO
);
一张图流程
XML
业务Service
↓ convertAndSend
RabbitTemplate
↓ Jackson2JsonMessageConverter
JSON 字节 + MessageProperties
↓ Channel#basicPublish
Broker Exchange
↓ 路由
Queue → 消费者
RabbitMQ 双核心(生产者 、消费者)
作用
组件 | 作用域 | 核心职责 |
---|---|---|
RabbitTemplate | 生产者 | 序列化 → 发消息 → 发布确认/返回 |
SimpleRabbitListenerContainerFactory | 消费者 | 建容器 → 反序列化 → 调监听方法 → ACK/重试 |
两者完全独立:Template 的 Converter 不会自动透传给工厂。
RabbitTemplate 全景
1. 创建与注入
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory cf,
Jackson2JsonMessageConverter converter) {
RabbitTemplate tpl = new RabbitTemplate(cf);
tpl.setMessageConverter(converter); // ① 序列化
tpl.setConfirmCallback((cd, ack, cause) -> { ... }); // ② 发布确认
tpl.setReturnsCallback(returned -> { ... }); // ③ 不可路由
// 更多见下方
return tpl;
}
2. 关键 API
方法 | 场景 |
---|---|
convertAndSend(ex, rk, obj) |
单条即时发送 |
convertAndSend(ex, rk, obj, m -> {...}, correlationData) |
加 TTL/优先级/头 |
invoke(ops -> { ... waitForConfirmsOrDie(); }) |
批量+同步确认 |
3. 可靠性三件套
配置 | 说明 | 默认 |
---|---|---|
publisher-confirm-type: correlated |
异步等 Broker ack | none |
publisher-returns: true |
不可路由时回调 | false |
mandatory=true (单条)或 spring.rabbitmq.template.mandatory=true |
无队列可投立即 return | false |
4. 消息转换器
-
Jackson2JsonMessageConverter
支持 LocalDateTime、枚举、多态;需自定义 ObjectMapper 时:
java
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(new JavaTimeModule());
mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
return new Jackson2JsonMessageConverter(mapper);
5. 批量与异步
-
BatchingRabbitTemplate (Spring AMQP 3.x)
内存攒批 → 一次
basicPublish
→ 吞吐量 ↑ 10 倍 -
AsyncRabbitTemplate
返回
CompletableFuture
;适合高并发网关。
SimpleRabbitListenerContainerFactory 全景
1. 创建与注入
java
@Bean
public SimpleRabbitListenerContainerFactory factory(
ConnectionFactory cf,
Jackson2JsonMessageConverter converter) {
SimpleRabbitListenerContainerFactory f = new SimpleRabbitListenerContainerFactory();
f.setConnectionFactory(cf);
f.setMessageConverter(converter); // ① 反序列化
f.setAcknowledgeMode(AcknowledgeMode.MANUAL); // ② ACK 模式
f.setConcurrency("10-20"); // ③ 线程池
f.setPrefetchCount(1); // ④ 公平分发
f.setDefaultRequeueRejected(false); // ⑤ 死信开关
f.setAdviceChain(retryInterceptor()); // ⑥ 重试
return f;
}
2. ACK 模式对照
模式 | 行为 | 代码责任 |
---|---|---|
AUTO | 方法返回自动 basicAck |
无 |
MANUAL | 必须自己 basicAck/Nack |
有 |
NONE | 无 ACK,Broker 立即删 | 无 |
3. 并发与流量控制
参数 | 含义 | 建议 |
---|---|---|
concurrency = "10-20" |
初始 10 线程,峰值 20 | CPU 核心 × 2 |
prefetchCount = 1 |
单通道未 ack 上限 | 1(公平)/ 10-50(吞吐) |
4. 重试 & 死信
java
@Bean
public RetryOperationsInterceptor retryInterceptor() {
return RetryInterceptorBuilder.stateless()
.maxAttempts(3)
.backOffOptions(1000, 2, 5000)
.recoverer(new RejectAndDontRequeueRecoverer()) // 进 DLX
.build();
}
-
本地重试 (同线程,不重新入队)
-
耗尽后 →
RejectAndDontRequeueRecoverer
→basicReject(requeue=false)
→ DLX
5. 消息转换器注意事项
-
必须
factory.setMessageConverter(converter)
否则回退到
SimpleMessageConverter
→ JSON 反序列化失败 -
与 Template 用同一个 Bean 最简洁:(Spring Boot 的自动配置会自动把容器中唯一的
Jackson2JsonMessageConverter
Bean 注入给所有未指定 Converter 的容器工厂)
java
@Bean
public Jackson2JsonMessageConverter jsonConverter(){
return new Jackson2JsonMessageConverter(objectMapper());
}
常见误区
误区 | 正确做法 |
---|---|
只给 Template 设 Converter → 消费端转换失败 | 工厂也显式 setMessageConverter |
@RabbitListener 没写 ackMode="MANUAL" | 手动 ACK 必须显式指定 |
漏 basicAck → Unacked 堆积 | 方法尾必写 channel.basicAck(tag,false) |
default-requeue-rejected=true → 死循环 | 生产设为 false 配合 DLX |
concurrency 过大 → CPU 飙高 | 初始值 ≤ 核心数,峰值 2-3 倍 |
prefetch 过大 → 单节点内存爆 | 公平场景用 1,吞吐场景 10-50 |
总结
"发看 Template,收看工厂;
Converter 各设各,ACK 模式要手动;
重试+死信保可靠,监控 Unacked 防堆积!
springboot中使用是yml文件中的配置相关内容
|----------------------|---------------|--------|
| acknowledge-mode | manual
手动签收 | 业务可靠必开 |
XML
acknowledge-mode: manual # 关键
手动 ACK 三种操作
API | 语义 | 场景 |
---|---|---|
basicAck(deliveryTag, false) |
签收当前消息 | 正常处理完 |
basicNack(deliveryTag, false, true) |
拒绝并重新入队 | 瞬时异常可重试 |
basicNack(deliveryTag, false, false) |
拒绝不再入队 | 重试耗尽 → 死信 |
单条手动 ACK
java
@RabbitListener(queues = "order.queue", ackMode = "MANUAL")
public void handle(OrderEvent event, Channel channel,
@Header(AmqpHeaders.DELIVERY_TAG) long tag) {
try {
businessService.process(event);
channel.basicAck(tag, false); // 成功签收
} catch (BizException e) {
// 业务规则失败,不再重试
channel.basicNack(tag, false, false); // 直接进死信
} catch (Exception e) {
// 系统异常 → 本地重试(Spring Retry 3 次)仍失败
channel.basicNack(tag, false, false); // 进死信
}
}
2. 批量 ACK(高性能)
java
@RabbitListener(queues = "batch.queue", ackMode = "MANUAL")
public void handleBatch(List<Message> messages, Channel channel) {
long lastTag = messages.get(messages.size() - 1).getMessageProperties()
.getDeliveryTag();
try {
businessService.processBatch(messages);
channel.basicAck(lastTag, true); // 批量确认到 lastTag
} catch (Exception e) {
channel.basicNack(lastTag, true, false); // 整批进死信
}
}
✅ acknowledge-mode: manual
✅ 消费方法一定写 basicAck
/ basicNack
✅ default-requeue-rejected: false
(防死循环)
✅ 开启 死信交换机 承接 Nack 消息
✅ prefetch=1
或按并发调整
✅ 记录 deliveryTag 原值传回
✅ 监控 Unacked 指标告警
"manual = 自己签快递
不 ack 就永远占地方,重试耗尽进死信,
监控 Unacked > 0 立刻排查!"