RabbitMQ的消息模式和高级特性

RabbitMQ的消息模式和高级特性

一、RabbitMQ 概述

RabbitMQ 是基于 AMQP (高级消息队列协议)实现的开源消息中间件,采用 Erlang 语言开发。其核心作用是实现系统间的异步通信解耦削峰填谷。它广泛应用于分布式系统、微服务架构中,作为连接不同服务的"神经中枢",有效避免因系统间直接调用导致的耦合过高、峰值压力过大及单点故障问题。

1.1 适用场景

  • 订单处理:用户下单后,订单系统发送消息,库存系统、积分系统、物流系统并行处理
  • 日志采集:多服务日志统一发送到日志队列,由日志中心异步处理
  • 邮件/短信通知:注册成功、密码找回等非即时性通知,异步发送
  • 数据同步:主数据变更后发送消息,通知各业务系统更新缓存或索引

二、RabbitMQ 核心作用

  • 解耦:打破系统间直接调用的依赖。生产者只需发送消息,无需关心消费者的实现及状态;消费者只需监听消息,无需与生产者直接交互。降低系统耦合度,便于后续扩展和维护。
  • 异步通信:生产者发送消息后无需等待消费者处理完成,可立即返回,显著提升系统响应速度(吞吐量)。消费者在后台异步处理消息,避免同步调用导致的线程阻塞。
  • 削峰填谷:当系统面临突发流量(如秒杀、大促)时,消息队列可暂存大量请求,消费者按自身处理能力逐步消费,避免后端数据库或服务因瞬间压力过大而崩溃。
  • 消息缓冲:当下游服务暂时不可用(如重启、故障、网络波动)时,消息可在队列中持久化暂存,待服务恢复后继续消费,保证数据不丢失,维持最终一致性。
  • 流量控制:通过调节消费者并发数量或预取计数(Prefetch Count),控制消息处理节奏,防止下游服务过载。
  • 顺序保证:单队列下可保证消息顺序性,满足特定业务需求(如状态机变更)。
  • 最终一致性:结合本地事务和消息表,可实现分布式事务的最终一致性。

三、RabbitMQ 的优点与缺点

3.1 优点

  • 高可靠性:支持消息持久化、事务机制、多种确认机制(Publisher Confirm、Consumer ACK)、死信队列(DLX)等,确保消息在极端情况下不丢失。
  • 灵活路由:提供多种交换机类型(Direct, Fanout, Topic, Headers),支持复杂的路由规则,满足多样化的业务分发需求。
  • 多语言支持:提供 Java, Python, Go, .NET, Node.js 等几乎所有主流语言的客户端库,集成成本低。
  • 管理便捷:内置强大的 Web 管理界面,可实时监控队列长度、连接数、消息速率,并支持在线配置交换机和队列。
  • 插件生态 :拥有丰富的插件体系(如 rabbitmq_delayed_message_exchange 实现延迟队列,rabbitmq_shovel 实现数据同步,rabbitmq_federation 实现跨集群同步),可扩展性强。
  • 社区活跃:作为老牌开源项目,文档齐全,Stack Overflow 和 GitHub 上问题解决方案丰富,学习资源充足。
  • 支持多种协议:原生支持 AMQP,通过插件支持 MQTT、STOMP,满足物联网和 WebSocket 场景。
  • 可视化运维:Web 管理界面功能强大,可在线创建队列、交换机,查看消息速率,管理用户权限,无需编写脚本即可完成大部分运维操作。

3.2 缺点

  • 吞吐量瓶颈:相比 Kafka 等日志型消息队列,RabbitMQ 的吞吐量较低(通常在万级到十万级 TPS),不适合海量日志采集(日均 TB 级别)或大数据流处理场景。
  • Erlang 门槛:底层基于 Erlang 开发,若需进行深度定制、源码级排查或二次开发,学习曲线较陡峭。大部分运维和开发人员不具备 Erlang 背景,排查深层次问题时可能遇到困难。
  • 集群运维复杂:虽然支持集群,但镜像队列(Mirrored Queues)配置、脑裂处理及版本升级对运维人员要求较高。集群扩容时需要谨慎操作,可能导致服务抖动。
  • 资源消耗:大量未消费的消息堆积会占用大量内存和磁盘空间,若未合理配置 TTL 或配额,可能导致节点 OOM 或磁盘写满宕机。
  • 消息顺序性保障较弱:多消费者并发消费时无法保证顺序,若需严格顺序需使用单消费者或分区(需借助插件)。
  • 分布式事务支持有限:RabbitMQ 本身不提供完整的分布式事务方案,需借助本地事务表+重试机制实现最终一致性。

四、RabbitMQ 核心组件详解

4.1 生产者(Producer)

消息的发送方。负责创建消息(包含消息体、属性、路由键),建立连接与通道,将消息投递至交换机,并处理发送确认(Confirm)以确保消息到达 Broker。

最佳实践

  • 生产者应复用连接和通道,避免频繁创建销毁
  • 高并发场景下建议启用 Publisher Confirm 确保消息不丢
  • 对于重要业务消息,建议设置消息持久化并配合 Confirm 机制

4.2 通道(Channel)

建立在 TCP 连接之上的虚拟连接。由于 TCP 连接创建和销毁开销大,RabbitMQ 推荐复用 TCP 连接,在其上创建多个 Channel 进行并发通信。Channel 是线程不安全的,多线程环境下需为每个线程创建独立的 Channel,或使用线程池管理。

注意事项

  • 单个连接可创建数百个 Channel,但不宜过多(建议 ≤ 100)
  • Channel 使用完毕后应关闭释放资源,但在复用连接的模式下可缓存复用
  • Spring AMQP 的 CachingConnectionFactory 已封装好 Channel 的缓存管理

4.3 交换机(Exchange)

消息的"路由器"。接收生产者消息,根据绑定键(Binding Key)路由算法将消息分发到队列。交换机属性包括:名称、持久化、自动删除、内部(Internal,仅用于交换机间绑定)。

4.3.1 交换机类型详解
类型 匹配规则 适用场景 性能 备注
Direct Exchange 精确匹配 Routing Key 任务分发、日志级别分类 最常用,简单直接
Fanout Exchange 广播所有队列 事件广播、配置更新 最高 忽略 Routing Key
Topic Exchange 通配符匹配:*(一个单词),#(零或多个单词) 复杂的消息路由、按维度过滤 最灵活,规则丰富
Headers Exchange 根据消息头属性匹配(x-match: all/any) 多条件过滤 性能较差,少用

默认交换机(Default Exchange):RabbitMQ 预置的 Direct 类型交换机,名称是空字符串。所有队列都会自动绑定到该交换机,Binding Key 即为队列名称。生产者发送消息时指定 routingKey 为队列名,即可直接发送到队列,无需显式绑定。

4.4 队列(Queue)

消息的缓冲区。存储被路由过来的消息,直到被消费者消费。队列属性包括:

  • 持久化(Durable):队列元数据存盘,Broker 重启后队列不丢失。注意:队列持久化不保证消息不丢失,消息本身也需设置持久化。
  • 独占(Exclusive):仅当前连接可见,连接断开后自动删除。通常用于临时队列。
  • 自动删除(Auto-delete):当最后一个消费者断开后自动删除。
  • 消息 TTL:设置消息在队列中的存活时间,过期自动丢弃或进入死信队列。
  • 队列 TTL:队列空闲指定时间后自动删除。
  • 最大长度:队列可容纳的最大消息数或最大字节数,超限后根据策略丢弃或进入死信。
  • 溢出策略drop-head(丢弃头部)、reject-publish(拒绝新消息)。
  • 惰性队列(Lazy Queue):RabbitMQ 3.6+ 引入,消息直接写入磁盘,减少内存消耗,适用于长队列场景。

4.5 消费者(Consumer)

