消息队列-八股

消息队列使用场景有哪些?

消息队列(Message Queue,MQ)是分布式系统中的核心中间件之一,主要从 解耦、异步、削峰 三大核心价值出发。

价值说明
1. 系统解耦消除服务之间的强依赖,通过 MQ 间接通信
2. 异步处理将耗时操作异步化,提升响应速度和吞吐量
3. 流量削峰缓冲突发流量,保护后端系统不被压垮

场景 1系统解耦(Decoupling)

  • 问题
    订单服务创建订单后,需要通知库存、物流、积分、短信等多个下游系统。如果直接调用,任何一个下游故障都会导致订单失败。

  • 解决方案
    订单服务只负责发一条“订单创建”消息到 MQ,各下游系统自行订阅消费

  • 好处

    • 新增/下线服务无需修改订单服务代码
    • 下游系统故障不影响主流程

场景 2异步处理(Asynchronous Processing)

  • 问题:加入一个操作设计到好几个步骤,这些步骤之间不需要同步完成
    比如客户去创建了一个订单,还要去客户轨迹系统添加一条轨迹、去库存系统更新库存、去客户系统修改客户的状态等等,这些操作串行执行将会产生大量的时间,用户体验差。

  • 解决方案:如果使用MQ将客户创建订单时,将后面的轨迹、库存、状态等信息的更新全都放到MQ里面然后去异步操作,这样就可加快系统的访问速度,提供更好的客户体验。

  • 效果

    • 接口响应时间从 2s → 50ms
    • 提升系统吞吐量

场景 3:流量削峰(Peak Shaving)

  • 问题
    秒杀活动瞬间涌入 10 万请求,但数据库只能承受 2000 QPS,直接打满会宕机。

  • 解决方案
    请求先写入 MQ(如 RocketMQ/Kafka),后端服务以 2000 QPS 的稳定速率消费处理。

  • 关键点

    • MQ 充当“缓冲池”
    • 用户看到“排队中”或“稍后查看结果”,体验可接受

MQ 的技术选型

MQ:Message Queue,消息队列

以下是当前主流的消息队列

吞吐量:优先选Kafka和RocketMQ这种更高吞吐的。

消息丢失怎么解决的?

消息丢失的情况主要有以下三种:

  1. 生产者向消息代理传递消息的过程中,消息丢失了
  2. 消息代理( RabbitMQ )把消息弄丢了
  3. 消费者把消息弄丢了

使用一个消息队列,其实就分为三大块:生产者、中间件、消费者,所以要保证消息就是保证三个环节都不能丢失数据。

消息生产阶段:生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给 MQ 的过程中,只要能正常收到 ( MQ 中间件) 的 ack 确认响应,就表示发送成功,所以只要处理好返回值和异常,如果返回异常则进行消息重发,那么这个阶段是不会出现消息丢失的。

消息存储阶段:Kafka 在使用时是部署一个集群,生产者在发布消息时,队列中间件通常会写「多个节点」,也就是有多个副本,这样一来,即便其中一个节点挂了,也能保证集群的数据不丢失。

消息消费阶段:消费者接收消息+消息处理之后,才回复 ack 的话,那么消息阶段的消息不会丢失。不能收到消息就回 ack,否则可能消息处理中途挂掉了,消息就丢失了。

如何保证幂等写?

幂等性是指 同一操作的多次执行对系统状态的影响与一次执行结果一致。例如,支付接口若因网络重试被多次调用,最终应确保仅扣款一次。实现幂等写的核心方案:

  • 唯一标识(幂等键):客户端为每个请求生成全局唯一ID(如 UUID、业务主键),服务端校验该ID是否已处理,适用场景接口调用、消息消费等。
  • 数据库事务 + 乐观锁:通过版本号或状态字段控制并发更新,确保多次更新等同于单次操作,适用场景数据库记录更新(如余额扣减、订单状态变更)。
  • 数据库唯一约束:利用数据库唯一索引防止重复数据写入,适用场景数据插入场景(如订单创建)。
  • 分布式锁:通过锁机制保证同一时刻仅有一个请求执行关键操作,适用场景高并发下的资源抢夺(如秒杀)。
  • 消息去重:消息队列生产者为每条消息生成唯一的消息 ID,消费者在处理消息前,先检查该消息 ID 是否已经处理过,如果已经处理过则丢弃该消息。

