rabbitMQ续谈

前章内容在"苍穹外卖优化-续"笔记中

交换机

种类

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.successlog.system.error

3. fanout(广播)

  • 不处理 routingKey ,只要队列绑定了这个交换机,就 每人一份

  • 场景:配置刷新、价格推送、广告广播。

4. headers(头)

  • 绑定队列时给一组 x-match=any/all 的 KV 条件;发消息时把条件写在 消息头 里。

  • 路由键完全忽略,适合 多属性组合过滤;但性能比前三种差,生产环境用得最少。

5. system(系统)

  • AMQP 协议保留值,客户端无法声明,可忽略

速记口诀

direct 点对点,topic 带通配,fanout 全广播,headers 看头信息。

在 Spring AMQP 里分别对应:
DirectExchangeTopicExchangeFanoutExchangeHeadersExchange,用 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" 默认 classicquorum 为仲裁队列(高可用)
x-max-priority 10 开启 优先级队列,消息 0-10 级
x-queue-mode "lazy" 消息直接落盘,百万级堆积场景 CPU 更平稳
x-queue-master-locator "min-masters" 集群下选择 最少主节点 的机子建队

绑定

是什么

  • "桥" :把 ExchangeQueue路由规则 连接起来;

  • 消息能否到达队列,取决于有没有桥

  • 无绑定 → 消息直接丢弃零报错

参数

片段 含义
.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
  • @RabbitListenerRabbitListenerAnnotationBeanPostProcessor 扫描并注册监听容器

  • 仅对 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();
}
  • 本地重试 (同线程,不重新入队

  • 耗尽后 → RejectAndDontRequeueRecovererbasicReject(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 立刻排查!"

相关推荐
用户8307196840821 天前
RabbitMQ vs RocketMQ 事务大对决:一个在“裸奔”,一个在“开挂”?
后端·rabbitmq·rocketmq
初次攀爬者2 天前
RabbitMQ的消息模式和高级特性
后端·消息队列·rabbitmq
初次攀爬者4 天前
ZooKeeper 实现分布式锁的两种方式
分布式·后端·zookeeper
让我上个超影吧5 天前
消息队列——RabbitMQ(高级)
java·rabbitmq
塔中妖5 天前
Windows 安装 RabbitMQ 详细教程(含 Erlang 环境配置)
windows·rabbitmq·erlang
断手当码农5 天前
Redis 实现分布式锁的三种方式
数据库·redis·分布式
初次攀爬者5 天前
Redis分布式锁实现的三种方式-基于setnx,lua脚本和Redisson
redis·分布式·后端
业精于勤_荒于稀5 天前
物流订单系统99.99%可用性全链路容灾体系落地操作手册
分布式
Ronin3055 天前
信道管理模块和异步线程模块
开发语言·c++·rabbitmq·异步线程·信道管理
Asher05095 天前
Hadoop核心技术与实战指南
大数据·hadoop·分布式