消息的接收方。订阅队列,拉取或推送接收消息,处理业务逻辑后发送确认(ACK)。若未 ACK 且连接断开,消息会重新入队。

消费模式

  • 推模式(Push) :订阅队列后,Broker 主动推送消息,需实现 Consumer 接口或使用 @RabbitListener。实时性好。
  • 拉模式(Pull) :主动调用 basicGet 拉取消息,类似轮询。适用于批量处理或控制消费速率。

4.6 虚拟主机(Virtual Host)

逻辑隔离单元。类似于 MySQL 中的 Database,用于隔离不同项目或环境的交换机、队列和用户权限,默认名为 /。不同 VHost 间完全隔离,名称空间独立。

最佳实践

  • 按环境划分:/dev/test/prod
  • 按业务线划分:/order/user/payment
  • 每个 VHost 分配独立用户,最小权限原则

4.7 绑定(Binding)

交换机和队列之间的关联关系。绑定包含 Binding Key,决定了消息如何从交换机路由到队列。一个队列可绑定多个交换机,一个交换机也可绑定多个队列。


五、RabbitMQ 核心消息模式

结合 Spring Boot (Spring AMQP) 框架,提供六种核心模式的完整代码示例。

前置准备

环境要求

  • JDK 8+
  • Spring Boot 2.x
  • RabbitMQ Server 3.8+(建议 3.9+)

Maven 依赖 (pom.xml):

xml 复制代码
<dependencies>
    <!-- Spring Boot Starter for RabbitMQ -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-amqp</artifactId>
    </dependency>
    <!-- 测试依赖 -->
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
    </dependency>
    <!-- JSON 序列化(可选) -->
    <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-databind</artifactId>
    </dependency>
</dependencies>

基础配置 (application.yml):

yaml 复制代码
spring:
  rabbitmq:
    host: localhost
    port: 5672
    username: guest
    password: guest
    virtual-host: /
    # 连接池配置
    connection-timeout: 15000
    # 生产者确认
    publisher-confirm-type: correlated  # 开启 Confirm 回调
    publisher-returns: true             # 开启 Return 回调
    template:
      mandatory: true                   # 开启强制回调
    # 消费者配置
    listener:
      simple:
        acknowledge-mode: manual        # 手动 ACK
        prefetch: 1                     # 预取数量
        concurrency: 1                   # 最小消费者数
        max-concurrency: 5               # 最大消费者数
        retry:
          enabled: true                  # 开启重试
          max-attempts: 3                # 最大重试次数
          initial-interval: 1000         # 重试间隔

5.1 简单模式 (Simple Mode)

原理:无交换机(使用 Default Exchange),生产者直接发送消息到指定队列,一对一消费。

1. 配置类
java 复制代码
@Configuration
public class SimpleConfig {
    public static final String SIMPLE_QUEUE = "simple.queue";

    @Bean
    public Queue simpleQueue() {
        // durable=true: 队列持久化,重启后存在
        // exclusive=false: 非独占
        // auto-delete=false: 不自动删除
        return QueueBuilder.durable(SIMPLE_QUEUE).build();
    }
}
2. 生产者
java 复制代码
@Component
@Slf4j
public class SimpleProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String msg) {
        // 默认使用 default exchange,routingKey 即为队列名
        rabbitTemplate.convertAndSend(SimpleConfig.SIMPLE_QUEUE, msg);
        log.info(" [x] Sent: {}", msg);
    }
    
    // 发送带自定义属性的消息
    public void sendWithProperties(String msg) {
        MessageProperties properties = new MessageProperties();
        properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化
        properties.setContentType("text/plain");
        properties.setMessageId(UUID.randomUUID().toString());
        
        Message message = new Message(msg.getBytes(StandardCharsets.UTF_8), properties);
        rabbitTemplate.send(SimpleConfig.SIMPLE_QUEUE, message);
        log.info(" [x] Sent with properties: {}", msg);
    }
}
3. 消费者
java 复制代码
@Component
@Slf4j
public class SimpleConsumer {
    
    @RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
    public void receive(String msg) {
        log.info(" [.] Received: {}", msg);
        // 业务逻辑处理...
        // 默认自动 ACK,方法执行完毕即 ACK
    }
    
    // 手动 ACK 示例(需配置 acknowledge-mode: manual)
    @RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
    public void receiveManual(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        
        try {
            log.info(" [.] Received: {}", msg);
            // 业务处理...
            
            // 手动确认
            channel.basicAck(deliveryTag, false);
            log.debug(" [.] Acked: {}", deliveryTag);
        } catch (Exception e) {
            log.error("Process failed: {}", msg, e);
            // 拒绝并重新入队(requeue=true)
            channel.basicNack(deliveryTag, false, true);
            // 或拒绝并不重新入队(进入死信)
            // channel.basicNack(deliveryTag, false, false);
        }
    }
}
4. 测试类
java 复制代码
@SpringBootTest
public class SimpleModeTest {
    @Autowired
    private SimpleProducer producer;
    
    @Test
    public void testSend() throws InterruptedException {
        producer.send("Hello RabbitMQ!");
        producer.sendWithProperties("Message with properties");
        
        // 等待消费者处理
        Thread.sleep(2000);
    }
}

5.2 工作队列模式 (Work Queue Mode)

原理 :多个消费者监听同一队列,消息轮询分发。需开启手动 ACK预取限制以实现公平分发(能者多劳)。

1. 配置文件(已在基础配置中开启)

acknowledge-mode: manualprefetch: 1 已在 application.yml 中配置。

2. 消费者 A (慢速处理)
java 复制代码
@Component
@Slf4j
public class WorkConsumerA {
    
    @RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
    public void receive(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        
        log.info(" [Worker A] Received: {}", msg);
        
        try {
            // 模拟耗时操作
            Thread.sleep(3000);
            
            // 业务处理...
            
            // 手动 ACK
            channel.basicAck(deliveryTag, false);
            log.info(" [Worker A] Done & ACKed: {}", deliveryTag);
        } catch (Exception e) {
            log.error(" [Worker A] Processing failed: {}", msg, e);
            // 处理失败,拒绝消息且不重新入队 (requeue=false)
            // 可根据业务决定是否 requeue,避免无限循环
            channel.basicNack(deliveryTag, false, false);
            log.info(" [Worker A] Nacked, not requeue");
        }
    }
}
3. 消费者 B (快速处理)
java 复制代码
@Component
@Slf4j
public class WorkConsumerB {
    
    @RabbitListener(queues = SimpleConfig.SIMPLE_QUEUE)
    public void receive(Message message, Channel channel) throws Exception {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        
        log.info(" [Worker B] Received: {}", msg);
        
        try {
            // 模拟快速处理
            Thread.sleep(500);
            
            // 业务处理...
            
            channel.basicAck(deliveryTag, false);
            log.info(" [Worker B] Done & ACKed: {}", deliveryTag);
        } catch (Exception e) {
            log.error(" [Worker B] Processing failed: {}", msg, e);
            channel.basicNack(deliveryTag, false, false);
        }
    }
}
4. 批量发送测试
java 复制代码
@Component
public class WorkQueueTest {
    @Autowired
    private SimpleProducer producer;
    
    @PostConstruct
    public void testWorkQueue() {
        // 模拟发送 10 条消息
        for (int i = 1; i <= 10; i++) {
            producer.send("Task " + i + ": " + LocalDateTime.now());
            try {
                Thread.sleep(100); // 间隔 100ms
            } catch (InterruptedException ignored) {}
        }
    }
}

由于 prefetch=1,消费者 B 处理快会消费更多消息,实现"能者多劳"。


5.3 发布订阅模式 (Publish/Subscribe)

原理 :使用 Fanout Exchange 。消息广播给所有绑定该交换机的队列。通常队列是临时且独占的,确保每个消费者实例都能收到全量消息。

1. 配置类
java 复制代码
@Configuration
public class FanoutConfig {
    public static final String FANOUT_EXCHANGE = "fanout.exchange";
    
