RabbitMQ 知识点全面总结:核心模型、可靠性、顺序性、监控与死信队列

一、RabbitMQ 核心概念

RabbitMQ 是基于 AMQP(高级消息队列协议)的开源消息中间件,支持多种消息模型,具备高可靠、可扩展、易集成等特性。其核心组件包括:

  • ​​Broker​​:消息服务器(如安装在本地的 localhost)
  • ​​Exchange​​:消息交换器(负责路由消息到队列)
  • ​​Queue​​:消息队列(存储消息的缓冲区)
  • ​​Binding​​:绑定关系(Exchange 与 Queue 的关联规则)
  • ​​Producer​​:消息生产者(发送消息)
  • ​​Consumer​​:消息消费者(接收消息)

二、RabbitMQ 五种核心消息模型

RabbitMQ 支持 5 种基础消息模型,本质是通过 Exchange 的不同类型实现的。以下是模型示意图及说明:

2.1 简单队列(Simple Queue)
​​特点​​:单生产者 → 单消费者,一对一通信。
​​适用场景​​:简单的任务分发(如单实例日志收集)。

​​Java 代码示例(Spring Boot)​​:

// 生产者
@Autowired
private RabbitTemplate rabbitTemplate;

public void sendSimpleMsg(String msg) {
    rabbitTemplate.convertAndSend("simpleQueue", msg); // 直接发送到队列
}

// 消费者
@RabbitListener(queues = "simpleQueue")
public void receiveSimpleMsg(String msg) {
    System.out.println("接收简单队列消息:" + msg);
}

2.2 工作队列(Work Queue)
​​特点​​:单生产者 → 多消费者,竞争消费同一消息(默认轮询分发)。
​​优化​​:可通过 fair dispatch(公平分发)避免消费者负载不均(设置 spring.rabbitmq.listener.simple.prefetch=1)。
​​适用场景​​:任务削峰(如批量订单处理)。

# application.properties 配置公平分发(预取计数=1)
spring.rabbitmq.listener.simple.prefetch=1

2.3 发布-订阅(Publish/Subscribe)
​​特点​​:单生产者 → 多消费者(通过 FanoutExchange 广播消息到所有绑定的队列)。
​​适用场景​​:事件广播(如系统通知、实时推送)。

// 声明 FanoutExchange(需在配置类中定义)
@Bean
public FanoutExchange fanoutExchange() {
    return new FanoutExchange("pubSubExchange");
}

// 生产者发送消息到 Exchange
public void sendPubSubMsg(String msg) {
    rabbitTemplate.convertAndSend("pubSubExchange", "", msg); // 路由键为空(Fanout 忽略)
}

// 消费者 A 绑定队列 1
@RabbitListener(queues = "pubSubQueue1")
public void receivePubSub1(String msg) {
    System.out.println("消费者1接收:" + msg);
}

// 消费者 B 绑定队列 2
@RabbitListener(queues = "pubSubQueue2")
public void receivePubSub2(String msg) {
    System.out.println("消费者2接收:" + msg);
}

2.4 路由(Routing)
​​特点​​:生产者 → DirectExchange(根据路由键精确匹配)→ 绑定队列。
​​适用场景​​:条件路由(如按日志级别分发:ERROR、INFO)。

// 声明 DirectExchange
@Bean
public DirectExchange directExchange() {
    return new DirectExchange("routingExchange");
}

// 生产者发送带路由键的消息
public void sendRoutingMsg(String routingKey, String msg) {
    rabbitTemplate.convertAndSend("routingExchange", routingKey, msg);
}

// 消费者绑定 ERROR 路由键的队列
@RabbitListener(queues = "errorQueue")
public void receiveError(String msg) {
    System.out.println("接收 ERROR 消息:" + msg);
}

// 消费者绑定 INFO 路由键的队列
@RabbitListener(queues = "infoQueue")
public void receiveInfo(String msg) {
    System.out.println("接收 INFO 消息:" + msg);
}

2.5 主题(Topics)
​​特点​​:生产者 → TopicExchange(根据路由键通配符匹配)→ 绑定队列。
​​通配符规则​​:*(匹配一个单词)、#(匹配零或多个单词)。
​​适用场景​​:复杂条件路由(如按用户类型+操作类型分发)。

// 声明 TopicExchange
@Bean
public TopicExchange topicExchange() {
   return new TopicExchange("topicExchange");
}

