文章目录
- 前言
- [一、🛑 消费端限流 (QoS)](#一、🛑 消费端限流 (QoS))
-
- [1. 为什么需要限流?](#1. 为什么需要限流?)
- [2. 核心原理:`prefetchCount`](#2. 核心原理:
prefetchCount) - [3. 关键前提:必须手动 ACK](#3. 关键前提:必须手动 ACK)
- [4. Spring Boot 配置示例](#4. Spring Boot 配置示例)
- [二、⏳ 消息过期时间 (TTL)](#二、⏳ 消息过期时间 (TTL))
-
- [1. 两种设置方式](#1. 两种设置方式)
- [2. 过期消息的去向](#2. 过期消息的去向)
- [三、⚰️ 死信队列 (DLQ)](#三、⚰️ 死信队列 (DLQ))
-
- [1. 消息成为"死信"的三种场景](#1. 消息成为“死信”的三种场景)
- [2. 死信流转流程](#2. 死信流转流程)
- [四、🚀 高级应用:实现延迟队列](#四、🚀 高级应用:实现延迟队列)
-
- [1. 核心思想](#1. 核心思想)
- [2. 典型场景:订单超时自动关闭](#2. 典型场景:订单超时自动关闭)
前言
限流、TTL、死信队列
构建高可用、高可靠 RabbitMQ 系统的"三驾马车"。它们分别解决了消费过载 、消息过期 和异常处理的问题。。
一、🛑 消费端限流 (QoS)
消费端限流,也称为服务质量(QoS),其核心目的是防止消费者被消息淹没。
1. 为什么需要限流?
想象一个场景:你的 RabbitMQ 队列中积压了 10 万条消息,此时你启动了消费者服务。如果不限流,RabbitMQ 会瞬间将这 10 万条消息全部推送给消费者。如果消费者的处理能力有限(例如需要调用耗时的外部 API),这会导致:
- 内存溢出 (OOM):大量消息堆积在内存中。
- 服务假死:CPU 和线程资源被耗尽。
- 系统崩溃:整个消费者服务宕机。
限流就是给消费者一个"安全阀",让它按照自己的能力去处理消息。
2. 核心原理:prefetchCount
RabbitMQ 通过 basic.qos 命令实现限流,其中最关键的参数是 prefetchCount。
- 含义 :它定义了每个消费者最多能同时持有(即已接收但未确认)的消息数量。
- 工作流程 :
- 设置
prefetchCount = 5。 - 消费者启动后,RabbitMQ 会立即推送 5 条消息给它。
- 消费者处理完其中 1 条并发送
ACK确认后,RabbitMQ 才会再推送第 6 条消息给它。 - 这个过程持续进行,确保消费者始终只处理 5 条消息,实现了"按需拉取"。
- 设置
3. 关键前提:必须手动 ACK
限流只在手动确认(Manual ACK)模式下生效! 如果使用自动 ACK,消息一旦被推送出去就被视为已消费,RabbitMQ 会继续疯狂推送,prefetchCount 设置将完全失效。
4. Spring Boot 配置示例
在 application.yml 中,通过以下配置即可轻松实现限流:
yaml
spring:
rabbitmq:
listener:
simple:
acknowledge-mode: manual # 1. 开启手动ACK
prefetch: 10 # 2. 设置每个消费者最多预取10条消息
concurrency: 5 # 3. 启动5个消费者线程
在这个配置下,你的应用最多可以同时处理 10 * 5 = 50 条消息,有效控制了并发量。
二、⏳ 消息过期时间 (TTL)
TTL(Time To Live)即消息的生存时间。超过这个时间的消息会被视为"过期",RabbitMQ 会将其丢弃。
1. 两种设置方式
RabbitMQ 支持两种 TTL 设置,它们可以组合使用,以较短的时间为准。
- 队列级 TTL
- 作用范围:对整个队列生效,队列中所有消息的过期时间都相同。
- 设置方式 :在声明队列时,通过
x-message-ttl参数设置,单位为毫秒。 - 优点:性能高。RabbitMQ 只需定期检查队首消息是否过期即可。
- 适用场景:所有消息具有相同的有效期,如"所有订单30分钟后关闭"。
- 消息级 TTL
- 作用范围:对单条消息生效,每条消息可以有不同的过期时间。
- 设置方式 :在发布消息时,通过
expiration属性设置,单位为毫秒。 - 缺点:性能开销较大。因为每条消息的过期时间不同,RabbitMQ 无法只检查队首,需要在消息即将被投递时才判断是否过期(懒删除)。
- 适用场景:不同消息需要不同的有效期。
2. 过期消息的去向
默认情况下,过期消息会被直接删除。但更常见的做法是结合死信队列来处理。
三、⚰️ 死信队列 (DLQ)
死信队列(Dead Letter Queue, DLQ)是一个"避难所",用于存放那些无法正常被消费的消息。
1. 消息成为"死信"的三种场景
- 消费者拒收 :消费者处理消息失败,调用
basicNack或basicReject,并设置requeue=false(不重新入队)。 - 消息过期:消息的 TTL 时间到期。
- 队列满了 :队列长度超过了设置的最大值(
x-max-length),最早的消息会被挤出成为死信。
2. 死信流转流程
普通队列 → 死信交换机 (DLX) → 死信队列 (DLQ)
- 在声明普通队列时,通过
x-dead-letter-exchange参数指定一个死信交换机。 - 当消息在普通队列中成为死信后,RabbitMQ 会将其重新发布到这个死信交换机。
- 死信交换机根据路由键(
x-dead-letter-routing-key)将消息路由到最终的死信队列。 - 你可以为死信队列配置专门的消费者,用于记录日志、发送告警或进行人工干预。
四、🚀 高级应用:实现延迟队列
将 TTL 和 死信队列 结合,是 RabbitMQ 实现延迟队列的经典模式。RabbitMQ 本身不直接支持延迟消息,但可以通过这个技巧来实现。
1. 核心思想
让消息在"普通队列"中等待一段时间(TTL),过期后自动转为死信,进入"死信队列",由"死信队列"的消费者进行真正的业务处理。这个"等待时间"就是延迟时间。
2. 典型场景:订单超时自动关闭
- 创建组件:
- 延迟队列 :一个没有消费者的普通队列,设置
x-message-ttl=1800000(30分钟),并绑定到死信交换机。 - 死信交换机 (DLX):一个普通的 Direct 或 Topic 交换机。
- 死信队列 (DLQ):一个有消费者的队列,绑定到死信交换机。
- 工作流程:
- 用户下单后,生产者向"延迟队列"发送一条消息。
- 由于"延迟队列"没有消费者,消息会一直等待。
- 30分钟后,消息 TTL 过期,成为死信。
- 死信被转发到"死信交换机",再路由到"死信队列"。
- "死信队列"的消费者收到消息,执行业务逻辑,如"检查订单支付状态,若未支付则关闭订单"。
通过这种方式,你巧妙地利用 RabbitMQ 的现有特性,实现了强大的定时任务功能。