    @Bean
    public FanoutExchange fanoutExchange() {
        // durable=true: 交换机持久化
        // auto-delete=false: 不自动删除
        return ExchangeBuilder.fanoutExchange(FANOUT_EXCHANGE)
                .durable(true)
                .build();
    }
    
    // 也可以不定义具体队列,由消费者动态创建
}
2. 生产者
java 复制代码
@Component
@Slf4j
public class FanoutProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void send(String msg) {
        // 第二个参数 routingKey 在 Fanout 模式下被忽略,通常传空字符串
        rabbitTemplate.convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", msg);
        log.info(" [x] Sent Broadcast: {}", msg);
    }
    
    public void sendEvent(String eventType, Object payload) {
        // 发送 JSON 格式事件
        Map<String, Object> event = new HashMap<>();
        event.put("type", eventType);
        event.put("data", payload);
        event.put("timestamp", Instant.now().toEpochMilli());
        
        rabbitTemplate.convertAndSend(FanoutConfig.FANOUT_EXCHANGE, "", event);
        log.info(" [x] Sent Event: {} - {}", eventType, payload);
    }
}
3. 消费者 A
java 复制代码
@Component
@Slf4j
public class FanoutConsumerA {
    
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
        exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
    ))
    public void receive(String msg) {
        log.info(" [Consumer A] Received: {}", msg);
    }
    
    // 接收 Map 类型
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
        exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
    ))
    public void receiveEvent(Map<String, Object> event) {
        log.info(" [Consumer A] Received Event: {}", event);
    }
}
4. 消费者 B
java 复制代码
@Component
@Slf4j
public class FanoutConsumerB {
    
    @RabbitListener(bindings = @QueueBinding(
        value = @Queue(value = "", durable = "false", exclusive = "true", autoDelete = "true"),
        exchange = @Exchange(value = FanoutConfig.FANOUT_EXCHANGE, type = ExchangeTypes.FANOUT)
    ))
    public void receive(String msg) {
        log.info(" [Consumer B] Received: {}", msg);
    }
}
5. 测试
java 复制代码
@SpringBootTest
public class FanoutModeTest {
    @Autowired
    private FanoutProducer producer;
    
    @Test
    public void testBroadcast() throws InterruptedException {
        producer.send("System configuration updated!");
        producer.sendEvent("USER_REGISTER", Map.of("userId", 1001, "username", "张三"));
        
        // 所有消费者都会收到相同消息
        Thread.sleep(2000);
    }
}

5.4 路由模式 (Routing Mode)

原理 :使用 Direct Exchange 。Routing Key 必须与 Binding Key 完全匹配

1. 配置类
java 复制代码
@Configuration
public class DirectConfig {
    public static final String DIRECT_EXCHANGE = "direct.exchange";
    public static final String QUEUE_INFO = "queue.info";
    public static final String QUEUE_WARN = "queue.warn";
    public static final String QUEUE_ERROR = "queue.error";

    @Bean
    public DirectExchange directExchange() {
        return ExchangeBuilder.directExchange(DIRECT_EXCHANGE)
                .durable(true)
                .build();
    }

    @Bean
    public Queue infoQueue() { 
        return QueueBuilder.durable(QUEUE_INFO).build(); 
    }
    
    @Bean
    public Queue warnQueue() { 
        return QueueBuilder.durable(QUEUE_WARN).build(); 
    }
    
    @Bean
    public Queue errorQueue() { 
        return QueueBuilder.durable(QUEUE_ERROR).build(); 
    }

    @Bean
    public Binding bindingInfo() {
        return BindingBuilder.bind(infoQueue())
                .to(directExchange())
                .with("info");
    }

    @Bean
    public Binding bindingWarn() {
        return BindingBuilder.bind(warnQueue())
                .to(directExchange())
                .with("warn");
    }

    @Bean
    public Binding bindingError() {
        return BindingBuilder.bind(errorQueue())
                .to(directExchange())
                .with("error");
    }
    
    // 也可以让 error 队列同时接收 error 和 fatal 级别
    @Bean
    public Binding bindingFatal() {
        return BindingBuilder.bind(errorQueue())
                .to(directExchange())
                .with("fatal");
    }
}
2. 生产者
java 复制代码
@Component
@Slf4j
public class DirectProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String level, String msg) {
        // routingKey 为 "info"、"warn"、"error"、"fatal" 等
        rabbitTemplate.convertAndSend(DirectConfig.DIRECT_EXCHANGE, level, msg);
        log.info(" [x] Sent [{}]: {}", level, msg);
    }
    
    public void sendLog(LogEntry logEntry) {
        rabbitTemplate.convertAndSend(
            DirectConfig.DIRECT_EXCHANGE, 
            logEntry.getLevel(), 
            logEntry
        );
        log.info(" [x] Sent Log [{}]: {}", logEntry.getLevel(), logEntry.getMessage());
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class LogEntry {
        private String level;
        private String message;
        private String service;
        private long timestamp;
    }
}
3. 消费者
java 复制代码
@Component
@Slf4j
public class DirectConsumer {
    
    @RabbitListener(queues = DirectConfig.QUEUE_INFO)
    public void receiveInfo(String msg) {
        log.info(" [Info Log] Received: {}", msg);
    }
    
    @RabbitListener(queues = DirectConfig.QUEUE_INFO)
    public void receiveInfo(DirectProducer.LogEntry logEntry) {
        log.info(" [Info Log] {} - {}", logEntry.getService(), logEntry.getMessage());
    }

    @RabbitListener(queues = DirectConfig.QUEUE_WARN)
    public void receiveWarn(String msg) {
        log.info(" [Warn Log] Received: {}", msg);
    }

    @RabbitListener(queues = DirectConfig.QUEUE_ERROR)
    public void receiveError(String msg) {
        log.info(" [Error Log] Received: {}", msg);
    }
}
4. 测试
java 复制代码
@SpringBootTest
public class DirectModeTest {
    @Autowired
    private DirectProducer producer;
    
    @Test
    public void testRouting() throws InterruptedException {
        producer.send("info", "User login success");
        producer.send("warn", "Slow database query detected");
        producer.send("error", "Payment service timeout");
        producer.send("fatal", "Database connection lost"); // error 队列也会收到
        
        Thread.sleep(2000);
    }
}

5.5 主题模式 (Topic Mode)

原理 :使用 Topic Exchange 。支持通配符 *(匹配一个单词)和 #(匹配零个或多个单词)。单词由点号分隔。

通配符规则

  • *:匹配一个单词,如 user.* 匹配 user.infouser.error,不匹配 user.info.detail
  • #:匹配零个或多个单词,如 log.# 匹配 loglog.infolog.error.db
  • 示例:*.error.# 匹配 user.errorsystem.error.db.timeout
1. 配置类
java 复制代码
@Configuration
public class TopicConfig {
    public static final String TOPIC_EXCHANGE = "topic.exchange";
    public static final String QUEUE_ALL = "queue.all";      // 接收所有日志
    public static final String QUEUE_ERROR = "queue.error";  // 仅接收 error 级别
    public static final String QUEUE_USER = "queue.user";    // 接收用户相关日志

    @Bean
    public TopicExchange topicExchange() {
        return ExchangeBuilder.topicExchange(TOPIC_EXCHANGE)
                .durable(true)
                .build();
    }

    @Bean
    public Queue allQueue() { 
        return QueueBuilder.durable(QUEUE_ALL).build(); 
    }
    
    @Bean
    public Queue errorQueue() { 
        return QueueBuilder.durable(QUEUE_ERROR).build(); 
    }
    
    @Bean
    public Queue userQueue() { 
        return QueueBuilder.durable(QUEUE_USER).build(); 
    }

    // log.# 匹配所有以 log. 开头的 routingKey
    @Bean
    public Binding bindingAll() {
        return BindingBuilder.bind(allQueue())
                .to(topicExchange())
                .with("log.#");
    }