// 生产者发送带通配符路由键的消息(如 "user.create"、"order.pay.*")
public void sendTopicMsg(String routingKey, String msg) {
   rabbitTemplate.convertAndSend("topicExchange", routingKey, msg);
}

// 消费者绑定 "user.#" 匹配所有 user 相关消息
@RabbitListener(queues = "userQueue")
public void receiveUser(String msg) {
   System.out.println("接收 USER 消息:" + msg);
}

// 消费者绑定 "order.pay.success" 精确匹配
@RabbitListener(queues = "orderQueue")
public void receiveOrder(String msg) {
   System.out.println("接收 ORDER 消息:" + msg);
}

三、如何保证消息不丢失?

消息丢失可能发生在 ​​生产者 → Broker → 消费者​​ 三个阶段,需分别处理:
3.1 生产者阶段:确认机制(Confirm)
通过 ConfirmCallback 确认消息是否到达 Broker。
​​配置步骤​​:

  1. 开启生产者确认:spring.rabbitmq.publisher-confirm-type=correlated(关联回调)。
  2. 实现 RabbitTemplate.ConfirmCallback 接口。
@Component
public class RabbitMQProducer {

    @Autowired
    private RabbitTemplate rabbitTemplate;

    // 生产者确认回调
    @PostConstruct
    public void init() {
        rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> {
            if (ack) {
                System.out.println("消息成功到达 Broker");
            } else {
                System.err.println("消息未到达 Broker,原因:" + cause);
                // 可在此重发消息
            }
        });
    }

    public void sendMessage(String queue, String msg) {
        CorrelationData correlationData = new CorrelationData(UUID.randomUUID().toString());
        rabbitTemplate.convertAndSend(queue, msg, correlationData);
    }
}

3.2 Broker 阶段:持久化(Persistence)
通过持久化保证 Broker 重启后消息不丢失。
​​配置方式​​:

  • 队列持久化:durable=true(声明队列时设置)。
  • 消息持久化:MessageProperties.PERSISTENT_TEXT_PLAIN(发送消息时设置)
// 声明持久化队列(在配置类中)
@Bean
public Queue durableQueue() {
    return new Queue("durableQueue", true); // 第二个参数为持久化标志
}

// 发送持久化消息
public void sendDurableMsg(String msg) {
    rabbitTemplate.convertAndSend("durableQueue", msg, message -> {
        message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); // 设置消息持久化
        return message;
    });
}

3.3 消费者阶段:手动确认(ACK)
通过 manual ack 避免因消费者崩溃导致消息丢失。
​​配置方式​​:

  • 开启手动确认:spring.rabbitmq.listener.simple.acknowledge-mode=manual。
  • 消费者处理完成后调用 channel.basicAck(deliveryTag, false)。
@RabbitListener(queues = "manualAckQueue")
public void receiveManualAck(String msg, Channel channel, Message message) throws IOException {
    try {
        System.out.println("处理消息:" + msg);
        // 模拟业务处理耗时
        Thread.sleep(1000);
        // 手动确认(第二个参数为是否批量确认)
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } catch (Exception e) {
        // 处理失败,拒绝消息(可重新入队或丢弃)
        channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); // 第三个参数为是否重新入队
    }
}

四、如何避免消息重复消费?

消息重复消费的根本原因是:​​Broker 在未收到消费者 ACK 时,会将消息重新投递​​(如消费者崩溃或超时)。
​​解决方案​​:在消费者端实现 ​​幂等性​​(多次消费同一消息结果与一次消费一致)。

4.1 幂等性实现策略

策略 说明
唯一 ID 校验 为每条消息生成唯一 ID(如 UUID),消费者记录已处理的 ID(Redis/数据库)
数据库唯一索引 插入数据时使用唯一索引,避免重复写入
Redis 缓存标记 消费前检查 Redis 中是否存在该消息 ID,存在则跳过

4.2 Java 代码示例(Redis 去重)

@RabbitListener(queues = "idempotentQueue")
public void receiveIdempotentMsg(String msg, Channel channel, Message message) throws IOException {
    String messageId = extractMessageId(msg); // 从消息中提取唯一 ID(如 JSON 中的 "id" 字段)
    
    // 检查 Redis 中是否已处理
    if (redisTemplate.opsForValue().setIfAbsent("msg:" + messageId, "1", 24, TimeUnit.HOURS)) {
        // 未处理过,执行业务逻辑
        processMsg(msg);
        // 手动确认
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    } else {
        // 已处理过,直接确认(不重复消费)
        channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
    }
}

