消息队列使用场景有哪些?
消息队列(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这种更高吞吐的。
消息丢失怎么解决的?
消息丢失的情况主要有以下三种:
- 生产者向消息代理传递消息的过程中,消息丢失了
- 消息代理( RabbitMQ )把消息弄丢了
- 消费者把消息弄丢了
使用一个消息队列,其实就分为三大块:生产者、中间件、消费者,所以要保证消息就是保证三个环节都不能丢失数据。
消息生产阶段:生产者会不会丢消息,取决于生产者对于异常情况的处理是否合理。从消息被生产出来,然后提交给 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 的整体架构和核心概念
- Publisher:消息发送者
- Consumer:消息的消费者
- Queue:消息队列,存储消息
- Exchange:交换机,负责路由消息。交换机只能路由和转发消息,不能存储消息
- 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 Consumer Direct 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 重启不丢手动 ACK Consumer 处理成功后手动确认,避免消息丢失 死信队列(DLQ) 消息被拒绝、TTL 过期、队列满时进入 DLQ,用于异常处理 延迟消息 通过插件 rabbitmq-delayed-message-exchange实现镜像队列 高可用方案,Queue 在多个节点复制(已逐步被 Quorum Queue 替代)
交换机
真正的生产环境都会经过交换机来发送消息,而不是直接发送到队列
交换机的作用:
- 接收 publisher 发送的消息
- 将消息按照规则路由到与交换机绑定的队列
交换机的类型有以下三种:
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.log、system.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 刷盘机制核心要点:
- 持久化消息写入
msg_store_persistent,非持久化消息只在内存。 - 默认 异步刷盘(约 1 秒一次),不保证消息写盘后才 ACK。
- 通过 Publisher Confirms + 持久化配置 + Quorum Queue 可大幅提升可靠性。
- 若业务要求 “绝对不丢消息”,建议结合 应用层落库 + 补偿机制,而非完全依赖 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_limit | 50MB | 磁盘剩余空间低于此值时,阻塞生产者 |
vm_memory_high_watermark | 40% of RAM | 内存使用超限时,触发刷盘+流控 |
msg_store_file_size_limit | 16MB | 单个 msg_store 文件最大大小,超限则新建文件 |
queue_index_embed_msgs_below | 4096 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(消息持久化)⚠️⚠️ 较大 仅对关键消息开启,非关键消息可设为非持久化 禁用持久化 ✅ 极高吞吐 仅用于临时任务、可丢失场景
消息丢失的情况
消息丢失的情况主要有以下三种:
- 生产者向消息代理传递消息的过程中,消息丢失了
- 消息代理( RabbitMQ )把消息弄丢了
- 消费者把消息弄丢了
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 成功收到消息后,会返回确认消息给生产者,返回的结果有以下几种情况:
- 消息投递到了 MQ,但是路由失败,此时会通过 PublisherReturn 机制返回路由异常的原因,然后返回 ACK,告知生产者消息投递成功
- 临时消息投递到了 MQ,并且入队成功,返回 ACK,告知生产者消息投递成功
- 持久消息投递到了MQ,并且入队完成持久化,返回 ACK,告知生产者消息投递成功
- 其它情况都会返回 NACK,告知生产者消息投递失败
在 publisher 服务中编写与生产者确认机制有关的配置信息( application.yml 文件)
spring: rabbitmq: publisher-returns: true publisher-confirm-type: correlated ### publisher-confirm-type 有三种模式: none:关闭 confirm 机制 simple:以同步阻塞等待的方式返回 MQ 的回执消息 correlated:以异步回调方式的方式返回 MQ 的回执消息
- 生产者确认需要额外的网络开销和系统资源开销,尽量不要使用
- 如果一定要使用,无需开启 Publisher-Return 机制,因为路由失败一般是业务出了问题
- 对于返回 nack 的消息,可以尝试重新投递,如果依然失败,则记录异常消息
2、消息代理(RabbitMQ)的可靠性
在默认情况下,RabbitMQ 会将接收到的信息保存在内存中以降低消息收发的延迟,这样会导致两个问题:
- 一旦 RabbitMQ 宕机,内存中的消息会丢失
- 内存空间是有限的,当消费者处理过慢或者消费者出现故障或时,会导致消息积压,引发 MQ 阻塞( Paged Out 现象)
怎么理解 MQ 阻塞呢,当队列的空间被消息占满了之后,RabbitMQ 会先把老旧的信息存到磁盘,为新消息腾出空间,在这个过程中,整个 MQ 是被阻塞的,也就是说,在 MQ 完成这一系列工作之前,无法处理已有的消息和接收新的消息
2.1数据持久化
RabbitMQ 实现数据持久化包括 3 个方面:
- 交换机持久化
- 队列持久化
- 消息持久化
注意事项:
- 利用 SpringAMQP 创建的交换机、队列、消息,默认都是持久化的
- 在 RabbitMQ 控制台创建的交换机、队列默认是持久化的,而消息默认是存在内存中( 3.12 版本之前默认存放在内存,3.12 版本及之后默认先存放在磁盘,消费者处理消息时才会将消息取出来放到内存中)
2.2 LazyQueue( 3.12 版本后所有队列都是 Lazy Queue 模式)
从 RabbitMQ 的
3.6.0版本开始,增加了 Lazy Queue 的概念,也就是惰性队列,惰性队列的特征如下:
- 接收到消息后直接存入磁盘而非内存(内存中只保留最近的消息,默认 2048条 )
- 消费者要处理消息时才会从磁盘中读取并加载到内存
- 支持数百万条的消息存储,在 3.12 版本后,所有队列都是 Lazy Queue 模式,无法更改
开启持久化和生产者确认时,RabbitMQ 只有在消息持久化完成后才会给生产者返回 ACK 回执
3消费者的可靠性
3.1消费者确认机制
为了确认消费者是否成功处理消息,RabbitMQ 提供了消费者确认机制(Consumer Acknowledgement)。处理消息后,消费者应该向 RabbitMQ 发送一个回执,告知 RabbitMQ 消息的处理状态,回执有三种可选值:
- ack:成功处理消息,RabbitMQ 从队列中删除该消息
- nack:消息处理失败,RabbitMQ 需要再次投递消息
- reject:消息处理失败并拒绝该消息,RabbitMQ 从队列中删除该消息
SpringAMQP 已经实现了消息确认功能,并允许我们通过配置文件选择 ACK 的处理方式,有三种方式:
- none:不处理,即消息投递给消费者后立刻 ack,消息会会立刻从 MQ 中删除,非常不安全,不建议使用
- manual:手动模式。需要自己在业务代码中调用 api,发送 ack 或 reject ,存在业务入侵,但更灵活
- auto:自动模式,SpringAMQP 利用 AOP 对我们的消息处理逻辑做了环绕增强,当业务正常执行时则自动返回 ack,当业务出现异常时,会根据异常的类型返回不同结果:
- 如果是业务异常,会自动返回 nack
- 如果是消息处理或校验异常,自动返回 reject
开启消息确认机制,需要在
application.yml文件中编写相关的配置spring: rabbitmq: listener: simple: prefetch: 1 acknowledge-mode: none3.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表示有状态,如果业务中包含事务,需要设置为false4.3 失败消息的处理策略
开启重试模式后,如果重试次数耗尽后消息依然处理失败,则需要由 MessageRecoverer 接口来处理, MessageRecoverer 有三个实现类:
RejectAndDontRequeueRecoverer:重试次数耗尽后,直接 reject,丢弃消息,默认就是这种方式ImmediateRequeueMessageRecoverer:重试次数耗尽后,返回 nack,消息重新入队RepublishMessageRecoverer:重试耗尽后,将失败消息投递到指定的交换机总结:消费者如何保证消息一定被消费?
- 开启消费者确认机制为 auto ,由 Spring 帮我们确认,消息处理成功后返回 ack,异常时返回 nack
- 开启消费者失败重试机制,并设置
MessageRecoverer,多次重试失败后将消息投递到异常交换机,交由人工处理

1015

被折叠的 条评论
为什么被折叠?