    // *.error 匹配 user.error、system.error,不匹配 log.error.info
    @Bean
    public Binding bindingError() {
        return BindingBuilder.bind(errorQueue())
                .to(topicExchange())
                .with("*.error");
    }
    
    // user.* 匹配 user.info、user.error、user.debug
    @Bean
    public Binding bindingUser() {
        return BindingBuilder.bind(userQueue())
                .to(topicExchange())
                .with("user.*");
    }
    
    // 也可同时匹配多个模式
    @Bean
    public Binding bindingErrorUser() {
        return BindingBuilder.bind(errorQueue())
                .to(topicExchange())
                .with("user.error");
    }
}
2. 生产者
java 复制代码
@Component
@Slf4j
public class TopicProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;

    public void send(String routingKey, String msg) {
        rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
        log.info(" [x] Sent [{}]: {}", routingKey, msg);
    }
    
    public void sendSystemLog(String module, String level, String msg) {
        String routingKey = String.format("system.%s.%s", module, level);
        rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
        log.info(" [x] Sent [{}]: {}", routingKey, msg);
    }
    
    public void sendUserLog(String action, String level, String msg) {
        String routingKey = String.format("user.%s.%s", action, level);
        rabbitTemplate.convertAndSend(TopicConfig.TOPIC_EXCHANGE, routingKey, msg);
        log.info(" [x] Sent [{}]: {}", routingKey, msg);
    }
}
3. 消费者
java 复制代码
@Component
@Slf4j
public class TopicConsumer {
    
    @RabbitListener(queues = TopicConfig.QUEUE_ALL)
    public void receiveAll(String msg) {
        log.info(" [All Logger] {}", msg);
    }
    
    @RabbitListener(queues = TopicConfig.QUEUE_ERROR)
    public void receiveError(String msg) {
        log.info(" [Error Logger] {}", msg);
    }
    
    @RabbitListener(queues = TopicConfig.QUEUE_USER)
    public void receiveUser(String msg) {
        log.info(" [User Logger] {}", msg);
    }
}
4. 测试
java 复制代码
@SpringBootTest
public class TopicModeTest {
    @Autowired
    private TopicProducer producer;
    
    @Test
    public void testTopic() throws InterruptedException {
        // 发送不同 routingKey 的消息
        producer.send("log.info", "System started");          // all 收到
        producer.send("log.error", "DB connection failed");   // all 收到
        producer.send("user.error", "User login failed");     // all、error、user 都收到
        producer.send("user.info", "User profile updated");   // all、user 收到
        producer.send("system.error", "CPU overload");        // all、error 收到
        producer.send("payment.success", "Order paid");       // 无队列匹配,消息丢失(需配置备份交换机)
        
        Thread.sleep(3000);
    }
}

5.6 RPC 模式 (RPC Mode)

原理 :客户端发送请求并携带 replyTo(回调队列)和 correlationId,服务端处理后回复。Spring AMQP 提供了 RabbitTemplateconvertSendAndReceive 方法封装这一模式。

1. 配置类
java 复制代码
@Configuration
public class RpcConfig {
    public static final String RPC_QUEUE = "rpc.request.queue";
    public static final String RPC_EXCHANGE = ""; // 使用默认交换机
    
    @Bean
    public Queue rpcQueue() {
        return QueueBuilder.durable(RPC_QUEUE).build();
    }
    
    // 配置 RabbitTemplate 支持 RPC
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        // 设置 reply 超时时间(毫秒)
        template.setReplyTimeout(60000);
        // 设置默认的 reply 队列(可选)
        template.setReplyAddress(RPC_QUEUE + ".reply");
        return template;
    }
}
2. 服务端 (Server)
java 复制代码
@Component
@Slf4j
public class RpcServer {
    
    @RabbitListener(queues = RpcConfig.RPC_QUEUE)
    public String process(String message) {
        log.info(" [Server] Received request: {}", message);
        
        // 模拟业务处理
        String result = handleRequest(message);
        
        log.info(" [Server] Returning response: {}", result);
        return result; // 返回值会自动发送到 replyTo 队列
    }
    
    // 复杂处理示例,访问 correlationId 和 replyTo
    @RabbitListener(queues = RpcConfig.RPC_QUEUE)
    public void processComplex(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String correlationId = message.getMessageProperties().getCorrelationId();
        String replyTo = message.getMessageProperties().getReplyTo();
        String request = new String(message.getBody(), StandardCharsets.UTF_8);
        
        log.info(" [Server] Received: {}, correlationId: {}, replyTo: {}", request, correlationId, replyTo);
        
        try {
            String result = "Processed: " + request;
            
            // 手动发送响应
            MessageProperties replyProps = new MessageProperties();
            replyProps.setCorrelationId(correlationId);
            Message reply = new Message(result.getBytes(StandardCharsets.UTF_8), replyProps);
            
            channel.basicPublish("", replyTo, null, reply.getBody());
            channel.basicAck(deliveryTag, false);
            
            log.info(" [Server] Replied: {}", result);
        } catch (Exception e) {
            log.error("Processing error", e);
            channel.basicNack(deliveryTag, false, true);
        }
    }
    
    private String handleRequest(String request) {
        // 模拟计算或数据库查询
        try {
            Thread.sleep(1000); // 模拟耗时
        } catch (InterruptedException ignored) {}
        return "Result for: " + request;
    }
}
3. 客户端 (Client)
java 复制代码
@Component
@Slf4j
public class RpcClient {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 同步调用 RPC 服务
     */
    public String call(String message) {
        log.info(" [Client] Sending request: {}", message);
        
        // convertSendAndReceive 自动处理回调队列和 correlationId
        // 参数:exchange, routingKey, message, correlationData
        String response = (String) rabbitTemplate.convertSendAndReceive(
            "", // 默认交换机
            RpcConfig.RPC_QUEUE,
            message
        );
        
        log.info(" [Client] Received response: {}", response);
        return response;
    }
    
    /**
     * 异步调用,带回调
     */
    public void callAsync(String message, RpcCallback callback) {
        // 发送消息
        rabbitTemplate.convertAndSend("", RpcConfig.RPC_QUEUE, message, m -> {
            // 设置消息属性
            m.getMessageProperties().setReplyTo(RpcConfig.RPC_QUEUE + ".reply");
            m.getMessageProperties().setCorrelationId(UUID.randomUUID().toString());
            return m;
        });
        
        // 监听响应(需单独实现一个消费者监听 reply 队列)
        // 这里简化,实际项目中可使用 CompletableFuture
        log.info(" [Client] Async request sent: {}", message);
    }
    
    public interface RpcCallback {
        void onSuccess(String result);
        void onFailure(Throwable t);
    }
}

// 使用 CompletableFuture 的高级版本
@Component
public class AsyncRpcClient {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    private final ConcurrentHashMap<String, CompletableFuture<String>> pendingRequests = new ConcurrentHashMap<>();
    
    @PostConstruct
    public void init() {
        // 启动时创建 reply 队列监听器
        rabbitTemplate.execute(channel -> {
            String replyQueue = channel.queueDeclare().getQueue();
            // 这里简化,实际需绑定消费者到 replyQueue
            return null;
        });
    }
    
    public CompletableFuture<String> callAsync(String message) {
        CompletableFuture<String> future = new CompletableFuture<>();
        String correlationId = UUID.randomUUID().toString();
        
        pendingRequests.put(correlationId, future);
        
        rabbitTemplate.convertAndSend("", RpcConfig.RPC_QUEUE, message, m -> {
            m.getMessageProperties().setCorrelationId(correlationId);
            m.getMessageProperties().setReplyTo("amq.rabbitmq.reply-to"); // 使用 Direct reply-to
            return m;
        });
        
        return future;
    }
}
4. 测试
java 复制代码
@SpringBootTest
public class RpcModeTest {
    @Autowired
    private RpcClient rpcClient;
    