private String extractMessageId(String msg) {
    // 假设消息是 JSON 格式,解析出 "id" 字段
    JSONObject json = JSON.parseObject(msg);
    return json.getString("id");
}

五、如何保证消息顺序性?

RabbitMQ 默认不保证消息顺序(因负载均衡可能将消息分发到不同队列)。
​​保证顺序的方案​​:

  1. ​​单队列单消费者​​:所有消息进入同一队列,由单个消费者顺序处理(牺牲吞吐量)。
  2. ​​消息分区​​:通过相同路由键将消息发送到同一队列(如订单 ID 作为路由键)

5.1 单队列单消费者示例

// 生产者发送消息时使用相同路由键(如 "order")
public void sendOrderMsg(String orderId, String msg) {
    rabbitTemplate.convertAndSend("orderExchange", "order", msg); // 路由键固定为 "order"
}

// 消费者使用单线程处理(@RabbitListener 默认是多线程,需配置为单线程)
@RabbitListener(queues = "orderQueue", concurrency = "1") // 并发数=1(单线程)
public void receiveOrderMsg(String msg) {
    System.out.println("顺序处理订单消息:" + msg);
}

六、RabbitMQ 监控

RabbitMQ 提供内置的管理插件(rabbitmq-management),可通过 Web 界面监控集群状态。

6.1 启用管理插件

# 进入 RabbitMQ 安装目录
rabbitmq-plugins enable rabbitmq_management

启动后访问 http://localhost:15672(默认账号:guest/guest,仅限本地访问)。

6.2 监控关键指标

指标 说明
Queues 队列数量、消息总数、消费者数量
Connections 客户端连接数、通道数
Exchanges 交换器类型、绑定数量
Message Rates 消息生产/消费速率(msg/s)
Disk/Memory Usage Broker 磁盘/内存使用情况

七、死信队列(Dead Letter Queue)
7.1 死信形成原因
消息变为死信(Dead Letter)的三种场景:

  1. ​​消息过期​​(x-message-ttl 设置超时时间)。
  2. ​​消费者拒绝​​(basicNack/basicReject 且 requeue=false)。
  3. ​​队列满​​(x-max-length 设置队列最大长度)。

7.2 死信队列配置步骤

  1. 声明死信交换器(DLX Exchange)和死信队列(DLQ)。
  2. 原队列绑定死信交换器,并设置死信参数。

java

// 声明死信交换器和队列(配置类)
@Bean
public DirectExchange dlxExchange() {
    return new DirectExchange("dlxExchange");
}

@Bean
public Queue dlq() {
    return new Queue("dlq");
}

@Bean
public Binding dlqBinding() {
    return BindingBuilder.bind(dlq()).to(dlxExchange()).with("dlqRoutingKey");
}

// 声明原队列(绑定死信参数)
@Bean
public Queue originalQueue() {
    Map<String, Object> args = new HashMap<>();
    args.put("x-dead-letter-exchange", "dlxExchange"); // 死信交换器
    args.put("x-dead-letter-routing-key", "dlqRoutingKey"); // 死信路由键
    args.put("x-message-ttl", 10000); // 消息过期时间(10秒)
    args.put("x-max-length", 100); // 队列最大长度(100条)
    return new Queue("originalQueue", true, false, false, args);
}

// 生产者发送消息到原队列
public void sendToOriginalQueue(String msg) {
    rabbitTemplate.convertAndSend("originalQueue", msg);
}

// 消费者监听死信队列(处理死信)
@RabbitListener(queues = "dlq")
public void receiveDlqMsg(String msg) {
    System.out.println("处理死信消息:" + msg);
}

总结
RabbitMQ 是功能强大的消息中间件,核心能力覆盖多种消息模型、高可靠性、顺序性保证等。实际使用中需根据业务场景选择模型(如发布订阅用于广播,路由用于条件分发),并通过确认机制、持久化、幂等性设计保障消息可靠性。监控和死信队列则是运维和故障排查的关键工具。

posted @ 2025-06-30 19:32  念笙  阅读(160)  评论(0)    收藏  举报