前章内容在"苍穹外卖优化-续"笔记中
交换机
种类
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 的自动配置会自动把容器中唯一的 Jackson2JsonMessageConverterBean 注入给所有未指定 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 立刻排查!"