    @Test
    public void testRpc() {
        String result = rpcClient.call("Calculate: 10 + 20");
        assertEquals("Result for: Calculate: 10 + 20", result);
    }
    
    @Test
    public void testMultipleCalls() {
        for (int i = 0; i < 5; i++) {
            String response = rpcClient.call("Task " + i);
            log.info("Response {}: {}", i, response);
        }
    }
}

六、消息可靠性与高级特性

6.1 确保消息不丢失(三重保险代码示例)

消息丢失可能发生在三个环节:

  1. 生产者发送到交换机
  2. 交换机路由到队列
  3. 队列存储后消费者处理
A. 生产者确认 (Publisher Confirm)

配置类

java 复制代码
@Configuration
@Slf4j
public class ReliabilityConfig {
    
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        
        // 1. 开启 Confirm 模式(消息到达 Exchange 的确认)
        template.setConfirmCallback(new RabbitTemplate.ConfirmCallback() {
            @Override
            public void confirm(CorrelationData correlationData, boolean ack, String cause) {
                if (ack) {
                    log.debug("Message sent to exchange successfully, correlationId: {}", 
                        correlationData != null ? correlationData.getId() : null);
                    
                    // 如果消息有回调数据,可更新消息状态
                    if (correlationData != null && correlationData.getReturned() != null) {
                        // 处理 returned 消息
                    }
                } else {
                    log.error("Message failed to reach exchange, correlationId: {}, cause: {}",
                        correlationData != null ? correlationData.getId() : null, cause);
                    
                    // 保存失败消息到数据库,定时重发
                    if (correlationData != null && correlationData.getReturned() != null) {
                        Message failedMessage = correlationData.getReturned().getMessage();
                        saveFailedMessage(failedMessage, cause);
                    }
                }
            }
        });
        
        // 2. 开启 Return 回调(消息从 Exchange 路由到 Queue 失败)
        template.setReturnsCallback(new RabbitTemplate.ReturnsCallback() {
            @Override
            public void returnedMessage(ReturnedMessage returned) {
                log.error("Message routing failed: exchange={}, routingKey={}, replyCode={}, replyText={}, message={}",
                    returned.getExchange(), returned.getRoutingKey(), 
                    returned.getReplyCode(), returned.getReplyText(), 
                    returned.getMessage());
                
                // 处理无法路由的消息(保存到数据库或发送到告警)
                handleUnroutableMessage(returned);
            }
        });
        
        return template;
    }
    
    private void saveFailedMessage(Message message, String cause) {
        // 实际项目可存入数据库失败消息表
        log.info("Saving failed message to DB: {}, cause: {}", message, cause);
    }
    
    private void handleUnroutableMessage(ReturnedMessage returned) {
        // 可将无法路由的消息重新发送到备份交换机
        log.info("Handling unroutable message: {}", returned);
    }
}

生产者使用 CorrelationData

java 复制代码
@Component
public class ReliableProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendWithConfirm(String exchange, String routingKey, Object message) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        correlationData.setReturned(new ReturnedMessage(
            new Message(message.toString().getBytes(), new MessageProperties()),
            -1,
            "Pending",
            exchange,
            routingKey
        ));
        
        rabbitTemplate.convertAndSend(exchange, routingKey, message, correlationData);
        log.info("Message sent with correlationId: {}", correlationData.getId());
    }
    
    // 批量发送,等待确认
    public void sendBatchWithConfirm(List<String> messages) {
        List<CorrelationData> correlationList = new ArrayList<>();
        
        for (String msg : messages) {
            CorrelationData cd = new CorrelationData(UUID.randomUUID().toString());
            correlationList.add(cd);
            rabbitTemplate.convertAndSend("batch.exchange", "batch.key", msg, cd);
        }
        
        // 等待所有确认(需根据实际需求决定等待时间)
        for (CorrelationData cd : correlationList) {
            try {
                if (cd.getFuture().get(5, TimeUnit.SECONDS).isAck()) {
                    log.info("Message {} confirmed", cd.getId());
                } else {
                    log.error("Message {} nacked", cd.getId());
                }
            } catch (Exception e) {
                log.error("Wait for confirm timeout", e);
            }
        }
    }
}
B. 消费者手动 ACK 与重试

手动 ACK 消费者

java 复制代码
@Component
@Slf4j
public class ReliableConsumer {
    
    @RabbitListener(queues = "reliable.queue", ackMode = "MANUAL")
    public void processMessage(Message message, Channel channel) throws IOException {
        long deliveryTag = message.getMessageProperties().getDeliveryTag();
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        
        try {
            log.info("Processing message: {}", msg);
            
            // 业务处理
            businessProcess(msg);
            
            // 成功确认
            channel.basicAck(deliveryTag, false);
            log.debug("ACKed message: {}", deliveryTag);
            
        } catch (BusinessException e) {
            log.error("Business processing failed, will not requeue: {}", msg, e);
            // 业务异常,拒绝并不重新入队(进入死信)
            channel.basicNack(deliveryTag, false, false);
            
        } catch (TransientException e) {
            log.error("Transient error, will requeue: {}", msg, e);
            // 临时异常,拒绝并重新入队(等待下次消费)
            channel.basicNack(deliveryTag, false, true);
            
        } catch (Exception e) {
            log.error("Unknown error, check and decide: {}", msg, e);
            // 未知错误,根据业务决定是否 requeue
            channel.basicNack(deliveryTag, false, false);
        }
    }
    
    private void businessProcess(String msg) {
        // 业务逻辑
    }
    
    // 业务异常(无需重试)
    static class BusinessException extends RuntimeException {}
    // 临时异常(可重试)
    static class TransientException extends RuntimeException {}
}

Spring Retry 配置

yaml 复制代码
spring:
  rabbitmq:
    listener:
      simple:
        retry:
          enabled: true
          max-attempts: 3
          initial-interval: 1000
          multiplier: 2.0
          max-interval: 10000
          stateless: true # 无状态重试

结合重试的消费者

java 复制代码
@Component
@Slf4j
public class RetryConsumer {
    
    @RabbitListener(queues = "retry.queue")
    public void processWithRetry(String message) {
        log.info("Processing message: {}, attempt: {}", message, 
            RetrySynchronizationManager.getContext().getRetryCount());
        
        // 业务逻辑可能抛出异常,触发重试
        if (message.contains("error")) {
            throw new RuntimeException("Simulated error");
        }
        
        log.info("Processed successfully: {}", message);
    }
}
C. 消息与队列持久化

声明持久化队列和交换机

java 复制代码
@Configuration
public class PersistentConfig {
    
    // 持久化队列
    @Bean
    public Queue persistentQueue() {
        return QueueBuilder.durable("persistent.queue")
                .withArgument("x-queue-type", "quorum") // 使用仲裁队列,强一致
                .withArgument("x-max-length", 10000)    // 最大长度
                .withArgument("x-overflow", "reject-publish") // 溢出策略
                .build();
    }
    
    // 持久化交换机
    @Bean
    public DirectExchange persistentExchange() {
        return ExchangeBuilder.directExchange("persistent.exchange")
                .durable(true)
                .build();
    }
    
    // 持久化绑定
    @Bean
    public Binding persistentBinding() {
        return BindingBuilder.bind(persistentQueue())
                .to(persistentExchange())
                .with("persistent.key");
    }
}

发送持久化消息

java 复制代码
public void sendPersistentMessage(String msg) {
    MessageProperties props = new MessageProperties();
    props.setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 持久化
    props.setContentType("text/plain");
    props.setPriority(5); // 优先级
    
    Message message = new Message(msg.getBytes(StandardCharsets.UTF_8), props);
    rabbitTemplate.send("persistent.exchange", "persistent.key", message);
}

6.2 死信队列 (DLX) 实现