RocketMQ

消息队列为什么选择RocketMQ的?

项目用的是 RocketMQ 消息队列。选择RocketMQ的原因是:

  • 开发语言优势。RocketMQ 使用 Java 语言开发,比起使用 Erlang 开发的 RabbitMQ 来说,有着更容易上手的阅读体验和受众。在遇到 RocketMQ 较为底层的问题时,大部分熟悉 Java 的同学都可以深入阅读其源码,分析、排查问题。
  • 社区氛围活跃。RocketMQ 是阿里巴巴开源且内部在大量使用的消息队列,说明 RocketMQ 是的确经得起残酷的生产环境考验的,并且能够针对线上环境复杂的需求场景提供相应的解决方案。
  • 特性丰富。根据 RocketMQ 官方文档的列举,其高级特性达到了 12 种,例如顺序消息、事务消息、消息过滤、定时消息等。顺序消息、事务消息、消息过滤、定时消息。RocketMQ 丰富的特性,能够为我们在复杂的业务场景下尽可能多地提供思路及解决方案。

RocketMQ如何保证消息不丢失?消息不被重复消费?

这两个问题是 RocketMQ 面试中的经典组合,分别对应消息的 可靠性(Reliability)幂等性(Idempotency)

问题解决方案
消息不丢失1. 生产者:同步发送 + 重试
2. Broker:同步刷盘 + 同步主从
3. 消费者:手动 ACK
不被重复消费消费者实现幂等性
• 数据库唯一索引
• Redis 缓存已消费 ID
• 业务状态判断

一句话总结:

  • 不丢失:靠 Broker 同步刷盘 + 同步主从 + 生产者/消费者正确使用 API
  • 不重复:靠 消费者自己实现幂等,因为 MQ 无法完全避免重试。

RocketMQ如何保证消息不丢失?

在分布式系统中,消息不丢失是核心要求。RocketMQ 通过 “生产者 -> Broker -> 消费者” 三个环节的协同机制,实现了高可靠的消息传递

RocketMQ 保证消息不丢失的关键是:在每个环节都采取措施,确保消息不会在传输或处理过程中丢失

我们分三个阶段来分析:

1. 生产者(Producer)端:确保消息成功发送

问题:

  • 网络抖动、Broker 宕机、发送超时等可能导致消息未到达 Broker。

解决方案:

1. 同步发送(send()) + 检查返回结果

try {
    SendResult result = producer.send(msg);
    // 必须检查发送状态
    if (result.getSendStatus() == SendStatus.SEND_OK) {
        System.out.println("消息发送成功");
    } else {
        // 失败处理:记录日志、重试、告警
    }
} catch (Exception e) {
    // 异常处理:重试或落库
}

2. 发送失败自动重试

  • RocketMQ Producer 默认会自动重试 2 次(可配置)。
  • 建议结合 try-catch 手动控制重试逻辑,避免无限重试。

3. 消息持久化到数据库(兜底方案)

  • 对于极高可靠性要求的场景(如金融交易),可先将消息落库(状态为“待发送”)。
  • 发送成功后更新状态为“已发送”。
  • 定时任务扫描“待发送”消息,进行补偿重发。

2. Broker 端:确保消息持久化存储

问题:

  • 消息写入内存后,Broker 宕机,未持久化到磁盘。

解决方案:

1. 同步刷盘(flushDiskType=SYNC_FLUSH

  • 消息写入 CommitLog 后,必须同步刷写到磁盘,才返回成功。
  • 优点:绝对不丢消息。
  • 缺点:性能较低(每次写都要 fsync)。

2. 异步刷盘(默认,flushDiskType=ASYNC_FLUSH

  • 消息写入内存后立即返回成功,后台线程异步刷盘。
  • 风险:如果 Broker 在刷盘前宕机,消息丢失。
  • 适用:对性能要求高,可容忍极短时间内的消息丢失。

3. 主从同步(brokerRole=SYNC_MASTER

  • 配置 同步双写(SYNC_MASTER) 模式:
    • 消息必须成功写入 Master 和 Slave 后,才返回成功。
    • 即使 Master 宕机,Slave 仍有完整数据。
  • 异步复制(ASYNC_MASTER):只写 Master,有丢失风险。

生产建议:

  • 高可靠场景SYNC_FLUSH + SYNC_MASTER
  • 高吞吐场景ASYNC_FLUSH + SYNC_MASTER(推荐平衡方案)

3. 消费者(Consumer)端:确保消息被正确消费

问题:

  • 消费者收到消息后,在处理过程中宕机,消息未完成业务逻辑。

解决方案:

1. 手动提交消费位点(ACK)

  • 集群模式(CLUSTERING):消费者处理完消息后,手动返回 ConsumeConcurrentlyStatus.CONSUME_SUCCESS,Broker 才会更新消费位点。
  • 如果消费者宕机未返回 ACK,Broker 会重新投递该消息(默认重试 16 次)。

2. 消费幂等性设计

  • 由于重试机制,消息可能被多次消费。
  • 必须在消费者端实现幂等处理(如数据库唯一索引、Redis 记录已处理 ID)。

3. 消费异常处理

  • 消费失败时返回 RECONSUME_LATER,触发重试。
  • 重试多次仍失败,进入死信队列(DLQ),人工干预。

如何保证消息不被重复消费?

⚠️ 重要前提:
RocketMQ 不保证消息不重复
因为在以下情况必然发生重试:

  • 消费者处理超时未返回 ACK
  • 消费者宕机
  • 网络抖动

所以,消费者必须自己实现幂等性

1. 为什么会有重复消费?
  • 消费者处理完消息,返回 ACK,但网络故障导致 Broker 未收到。
  • Broker 认为消息未消费成功,重新投递。
2. 如何实现消费幂等?(关键!)

方案 1:数据库唯一约束(推荐)

  • 将消息的 msgId 或业务唯一键(如订单号)作为数据库表的唯一索引
  • 插入时如果违反唯一约束,说明已处理,直接忽略。
CREATE TABLE order (order_id VARCHAR(64) PRIMARY KEY, ...);
-- 或
CREATE UNIQUE INDEX uk_msg_id ON consume_log (msg_id);

方案 2:Redis 记录已消费 ID

  • 使用 Redis 的 SET 或 String 类型记录已消费的 msgId 或业务 ID。
  • 每次消费前先检查:
String msgId = message.getMsgId();
Boolean exists = redisTemplate.hasKey("consumed:" + msgId);
if (exists) {
    return ConsumeConcurrentlyStatus.CONSUME_SUCCESS; // 重复,直接跳过
}
// 执行业务逻辑
redisTemplate.set("consumed:" + msgId, "1", 7, TimeUnit.DAYS);

方案 3:业务状态机控制

  • 在业务逻辑中判断当前状态是否允许执行。
Order order = orderService.getById(orderId);
if (order.getStatus() == OrderStatus.PAID) {
    return; // 已支付,无需重复处理
}
payOrder(order);

方案 4:使用 RocketMQ 的事务消息(复杂场景)

  • 通过事务消息 + 本地事务表,确保“本地操作”和“发消息”原子性,间接减少重复。

RabbitMQ

RabbitMQ 的整体架构和核心概念

  1. Publisher:消息发送者
  2. Consumer:消息的消费者
  3. Queue:消息队列,存储消息
  4. Exchange:交换机,负责路由消息。交换机只能路由和转发消息,不能存储消息
  5. VirtualHost:虚拟主机,用于数据隔离

RabbitMQ消息队列模型

    RabbitMQ 的消息队列模型是基于 AMQP(Advanced Message Queuing Protocol) 协议设计的,其核心思想是通过 解耦生产者与消费者,实现灵活、可靠的消息传递。

    组件作用类比
    Producer(生产者)发送消息到 RabbitMQ邮件发送人
    Exchange(交换机)接收生产者消息,并根据规则路由到 Queue邮局分拣中心
    Queue(队列)存储消息,等待消费者消费邮箱
    Binding(绑定)定义 Exchange 和 Queue 之间的路由关系分拣规则(如“北京邮件 → 北京邮箱”)
    Consumer(消费者)从 Queue 中拉取消息并处理收件人

    关键点:生产者不直接发消息给 Queue,而是发给 Exchange,由 Exchange 决定投递到哪个 Queue。

    典型工作模式(6 种)

    RabbitMQ 官方文档定义了 6 种经典使用模式:

    模式说明对应 Exchange
    1. Hello World(简单模式)1 Producer → 1 Queue → 1 ConsumerDirect
    2. Work Queues(工作队列)1 Queue → 多个 Consumer(轮询消费)Direct
    3. Publish/Subscribe(发布订阅)广播消息给所有消费者Fanout
    4. Routing(路由模式)按 Routing Key 精确路由Direct
    5. Topics(主题模式)按通配符路由Topic
    6. RPC(远程调用)请求-响应模型,使用临时回调队列Direct
    特性说明
    消息持久化Queue 和 Message 都需设为 durable=true 才能保证 Broker 重启不丢
    手动 ACKConsumer 处理成功后手动确认,避免消息丢失
    死信队列(DLQ)消息被拒绝、TTL 过期、队列满时进入 DLQ,用于异常处理
    延迟消息通过插件 rabbitmq-delayed-message-exchange 实现
    镜像队列高可用方案,Queue 在多个节点复制(已逐步被 Quorum Queue 替代)

    交换机

    真正的生产环境都会经过交换机来发送消息,而不是直接发送到队列

    交换机的作用:

    1. 接收 publisher 发送的消息
    2. 将消息按照规则路由到与交换机绑定的队列

    交换机的类型有以下三种:

    Fanout:广播交换机会将接收到的消息广播到每一个跟其绑定的 queue ,所以也叫广播模式

    • 路由规则:忽略 Routing Key,广播到所有绑定的 Queue
    • 适用场景:通知、日志广播、配置更新
    • 特点:最快,无需匹配计算

    Direct:定向:Direct 交换机会将接收到的消息根据规则路由到指定的队列,被称为定向路由

    • 每一个 Queue 都与 Exchange 设置一个 bindingKey
    • 发布者发送消息时,指定消息的 RoutingKey
    • Exchange 将消息路由到 bindingKey 与消息 routingKey 一致的队列

    需要注意的是:同一个队列可以绑定多个 bindingKey ,如果有多个队列绑定了同一个 bindingKey 就可以实现类似于 Fanout 交换机的效果。

    Topic:话题:

    • 路由规则:支持通配符匹配(* 匹配一个词,# 匹配多个词)
    • Routing Key 格式word1.word2.word3...
    • 示例
      • 绑定 key:*.error.#
      • 匹配:user.error.logsystem.error.db.connection
    • 适用场景:多维度消息分类(如日志级别+模块)

    RabbitMQ的特性你知道哪些?

    RabbitMQ 以 可靠性灵活性 和 易扩展性 为核心优势,适合需要稳定消息传递的复杂系统。其丰富的插件和协议支持使其在微服务、IoT、金融等领域广泛应用,比较核心的特性有如下:

    • 持久化机制:RabbitMQ 支持消息、队列和交换器的持久化。当启用持久化时,消息会被写入磁盘,即使 RabbitMQ 服务器重启,消息也不会丢失。例如,在声明队列时可以设置 durable 参数为 true 来实现队列的持久化:
    • 消息确认机制:提供了生产者确认和消费者确认机制。生产者可以设置 confirm 模式,当消息成功到达 RabbitMQ 服务器时,会收到确认消息;消费者在处理完消息后,可以向 RabbitMQ 发送确认信号,告知服务器该消息已被成功处理,服务器才会将消息从队列中删除。
    • 镜像队列:支持创建镜像队列,将队列的内容复制到多个节点上,提高消息的可用性和可靠性。当一个节点出现故障时,其他节点仍然可以提供服务,确保消息不会丢失。
    • 多种交换器类型:RabbitMQ 提供了多种类型的交换器,如直连交换器(Direct Exchange)、扇形交换器(Fanout Exchange)、主题交换器(Topic Exchange)和头部交换器(Headers Exchange)。不同类型的交换器根据不同的规则将消息路由到队列中。例如,扇形交换器会将接收到的消息广播到所有绑定的队列中;主题交换器则根据消息的路由键和绑定键的匹配规则进行路由。

    核心组件:生产者负责发送消息到 RabbitMQ、消费者负责从 RabbitMQ 接收并处理消息、RabbitMQ 本身负责存储和转发消息。

    交换机:交换机接收来自生产者的消息,并根据 routing key 和绑定规则将消息路由到一个或多个队列。

    持久化:RabbitMQ 支持消息的持久化,可以将消息保存在磁盘上,以确保在 RabbitMQ 重启后消息不丢失,队列也可以设置为持久化,以保证其结构在重启后不会丢失。

    确认机制:为了确保消息可靠送达,RabbitMQ 使用确认机制,消费者在处理完消息后发送确认给 RabbitMQ,未确认的消息会重新入队。

    高可用性:RabbitMQ 提供了集群模式,可以将多个 RabbitMQ 实例组成一个集群,以提高可用性和负载均衡。通过镜像队列,可以在多个节点上复制同一队列的内容,以防止单点故障。

    RabbitMQ的死信队列

    RabbitMQ 的死信队列(Dead Letter Exchange, DLX) 是一种非常重要的异常消息处理机制,用于捕获那些无法被正常消费的消息,避免消息丢失,并支持后续的人工干预或自动重试。

    什么是死信?什么情况下消息会变成“死信”?

    死信产生原因说明
    1. 消息被拒绝(Reject/Nack)且 requeue=false消费者明确表示“不要这条消息,也不要重新入队”
    2. 消息 TTL(Time-To-Live)过期消息在队列中存活时间超过设定的 TTL
    3. 队列达到最大长度限制(x-max-length)队列满了,新消息进来时,最早的消息被“挤出去”成为死信

    ✅ 注意:只有普通队列中的消息才可能变成死信,死信队列本身的消息不会再进入另一个死信队列。

    RabbitMQ 的刷盘机制

    RabbitMQ 的“刷盘机制”(即消息如何从内存持久化到磁盘)是保障消息可靠性的关键环节。

    abbitMQ 刷盘机制核心要点:

    1. 持久化消息写入 msg_store_persistent非持久化消息只在内存
    2. 默认 异步刷盘(约 1 秒一次),不保证消息写盘后才 ACK
    3. 通过 Publisher Confirms + 持久化配置 + Quorum Queue 可大幅提升可靠性。
    4. 若业务要求 “绝对不丢消息”,建议结合 应用层落库 + 补偿机制,而非完全依赖 MQ 刷盘。

    存储结构、刷盘时机、配置参数、性能影响 四个方面详细解析 RabbitMQ 的刷盘机制。

    RabbitMQ 的存储结构

    RabbitMQ 将持久化消息存储在两个核心文件中
    (位于 ./data/mnesia/<node>/msg_stores/vhosts/<vhost_id>/):

    文件类型作用特点
    msg_store_persistent存储所有 持久化消息内容消息体按顺序追加写入(append-only),支持批量写
    queue_index存储每个队列的 索引信息(如消息 ID、偏移量、状态)使用 WAL(Write-Ahead Log) + B+树 结构,保证索引一致性

    关键点

    • 只有标记为 delivery_mode=2 的消息才会写入 msg_store_persistent
    • 非持久化消息仅存在于内存,Broker 重启后丢失。

    刷盘时机:何时将数据写入磁盘?

    RabbitMQ 不会每条消息都立即刷盘,而是采用 延迟写 + 批量刷盘 策略,兼顾性能与可靠性。

    1. 消息写入内存缓冲区
    • 生产者发送持久化消息后,RabbitMQ 先将其写入 内存缓冲区 和 msg_store 的 write cache
    • 此时消息已对消费者可见,但尚未落盘。
    2. 触发刷盘的条件

    RabbitMQ 在以下情况会将缓冲区数据 fsync 到磁盘

    触发条件说明
    ① 定期刷盘(默认 1 秒)后台线程每秒执行一次 fsync,将缓存中的消息和索引刷盘
    ② 内存压力大当内存使用超过阈值(如 vm_memory_high_watermark),触发强制刷盘并释放内存
    ③ Broker 正常关闭关闭前会 flush 所有未刷盘数据
    ④ Publisher Confirms 要求强一致若开启 publisher confirms,部分实现可能等待刷盘后才发 ack(取决于配置)

    ⚠️ 注意:RabbitMQ 默认不保证“每条消息写盘后才返回 ACK”
    即使消息被确认,仍可能因宕机丢失(除非配合其他机制)。

    关键配置参数(影响刷盘行为)

    参数默认值作用
    disk_free_limit50MB磁盘剩余空间低于此值时,阻塞生产者
    vm_memory_high_watermark40% of RAM内存使用超限时,触发刷盘+流控
    msg_store_file_size_limit16MB单个 msg_store 文件最大大小,超限则新建文件
    queue_index_embed_msgs_below4096 bytes小于该大小的消息直接嵌入 queue_index,减少 I/O

    🔧 调优点

    • 提高 vm_memory_high_watermark 可减少刷盘频率,提升性能(但增加内存风险)
    • 降低 queue_index_embed_msgs_below 可减少小消息的磁盘 I/O

    何实现“强刷盘”?(确保不丢消息)

    虽然 RabbitMQ 默认刷盘策略有丢失风险,但可通过以下方式增强可靠性:

    ✅ 方案 1:启用 Publisher Confirms + 持久化队列/消息

    • 生产者开启 confirmSelect()
    • 队列声明为 durable=true
    • 消息设置 deliveryMode=2
    • 当收到 basic.ack 时,消息已至少写入磁盘缓存(但不一定 fsync)

    📌 仍不能 100% 保证不丢:若系统断电,磁盘缓存可能未落盘。

    RabbitMQ如何实现高效读写

    底层高效读写机制

    基于 Erlang 的轻量级进程模型

    • 每个连接、每个队列、每个消费者都由独立的轻量级 Erlang 进程处理,避免线程竞争。
    • 进程间通过消息传递通信,无锁设计,减少上下文切换开销。

    内存 + 磁盘混合存储策略

    • 热数据常驻内存:活跃队列的消息优先存于内存,读写极快。
    • 冷数据刷盘持久化
      • 当内存压力大时,自动将不活跃消息写入磁盘(queue_index_embed_msgs_below 控制阈值)。
      • 使用 顺序写日志(msg_store) 和 索引文件(queue_index),减少随机 I/O。
    • 读取时按需加载:消费者拉取消息时,若在磁盘则异步加载回内存。

    提升读写效率的关键优化点 1. 合理使用持久化(权衡性能与可靠)

    配置性能影响建议
    durable=true(队列持久化)⚠️ 中等必须开启(否则重启丢队列)
    delivery_mode=2(消息持久化)⚠️⚠️ 较大仅对关键消息开启,非关键消息可设为非持久化
    禁用持久化✅ 极高吞吐仅用于临时任务、可丢失场景

    消息丢失的情况

    消息丢失的情况主要有以下三种:

    1. 生产者向消息代理传递消息的过程中,消息丢失了
    2. 消息代理( RabbitMQ )把消息弄丢了
    3. 消费者把消息弄丢了

    1、生产者的可靠性

    1.1生产者重连

    由于网络问题,可能会出现客户端连接 RabbitMQ 失败的情况,我们可以通过配置开启连接 RabbitMQ 失败后的重连机制

    application.yml(将 host 更改为部署 RabbitMQ 的服务器的地址)

    spring:
      rabbitmq:
        host: 127.0.0.1
        port: 5672
        virtual-host: /blog
        username: CaiXuKun
        password: T1rhFXMGXIOYCoyi
        connection-timeout: 1s # 连接超时时间
        template:
          retry:
            enabled: true # 开启连接超时重试机制
            initial-interval: 1000ms # 连接失败后的初始等待时间
            multiplier: 1 # 连接失败后的等待时长倍数,下次等待时长 = (initial-interval) * multiplier
            max-attempts: 3 # 最大重试次数
    
    1.2生产者确认

    RabbitMQ 提供了 Publisher Confirm 和 Publisher Return 两种确认机制。开启确机制认后,如果 MQ 成功收到消息后,会返回确认消息给生产者,返回的结果有以下几种情况:

    1. 消息投递到了 MQ,但是路由失败,此时会通过 PublisherReturn 机制返回路由异常的原因,然后返回 ACK,告知生产者消息投递成功
    2. 临时消息投递到了 MQ,并且入队成功,返回 ACK,告知生产者消息投递成功
    3. 持久消息投递到了MQ,并且入队完成持久化,返回 ACK,告知生产者消息投递成功
    4. 其它情况都会返回 NACK,告知生产者消息投递失败

    在 publisher 服务中编写与生产者确认机制有关的配置信息( application.yml 文件)

    spring:
      rabbitmq:
        publisher-returns: true
        publisher-confirm-type: correlated
    ###
    publisher-confirm-type 有三种模式:
    none:关闭 confirm 机制
    simple:以同步阻塞等待的方式返回 MQ 的回执消息
    correlated:以异步回调方式的方式返回 MQ 的回执消息
    
    1. 生产者确认需要额外的网络开销和系统资源开销,尽量不要使用
    2. 如果一定要使用,无需开启 Publisher-Return 机制,因为路由失败一般是业务出了问题
    3. 对于返回 nack 的消息,可以尝试重新投递,如果依然失败,则记录异常消息

    2、消息代理(RabbitMQ)的可靠性

    在默认情况下,RabbitMQ 会将接收到的信息保存在内存中以降低消息收发的延迟,这样会导致两个问题:

    1. 一旦 RabbitMQ 宕机,内存中的消息会丢失
    2. 内存空间是有限的,当消费者处理过慢或者消费者出现故障或时,会导致消息积压,引发 MQ 阻塞( Paged Out 现象)

    怎么理解 MQ 阻塞呢,当队列的空间被消息占满了之后,RabbitMQ 会先把老旧的信息存到磁盘,为新消息腾出空间,在这个过程中,整个 MQ 是被阻塞的,也就是说,在 MQ 完成这一系列工作之前,无法处理已有的消息和接收新的消息

    2.1数据持久化

    RabbitMQ 实现数据持久化包括 3 个方面:

    1. 交换机持久化
    2. 队列持久化
    3. 消息持久化

    注意事项:

    1. 利用 SpringAMQP 创建的交换机、队列、消息,默认都是持久化的
    2. 在 RabbitMQ 控制台创建的交换机、队列默认是持久化的,而消息默认是存在内存中( 3.12 版本之前默认存放在内存,3.12 版本及之后默认先存放在磁盘,消费者处理消息时才会将消息取出来放到内存中)
    2.2 LazyQueue( 3.12 版本后所有队列都是 Lazy Queue 模式)

    从 RabbitMQ 的 3.6.0 版本开始,增加了 Lazy Queue 的概念,也就是惰性队列,惰性队列的特征如下:

    1. 接收到消息后直接存入磁盘而非内存(内存中只保留最近的消息,默认 2048条 )
    2. 消费者要处理消息时才会从磁盘中读取并加载到内存
    3. 支持数百万条的消息存储,在 3.12 版本后,所有队列都是 Lazy Queue 模式,无法更改

    开启持久化和生产者确认时,RabbitMQ 只有在消息持久化完成后才会给生产者返回 ACK 回执

    3消费者的可靠性

    3.1消费者确认机制

    为了确认消费者是否成功处理消息,RabbitMQ 提供了消费者确认机制(Consumer Acknowledgement)。处理消息后,消费者应该向 RabbitMQ 发送一个回执,告知 RabbitMQ 消息的处理状态,回执有三种可选值:

    1. ack:成功处理消息,RabbitMQ 从队列中删除该消息
    2. nack:消息处理失败,RabbitMQ 需要再次投递消息
    3. reject:消息处理失败并拒绝该消息,RabbitMQ 从队列中删除该消息

    SpringAMQP 已经实现了消息确认功能,并允许我们通过配置文件选择 ACK 的处理方式,有三种方式:

    1. none:不处理,即消息投递给消费者后立刻 ack,消息会会立刻从 MQ 中删除,非常不安全,不建议使用
    2. manual:手动模式。需要自己在业务代码中调用 api,发送 ack 或 reject ,存在业务入侵,但更灵活
    3. auto:自动模式,SpringAMQP 利用 AOP 对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回 ack,当业务出现异常时,会根据异常的类型返回不同结果:
      • 如果是业务异常,会自动返回 nack
      • 如果是消息处理或校验异常,自动返回 reject

    开启消息确认机制,需要在 application.yml 文件中编写相关的配置

    spring:
      rabbitmq:
        listener:
          simple:
            prefetch: 1
            acknowledge-mode: none
    
    3.2 失败重试机制

    当消费者出现异常后,消息会不断重新入队,重新发送给消费者,然后再次发生异常,再次 requeue(重新入队),陷入 无限循环,给 RabbitMQ 带来不必要的压力

    我们可以利用 Spring 提供的 retry 机制,在消费者出现异常时利用本地重试,而不是无限制地重新入队

    在 application.yml 配置文件中开启失败重试机制

    spring:
      rabbitmq:
        listener:
          simple:
            prefetch: 1
            acknowledge-mode: auto
            retry:
              enabled: true # 开启消息消费失败重试机制
              initial-interval: 1000ms # 消息消费失败后的初始等待时间
              multiplier: 1 # 消息消费失败后的等待时长倍数,下次等待时长 = (initial-interval) * multiplier
              max-attempts: 3 # 最大重试次数
              stateless: true # true表示无状态,false表示有状态,如果业务中包含事务,需要设置为false
    
    4.3 失败消息的处理策略

    开启重试模式后,如果重试次数耗尽后消息依然处理失败,则需要由 MessageRecoverer 接口来处理, MessageRecoverer 有三个实现类:

    1. RejectAndDontRequeueRecoverer:重试次数耗尽后,直接 reject,丢弃消息,默认就是这种方式
    2. ImmediateRequeueMessageRecoverer:重试次数耗尽后,返回 nack,消息重新入队
    3. RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机

    总结:消费者如何保证消息一定被消费?

    1. 开启消费者确认机制为 auto ,由 Spring 帮我们确认,消息处理成功后返回 ack,异常时返回 nack
    2. 开启消费者失败重试机制,并设置 MessageRecoverer ,多次重试失败后将消息投递到异常交换机,交由人工处理
    评论
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值