当消息被拒(requeue=false)、过期或队列满时,自动转入死信队列。

完整死信配置

java 复制代码
@Configuration
public class DlqConfig {
    // 死信交换机
    public static final String DLX_EXCHANGE = "dlx.exchange";
    public static final String DLQ_QUEUE = "dlq.queue";
    
    // 业务交换机
    public static final String BUSINESS_EXCHANGE = "business.exchange";
    public static final String BUSINESS_QUEUE = "business.queue";
    
    // 延迟消息交换机(使用插件)
    public static final String DELAYED_EXCHANGE = "delayed.exchange";

    // 1. 定义死信交换机和队列
    @Bean
    public DirectExchange dlxExchange() {
        return ExchangeBuilder.directExchange(DLX_EXCHANGE)
                .durable(true)
                .build();
    }
    
    @Bean
    public Queue dlqQueue() {
        return QueueBuilder.durable(DLQ_QUEUE)
                .withArgument("x-queue-type", "classic")
                .build();
    }
    
    @Bean
    public Binding dlqBinding() {
        return BindingBuilder.bind(dlqQueue())
                .to(dlxExchange())
                .with("dead");
    }

    // 2. 定义业务队列,配置死信参数
    @Bean
    public Queue businessQueue() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-dead-letter-exchange", DLX_EXCHANGE);     // 死信交换机
        args.put("x-dead-letter-routing-key", "dead");        // 死信路由键
        args.put("x-message-ttl", 60000);                     // 消息 TTL:60秒过期变死信
        args.put("x-max-length", 1000);                        // 最大消息数
        args.put("x-overflow", "reject-publish");              // 满时拒绝新消息
        
        return QueueBuilder.durable(BUSINESS_QUEUE)
                .withArguments(args)
                .build();
    }
    
    @Bean
    public DirectExchange businessExchange() {
        return ExchangeBuilder.directExchange(BUSINESS_EXCHANGE)
                .durable(true)
                .build();
    }
    
    @Bean
    public Binding businessBinding() {
        return BindingBuilder.bind(businessQueue())
                .to(businessExchange())
                .with("business");
    }
    
    // 3. 为死信队列设置过期时间,可二次死信
    @Bean
    public Queue dlqWithTtl() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-dead-letter-exchange", DLX_EXCHANGE);     // 可指向另一个死信交换机
        args.put("x-dead-letter-routing-key", "dead.again");
        args.put("x-message-ttl", 3600000);                   // 1小时后再次死信
        
        return QueueBuilder.durable("dlq.ttl")
                .withArguments(args)
                .build();
    }
}

死信消费者

java 复制代码
@Component
@Slf4j
public class DlqConsumer {
    
    @RabbitListener(queues = DlqConfig.DLQ_QUEUE)
    public void processDeadLetter(Message message) {
        String originalMsg = new String(message.getBody(), StandardCharsets.UTF_8);
        MessageProperties props = message.getMessageProperties();
        
        log.error("Received dead letter message: {}", originalMsg);
        log.error("Dead letter reason: {}", props.getHeader("x-first-death-reason"));
        log.error("Original exchange: {}", props.getHeader("x-first-death-exchange"));
        log.error("Original queue: {}", props.getHeader("x-first-death-queue"));
        
        // 记录死信消息,人工干预或自动修复后重新发送
        saveToDeadLetterTable(originalMsg, props);
        
        // 可尝试重新处理(修改后重新发送)
        // retryProcess(originalMsg);
    }
    
    private void saveToDeadLetterTable(String msg, MessageProperties props) {
        // 保存到数据库,用于监控和排查
        log.info("Dead letter saved: {}", msg);
    }
}

生产者触发死信

java 复制代码
@Component
public class BusinessProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    public void sendWithExpiration(String msg, long ttl) {
        MessagePostProcessor processor = m -> {
            m.getMessageProperties().setExpiration(String.valueOf(ttl));
            return m;
        };
        
        rabbitTemplate.convertAndSend(
            DlqConfig.BUSINESS_EXCHANGE, 
            "business", 
            msg, 
            processor
        );
    }
}

6.3 延迟队列(基于插件)

需要安装 rabbitmq_delayed_message_exchange 插件:

bash 复制代码
rabbitmq-plugins enable rabbitmq_delayed_message_exchange

配置延迟交换机

java 复制代码
@Configuration
public class DelayedConfig {
    public static final String DELAYED_EXCHANGE = "delayed.exchange";
    public static final String DELAYED_QUEUE = "delayed.queue";
    
    @Bean
    public CustomExchange delayedExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("x-delayed-type", "direct"); // 内部转发类型:direct/topic/fanout
        
        return new CustomExchange(
            DELAYED_EXCHANGE,
            "x-delayed-message", // 交换机类型
            true,                // durable
            false,               // auto-delete
            args
        );
    }
    
    @Bean
    public Queue delayedQueue() {
        return QueueBuilder.durable(DELAYED_QUEUE).build();
    }
    
    @Bean
    public Binding delayedBinding() {
        return BindingBuilder.bind(delayedQueue())
                .to(delayedExchange())
                .with("delayed.routing.key")
                .noargs(); // 注意:CustomExchange 的 binding 方式
    }
}

延迟消息生产者

java 复制代码
@Component
@Slf4j
public class DelayedProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    /**
     * 发送延迟消息
     * @param msg 消息内容
     * @param delayMs 延迟毫秒数
     */
    public void sendDelayed(String msg, int delayMs) {
        MessagePostProcessor processor = message -> {
            message.getMessageProperties().setHeader("x-delay", delayMs);
            message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT);
            return message;
        };
        
        rabbitTemplate.convertAndSend(
            DelayedConfig.DELAYED_EXCHANGE,
            "delayed.routing.key",
            msg,
            processor
        );
        
        log.info("Sent delayed message: {}, delay: {}ms", msg, delayMs);
    }
    
    /**
     * 发送延迟对象
     */
    public void sendDelayedObject(DelayedTask task) {
        MessagePostProcessor processor = message -> {
            message.getMessageProperties().setHeader("x-delay", task.getDelayMs());
            message.getMessageProperties().setHeader("x-task-type", task.getType());
            return message;
        };
        
        rabbitTemplate.convertAndSend(
            DelayedConfig.DELAYED_EXCHANGE,
            "delayed.routing.key",
            task,
            processor
        );
        
        log.info("Sent delayed task: {}, delay: {}ms", task, task.getDelayMs());
    }
    
    @Data
    @NoArgsConstructor
    @AllArgsConstructor
    public static class DelayedTask {
        private String id;
        private String type;
        private int delayMs;
        private Map<String, Object> payload;
    }
}

延迟消息消费者

java 复制代码
@Component
@Slf4j
public class DelayedConsumer {
    
    @RabbitListener(queues = DelayedConfig.DELAYED_QUEUE)
    public void receiveDelayed(String msg) {
        log.info("Received delayed message at {}: {}", LocalDateTime.now(), msg);
    }
    
    @RabbitListener(queues = DelayedConfig.DELAYED_QUEUE)
    public void receiveDelayedTask(DelayedProducer.DelayedTask task) {
        log.info("Received delayed task at {}: {}", LocalDateTime.now(), task);
        
        // 根据任务类型执行不同逻辑
        switch (task.getType()) {
            case "order.cancel":
                cancelOrder(task);
                break;
            case "payment.timeout":
                handlePaymentTimeout(task);
                break;
            default:
                log.warn("Unknown task type: {}", task.getType());
        }
    }
    
    private void cancelOrder(DelayedProducer.DelayedTask task) {
        String orderId = (String) task.getPayload().get("orderId");
        log.info("Cancelling order: {}", orderId);
        // 取消订单逻辑
    }
    
    private void handlePaymentTimeout(DelayedProducer.DelayedTask task) {
        String paymentId = (String) task.getPayload().get("paymentId");
        log.info("Payment timeout: {}", paymentId);
        // 超时处理逻辑
    }
}

测试

java 复制代码
@SpringBootTest
public class DelayedModeTest {
    @Autowired
    private DelayedProducer producer;
    
    @Test
    public void testDelayed() throws InterruptedException {
        log.info("Start time: {}", LocalDateTime.now());
        
        producer.sendDelayed("Message with 5s delay", 5000);
        producer.sendDelayed("Message with 10s delay", 10000);
        
        // 等待消费者处理
        Thread.sleep(15000);
    }
    
    @Test
    public void testDelayedTask() throws InterruptedException {
        DelayedProducer.DelayedTask task = new DelayedProducer.DelayedTask(
            UUID.randomUUID().toString(),
            "order.cancel",
            8000,
            Map.of("orderId", "12345", "userId", 67890)
        );
        
        producer.sendDelayedObject(task);
        
        Thread.sleep(10000);
    }
}

6.4 备份交换机 (Alternate Exchange)

当消息无法路由到任何队列时,可发送到备份交换机处理。

java 复制代码
@Configuration
public class AlternateExchangeConfig {
    public static final String MAIN_EXCHANGE = "main.exchange";
    public static final String ALTERNATE_EXCHANGE = "alternate.exchange";
    public static final String UNROUTABLE_QUEUE = "unroutable.queue";
    
    @Bean
    public DirectExchange alternateExchange() {
        return ExchangeBuilder.directExchange(ALTERNATE_EXCHANGE)
                .durable(true)
                .build();
    }
    
    @Bean
    public Queue unroutableQueue() {
        return QueueBuilder.durable(UNROUTABLE_QUEUE).build();
    }
    
    @Bean
    public Binding alternateBinding() {
        return BindingBuilder.bind(unroutableQueue())
                .to(alternateExchange())
                .with("unroutable");
    }
    
    @Bean
    public DirectExchange mainExchange() {
        Map<String, Object> args = new HashMap<>();
        args.put("alternate-exchange", ALTERNATE_EXCHANGE); // 设置备份交换机
        
        return ExchangeBuilder.directExchange(MAIN_EXCHANGE)
                .durable(true)
                .withArguments(args)
                .build();
    }
    
    // 主队列
    @Bean
    public Queue mainQueue() {
        return QueueBuilder.durable("main.queue").build();
    }
    
    @Bean
    public Binding mainBinding() {
        return BindingBuilder.bind(mainQueue())
                .to(mainExchange())
                .with("valid.key");
    }
}

消费者

java 复制代码
@Component
public class UnroutableConsumer {
    
    @RabbitListener(queues = AlternateExchangeConfig.UNROUTABLE_QUEUE)
    public void handleUnroutable(Message message) {
        String msg = new String(message.getBody(), StandardCharsets.UTF_8);
        String originalRoutingKey = message.getMessageProperties().getReceivedRoutingKey();
        
        log.warn("Received unroutable message, original routingKey: {}, content: {}", 
            originalRoutingKey, msg);
        
        // 记录并告警
        alertUnroutableMessage(msg, originalRoutingKey);
    }
    
    private void alertUnroutableMessage(String msg, String routingKey) {
        // 发送告警
    }
}

七、性能优化与实践总结

7.1 连接管理优化

配置优化

yaml 复制代码
spring:
  rabbitmq:
    # 连接池配置
    cache:
      channel:
        size: 50               # 缓存 Channel 数量
        checkout-timeout: 10000 # 获取 Channel 超时
      connection:
        mode: channel           # 缓存模式
        size: 10                # 缓存连接数
    # 连接超时
    connection-timeout: 15000
    # 心跳超时
    requested-heartbeat: 60

代码层面优化

java 复制代码
@Configuration
public class OptimizedConfig {
    
    @Bean
    public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
        RabbitTemplate template = new RabbitTemplate(connectionFactory);
        
        // 使用 JSON 序列化
        template.setMessageConverter(new Jackson2JsonMessageConverter());
        
        // 开启批量发送
        template.setConfirmCallback((correlationData, ack, cause) -> {
            // 批量确认处理
        });
        
        return template;
    }
    
    @Bean
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        
        // 并发消费者配置
        factory.setConcurrentConsumers(5);
        factory.setMaxConcurrentConsumers(20);
        
        // 预取数量
        factory.setPrefetchCount(10);
        
        // 批量消费
        factory.setBatchListener(true);
        factory.setBatchSize(100);
        factory.setReceiveTimeout(3000L);
        
        // 消息转换器
        factory.setMessageConverter(new Jackson2JsonMessageConverter());
        
        // 异常处理
        factory.setErrorHandler(t -> {
            log.error("Consumer error", t);
        });
        
        return factory;
    }
}

7.2 批量处理优化

批量生产者

java 复制代码
@Component
public class BatchProducer {
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    private final List<Object> batch = new ArrayList<>();
    private final int BATCH_SIZE = 100;
    private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
    
    @PostConstruct
    public void init() {
        // 定时批量发送(每 5 秒发送一次)
        scheduler.scheduleAtFixedRate(this::flush, 5, 5, TimeUnit.SECONDS);
    }
    
    public void addToBatch(Object message) {
        synchronized (batch) {
            batch.add(message);
            if (batch.size() >= BATCH_SIZE) {
                flush();
            }
        }
    }
    
    private void flush() {
        List<Object> toSend;
        synchronized (batch) {
            if (batch.isEmpty()) return;
            toSend = new ArrayList<>(batch);
            batch.clear();
        }
        
        // 批量发送
        rabbitTemplate.invoke(operations -> {
            for (Object msg : toSend) {
                operations.convertAndSend("batch.exchange", "batch.key", msg);
            }
            return null;
        });
        
        log.info("Flushed {} messages", toSend.size());
    }
}

批量消费者

java 复制代码
@Component
@Slf4j
public class BatchConsumer {
    
    @RabbitListener(queues = "batch.queue", containerFactory = "rabbitListenerContainerFactory")
    public void receiveBatch(List<String> messages) {
        log.info("Received batch of {} messages", messages.size());
        
        // 批量处理(如批量插入数据库)
        batchInsertToDatabase(messages);
    }
    
    private void batchInsertToDatabase(List<String> messages) {
        // JDBC batch insert
        // 或 Redis pipeline
    }
}

7.3 监控告警配置

Spring Boot Actuator 配置

yaml 复制代码
management:
  endpoints:
    web:
      exposure:
        include: health,info,metrics,rabbitmq
  metrics:
    export:
      prometheus:
        enabled: true
  endpoint:
    rabbitmq:
      enabled: true

自定义监控指标

java 复制代码
@Component
public class RabbitMQMonitor {
    @Autowired
    private RabbitManagementTemplate managementTemplate;
    
    private final MeterRegistry meterRegistry;
    
    public RabbitMQMonitor(MeterRegistry meterRegistry) {
        this.meterRegistry = meterRegistry;
    }
    
    @Scheduled(fixedDelay = 60000) // 每分钟采集一次
    public void collectMetrics() {
        // 获取所有队列
        List<QueueInfo> queues = managementTemplate.getQueues();
        
        for (QueueInfo queue : queues) {
            // 就绪消息数
            meterRegistry.gauge("rabbitmq.queue.messages.ready", 
                Tags.of("queue", queue.getName()), 
                queue, QueueInfo::getMessagesReady);
            
            // 未确认消息数
            meterRegistry.gauge("rabbitmq.queue.messages.unacked",
                Tags.of("queue", queue.getName()),
                queue, QueueInfo::getMessagesUnacknowledged);
            
            // 消费者数量
            meterRegistry.gauge("rabbitmq.queue.consumers",
                Tags.of("queue", queue.getName()),
                queue, QueueInfo::getConsumers);
        }
        
        // 监控堆积队列
        queues.stream()
            .filter(q -> q.getMessagesReady() > 1000)
            .forEach(q -> {
                log.warn("Queue {} is backed up: {} messages", 
                    q.getName(), q.getMessagesReady());
                // 发送告警
                alertQueueBacklog(q);
            });
    }
    
    private void alertQueueBacklog(QueueInfo queue) {
        // 发送邮件、钉钉、企业微信通知
    }
}

7.4 集群与高可用选择

仲裁队列 (Quorum Queues) 配置
java 复制代码
@Configuration
public class QuorumQueueConfig {
    
    @Bean
    public Queue quorumQueue() {
        return QueueBuilder.durable("quorum.queue")
                .withArgument("x-queue-type", "quorum")     // 仲裁队列
                .withArgument("x-quorum-initial-group-size", 3) // 初始集群大小
                .withArgument("x-max-length", 1000000)      // 最大长度
                .withArgument("x-overflow", "reject-publish") // 溢出策略
                .withArgument("x-delivery-limit", 5)         // 最大投递次数
                .build();
    }
    
    @Bean
    public DirectExchange quorumExchange() {
        return ExchangeBuilder.directExchange("quorum.exchange")
                .durable(true)
                .build();
    }
    
    @Bean
    public Binding quorumBinding() {
        return BindingBuilder.bind(quorumQueue())
                .to(quorumExchange())
                .with("quorum.key");
    }
}
多租户配置
java 复制代码
@Configuration
public class MultiVHostConfig {
    
    @Bean
    @Primary
    public ConnectionFactory defaultConnectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory("localhost");
        factory.setVirtualHost("/");
        factory.setUsername("guest");
        factory.setPassword("guest");
        return factory;
    }
    
    @Bean
    public ConnectionFactory orderVHostConnectionFactory() {
        CachingConnectionFactory factory = new CachingConnectionFactory("localhost");
        factory.setVirtualHost("/order");
        factory.setUsername("order_user");
        factory.setPassword("order_pass");
        return factory;
    }
    
    @Bean(name = "orderRabbitTemplate")
    public RabbitTemplate orderRabbitTemplate() {
        return new RabbitTemplate(orderVHostConnectionFactory());
    }
}

7.5 模式选择指南

业务场景 推荐模式 交换机类型 说明
点对点通信 简单模式 Default Exchange 简单直接,一个生产者对应一个消费者
任务分发/负载均衡 工作队列 Default Exchange 多个消费者处理同一队列任务
广播通知 发布订阅 Fanout 所有消费者收到相同消息
日志分级 路由模式 Direct 按级别分发到不同队列
复杂路由 主题模式 Topic 通配符匹配,灵活过滤
远程调用 RPC模式 Direct + ReplyTo 同步等待返回结果
定时任务 延迟队列 x-delayed-message 需要延迟处理的消息
异常处理 死信队列 Direct + DLX 处理失败消息
强一致性 仲裁队列 Quorum 数据不丢不重
高吞吐 惰性队列 Lazy 大量消息堆积

7.6 常见问题与解决方案

消息积压处理
java 复制代码
@Component
public class BacklogHandler {
    
    @Autowired
    private RabbitTemplate rabbitTemplate;
    
    // 动态增加消费者
    public void scaleUp(String queueName, int additionalConsumers) {
        // 实际需通过管理 API 或动态注册监听器
        log.info("Scaling up queue {} by {} consumers", queueName, additionalConsumers);
    }
    
    // 临时将消息转发到新队列
    public void redirectBacklog(String sourceQueue, String targetQueue, int count) {
        // 从源队列拉取消息
        for (int i = 0; i < count; i++) {
            Message message = rabbitTemplate.receive(sourceQueue, 1000);
            if (message != null) {
                // 转发到新队列
                rabbitTemplate.send(targetQueue, message);
            }
        }
    }
    
    // 批量重新发布(重新入队)
    public void republish(String queueName) {
        // 获取队列消息数量
        int messageCount = getMessageCount(queueName);
        
        for (int i = 0; i < messageCount; i++) {
            Message message = rabbitTemplate.receive(queueName, 100);
            if (message != null) {
                // 修改消息属性后重新发送
                rabbitTemplate.send(queueName, message);
            }
        }
    }
    
    private int getMessageCount(String queueName) {
        // 通过管理 API 获取
        return 0;
    }
}
消息重复消费
java 复制代码
@Component
public class IdempotentConsumer {
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @RabbitListener(queues = "idempotent.queue")
    public void process(Message message) {
        String messageId = message.getMessageProperties().getMessageId();
        if (messageId == null) {
            messageId = UUID.randomUUID().toString();
        }
        
        // 使用 Redis 做幂等判断
        String key = "msg:" + messageId;
        Boolean success = redisTemplate.opsForValue()
            .setIfAbsent(key, "processed", Duration.ofHours(1));
        
        if (Boolean.TRUE.equals(success)) {
            // 首次消费
            String content = new String(message.getBody(), StandardCharsets.UTF_8);
            businessProcess(content);
            log.info("Processed message: {}", messageId);
        } else {
            // 重复消息,直接 ACK 但不处理
            log.info("Duplicate message ignored: {}", messageId);
        }
    }
    
    private void businessProcess(String content) {
        // 业务逻辑
    }
}

八、生产环境实践清单

8.1 部署配置

  • RabbitMQ 版本使用 3.9+,推荐 3.12+
  • 集群节点数至少 3 个(仲裁队列要求)
  • 磁盘监控:可用空间 > 20%
  • 内存监控:使用率 < 70%
  • 文件描述符:ulimit -n 至少 65536
  • 开启防火墙,仅暴露 5672(AMQP) 和 15672(管理)

8.2 消息可靠性

  • 生产者开启 Confirm 模式
  • 生产者开启 Return 回调
  • 消息设置持久化(PERSISTENT)
  • 队列设置 durable=true
  • 消费者开启手动 ACK
  • 配置死信队列处理异常消息
  • 重要业务设置消息幂等处理

8.3 性能优化

  • 合理设置 prefetch 值(根据业务处理时间)
  • 使用连接池,复用 Connection 和 Channel
  • 批量发送/消费(高频小消息)
  • 消息大小控制在 4MB 以内(过大可压缩或存 OSS)
  • 避免大量消息堆积,设置队列 max-length
  • 使用惰性队列处理长堆积场景

8.4 监控告警

  • 队列堆积告警(> 阈值)
  • 消费者离线告警(消费者数量 < 预期)
  • 节点内存/磁盘告警
  • 消息处理延迟告警
  • 死信队列增长告警

8.5 安全加固

  • 生产环境禁用 guest 用户
  • 每个应用独立 VHost 和用户
  • 最小权限原则(仅授予必要权限)
  • TLS/SSL 加密传输
  • 管理界面绑定内网 IP 或使用 VPN
相关推荐
橙序员小站16 小时前
Agent Skill 是什么?一文讲透 Agent Skill 的设计与实现
前端·后端
怒放吧德德16 小时前
Netty 4.2 入门指南:从概念到第一个程序
java·后端·netty
雨中飘荡的记忆17 小时前
大流量下库存扣减的数据库瓶颈:Redis分片缓存解决方案
java·redis·后端
开心就好202519 小时前
UniApp开发应用多平台上架全流程:H5小程序iOS和Android
后端·ios
悟空码字19 小时前
告别“屎山代码”:AI 代码整洁器让老项目重获新生
后端·aigc·ai编程
小码哥_常19 小时前
大厂不宠@Transactional,背后藏着啥秘密?
后端
奋斗小强19 小时前
内存危机突围战:从原理辨析到线上实战,彻底搞懂 OOM 与内存泄漏
后端
小码哥_常20 小时前
Spring Boot接口防抖秘籍:告别“手抖”,守护数据一致性
后端
心之语歌20 小时前
基于注解+拦截器的API动态路由实现方案
java·后端
None32120 小时前
【NestJs】基于Redlock装饰器分布式锁设计与实现
后端·node.js