RabbitMQ多线程消费与死信队列方案


延迟启动rabbitmq,动态停止或启动消费

https://blog.csdn.net/duanyuanjun/article/details/128674859


rabbitmq 发送消息跟踪

https://blog.csdn.net/duanyuanjun/article/details/128664318


动态创建Rabbitmq队列和交换器

https://blog.csdn.net/duanyuanjun/article/details/128676268


复制代码
==========死信队列详细流程方便理解




# 统计平台 RabbitMQ 多线程消费 + 死信队列 技术方案

## 一、架构总览

```
生产者                    RabbitMQ                      消费者(统计平台)
───────                  ────────                      ──────────────
                         ┌─────────────────┐
  publish ──────────────>│ flink.exchange   │
                         │ (topic)          │
                         └───┬─────────┬───┘
                             │         │
                      flink.msg   flink.msg.dead
                             │         │
                             ▼         ▼
               ┌──────────────────┐  ┌─────────────────────┐
               │ 主队列            │  │ 死信 Exchange        │
               │ statistical.lite │  │ flink.exchange.dead  │
               │ .aggregator      │  │ (direct)             │
               │                  │  └──────────┬──────────┘
               │ x-max-length     │             │
               │   =100000        │      flink.msg.dead
               │ x-dead-letter-   │             │
               │   exchange=      │             ▼
               │   flink.exchange ├─> ┌─────────────────────┐
               │   .dead          │   │ 死信队列             │
               │ x-dead-letter-   │   │ statistical.lite    │
               │   routing-key=   │   │ .aggregator.dead    │
               │   flink.msg.dead │   │ x-message-ttl=24h   │
               └───────┬──────────┘   │ x-max-length=50000  │
                       │              └──────────┬──────────┘
                       ▼                         │
               ┌──────────────┐          死信消费者(可选)
               │ 多线程消费    │          记录日志/告警/重放
               │ 3~10 并发     │
               │ prefetch=250 │
               └──────────────┘
```

---

## 二、核心代码

### 2.1 容器工厂配置 --- RabbitMQConfig.java

**文件路径**:`com.statistical.config.RabbitMQConfig`

**职责**:自定义 `SimpleRabbitListenerContainerFactory`,覆盖 Spring Boot 默认工厂,实现并发消费。

```java
@Slf4j
@Configuration
@ConditionalOnBean(AggregatorManager.class)
public class RabbitMQConfig {

    @Value("${statistical.aggregator.listener.min-concurrency:3}")
    private int minConcurrency;

    @Value("${statistical.aggregator.listener.max-concurrency:10}")
    private int maxConcurrency;

    @Value("${statistical.aggregator.listener.prefetch:250}")
    private int prefetch;

    @Bean("rabbitListenerContainerFactory")
    public SimpleRabbitListenerContainerFactory rabbitListenerContainerFactory(
            ConnectionFactory connectionFactory) {
        SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
        factory.setConnectionFactory(connectionFactory);
        factory.setConcurrentConsumers(minConcurrency);
        factory.setMaxConcurrentConsumers(maxConcurrency);
        factory.setPrefetchCount(prefetch);
        factory.setConsumerTagStrategy(
                queue -> "lite-consumer-" + queue + "-" + System.currentTimeMillis());
        log.info("[RabbitMQ-Config] 并发消费容器工厂初始化, minConcurrency={}, maxConcurrency={}, prefetch={}",
                minConcurrency, maxConcurrency, prefetch);
        return factory;
    }
}
```

### 2.2 消息监听器 --- StatisticalMessageListener.java

**文件路径**:`com.statistical.lite.listener.StatisticalMessageListener`

**职责**:统一 MQ 消费入口,通过 `@RabbitListener` 实现多线程消费。

```java
@Slf4j
@Component
@ConditionalOnBean(AggregatorManager.class)
public class StatisticalMessageListener {

    @Autowired
    private AggregatorManager aggregatorManager;

    @RabbitListener(
            concurrency = "${statistical.aggregator.listener.min-concurrency:3}"
                       + "-${statistical.aggregator.listener.max-concurrency:10}",
            bindings = @QueueBinding(
                    value = @Queue(
                            name = "${statistical.aggregator.queue-name:statistical.lite.aggregator}",
                            durable = "true"),
                    exchange = @Exchange(
                            name = "${statistical.aggregator.exchange-name:flink.exchange}",
                            type = "topic"),
                    key = "${statistical.aggregator.routing-key:flink.msg}"
            )
    )
    public void onMessage(String message) {
        try {
            MqMessageWrapper.MessageWrapper vo =
                    JSONObject.parseObject(message, MqMessageWrapper.MessageWrapper.class);
            aggregatorManager.submit(vo.getData());
        } catch (Exception e) {
            log.error("[Lite-Listener] 消息解析失败,已丢弃: {}", message, e);
        }
    }
}
```

---

## 三、配置参数详解 --- application.yml

```yaml
statistical:
  aggregator:
    # ---- RabbitMQ 队列绑定 ----
    queue-name: statistical.lite.aggregator     # 主队列
    exchange-name: flink.exchange               # 上游 Exchange
    routing-key: flink.msg                      # 上游 Routing Key

    # ---- RabbitMQ 并发消费配置 ----
    listener:
      min-concurrency: 3       # 最小并发消费者数
      max-concurrency: 10      # 最大并发消费者数(弹性伸缩上限)
      prefetch: 250            # 每个消费者预取数

    # ---- 死信队列配置(待落地) ----
    dlx-exchange: flink.exchange.dead           # 死信 Exchange
    dlx-routing-key: flink.msg.dead             # 死信 Routing Key
    dlx-queue: statistical.lite.aggregator.dead # 死信队列名
    queue-max-length: 100000                    # 主队列最大长度
    dlx-ttl-ms: 86400000                        # 死信队列消息 TTL(24h)
    dlx-max-length: 50000                       # 死信队列最大长度
```

### 参数选择参考

| 参数 | 1C1G | 2C2G | 4C4G | 说明 |
|------|------|------|------|------|
| `min-concurrency` | 2 | 3 | 5 | 1C 上线程多反而因上下文切换降低吞吐 |
| `max-concurrency` | 5 | 10 | 20 | 弹性伸缩上限,按 CPU 核数调整 |
| `prefetch` | 250 | 250 | 250 | submit() 极轻量,大批预取提升吞吐 |
| `queue-max-length` | 100000 | 100000 | 100000 | 防止队列无限增长拖垮 RabbitMQ |

---

## 四、并发安全保证

多线程消费后,多个消费者线程并发调用 `aggregatorManager.submit()`,各组件的线程安全性:

| 组件 | 线程安全机制 | 说明 |
|------|-------------|------|
| **GuavaAggregatorManager** | `Cache.asMap().compute()` | 原子操作,同一 key 串行执行,不同 key 并发安全 |
| **DisruptorAggregatorManager** | `ProducerType.MULTI` + RingBuffer CAS | 多生产者安全发布,单 EventHandler 串行聚合 |
| **TenantAlgoDayAccumulator** | `LongAdder` + `synchronized` | count/sum 使用 LongAdder 无锁累加,max/min 使用 synchronized 保证原子 |
| **FlushScheduler** | `ReentrantLock` + `volatile` | storageLock/prometheusLock 双锁分离,volatile 防重入 |
| **LiteMysqlSinkService** | `ON DUPLICATE KEY UPDATE` | 天然幂等,重复写入不影响正确性 |

---

## 五、死信队列方案(待落地)

### 5.1 为什么需要死信队列

| 场景 | 无死信队列 | 有死信队列 |
|------|-----------|-----------|
| 消费不过来,队列堆积 | 队列无限增长 → RabbitMQ 内存溢出 → 流控阻塞生产者 | 队列达上限后最老消息转入死信队列,主队列长度可控 |
| 需要排查丢失消息 | 无法追踪 | 死信队列保留溢出消息,带 x-death Header 可溯源 |
| 极端情况 | RabbitMQ 宕机 | 仅死信队列增长,RabbitMQ 内存可控 |

### 5.2 死信触发条件

| 条件 | 说明 |
|------|------|
| 消息被 `basic.reject`/`basic.nack` 且 `requeue=false` | 消费者主动拒绝 |
| 消息 TTL 过期(`x-message-ttl`) | 消息超时 |
| 队列达到最大长度(`x-max-length`) | **本项目主要场景**:最老消息被挤出 |

### 5.3 死信消息附加 Header

消息变为死信后,RabbitMQ 自动添加 `x-death` Header:

| 字段 | 值示例 | 含义 |
|------|--------|------|
| `x-death[0].reason` | `"maxlen"` | 死因:maxlen / rejected / expired |
| `x-death[0].queue` | `"statistical.lite.aggregator"` | 原始队列名 |
| `x-death[0].exchange` | `"flink.exchange"` | 原始 Exchange |
| `x-death[0].routing-keys` | `["flink.msg"]` | 原始 Routing Key |
| `x-death[0].time` | `"2026-05-14T10:30:00+08:00"` | 变为死信的时间 |
| `x-death[0].count` | `1` | 被死信路由的次数 |

### 5.4 待落地的代码改造

主队列增加 `arguments`:

```java
@RabbitListener(
        concurrency = "${statistical.aggregator.listener.min-concurrency:3}"
                   + "-${statistical.aggregator.listener.max-concurrency:10}",
        bindings = {
                // 主队列绑定
                @QueueBinding(
                        value = @Queue(
                                name = "${statistical.aggregator.queue-name:statistical.lite.aggregator}",
                                durable = "true",
                                arguments = {
                                        @Argument(name = "x-max-length",
                                                  value = "${statistical.aggregator.queue-max-length:100000}",
                                                  type = "java.lang.Integer"),
                                        @Argument(name = "x-dead-letter-exchange",
                                                  value = "${statistical.aggregator.dlx-exchange:flink.exchange.dead}"),
                                        @Argument(name = "x-dead-letter-routing-key",
                                                  value = "${statistical.aggregator.dlx-routing-key:flink.msg.dead}")
                                }
                        ),
                        exchange = @Exchange(
                                name = "${statistical.aggregator.exchange-name:flink.exchange}",
                                type = "topic"),
                        key = "${statistical.aggregator.routing-key:flink.msg}"
                ),
                // 死信 Exchange + 死信队列绑定
                @QueueBinding(
                        value = @Queue(
                                name = "${statistical.aggregator.dlx-queue:statistical.lite.aggregator.dead}",
                                durable = "true",
                                arguments = {
                                        @Argument(name = "x-message-ttl",
                                                  value = "${statistical.aggregator.dlx-ttl-ms:86400000}",
                                                  type = "java.lang.Integer"),
                                        @Argument(name = "x-max-length",
                                                  value = "${statistical.aggregator.dlx-max-length:50000}",
                                                  type = "java.lang.Integer")
                                }
                        ),
                        exchange = @Exchange(
                                name = "${statistical.aggregator.dlx-exchange:flink.exchange.dead}",
                                type = "direct"),
                        key = "${statistical.aggregator.dlx-routing-key:flink.msg.dead}"
                )
        }
)
```

> **注意**:RabbitMQ 中已存在的队列不支持修改 arguments。落地时需先在管理端删除旧队列,再启动应用让新声明生效。生产环境建议在 RabbitMQ 管理端预创建队列。

### 5.5 死信队列处理方式

| 方式 | 适用场景 | 操作 |
|------|---------|------|
| 人工排查 | 偶发溢出 | RabbitMQ Management 查看,定位原因 |
| 独立消费者 | 需自动化 | 为死信队列写独立 `@RabbitListener`,记录日志/告警 |
| 重放回主队列 | 需重新消费 | 死信消费者将消息重新 publish 到原 Exchange(注意避免再溢出循环) |
| 定时清理 | 数据量大 | 死信队列配 `x-message-ttl` + `x-expires` 自动清理 |

---

## 六、容量评估(1C1G 基准)

### 6.1 消息处理耗时分解(Guava 模式)

| 步骤 | 操作 | 预估耗时 |
|------|------|---------|
| JSON 反序列化 | `JSONObject.parseObject()` | ~0.05-0.15ms |
| 日期格式化 | `KeyUtils.todayStartTime()` | ~0.01ms |
| 6× Cache.compute() | ConcurrentHashMap 原子 + LongAdder 累加 | ~0.06-0.12ms |
| synchronized max/min | `updateMaxMin()` | ~0.005ms |
| **合计** | | **~0.15-0.3ms/条** |

### 6.2 吞吐量预估

| 模式 | 1C1G 持续 | 1C1G 突发 | 2C2G 持续 | 4C4G 持续 |
|------|----------|----------|----------|----------|
| Guava | 1,000-2,000 TPS | ~3,000 TPS | 3,000-5,000 TPS | 5,000-8,000 TPS |
| Disruptor | 3,000-5,000 TPS | ~8,000 TPS | 5,000-10,000 TPS | 10,000-20,000 TPS |

### 6.3 消费不过来时的连锁反应

```
阶段1:队列堆积(无感知)
  → 消息延迟增大,但数据不丢

阶段2:RabbitMQ 内存告警
  → 超过 vm_memory_high_watermark(默认 40%)
  → 阻塞生产者连接(流控)
  → 内存回到安全水位后自动恢复

阶段3:极端情况
  → 无上限队列 → RabbitMQ 磁盘满 → 宕机
  → 加了死信队列后 → 主队列长度锁死在 max-length → 安全
```

---

## 七、关键注意事项

| 事项 | 说明 |
|------|------|
| **队列已存在时 arguments 不可变** | 修改队列参数前必须先删除旧队列,否则应用启动报错 |
| **x-dead-letter-routing-key 必须设** | 不设则死信用原 routing key 路由,会回到主队列造成死循环 |
| **x-max-length 计数包含 unacked** | 10 消费者 × 250 prefetch = 2,500 在途消息也占额度 |
| **@ConditionalOnBean 保证条件生效** | `statistical.aggregator.mode=off` 时不创建工厂和监听器 |
| **水平扩容无需改代码** | 多实例消费同一队列,RabbitMQ 自动轮询分发 |
| **prefetch=250 是 Spring Boot 默认值** | 本项目 submit() 极轻量,250 合理;如消费者处理慢可降低 |
相关推荐
tsyjjOvO1 小时前
深入浅出 RabbitMQ:从原理到实战
分布式·rabbitmq
爱吃苹果的梨叔1 小时前
2026年分布式坐席系统技术指南:从KVM延长到全IP坐席协作
分布式·网络协议·tcp/ip
容器魔方1 小时前
Kthena 核心原语:ModelServing CRD 如何定义分布式推理“新标准”?
大数据·分布式·云原生·容器·云计算
笨鸟先飞的橘猫2 小时前
基于Skynet的分布式游戏场景题:大型MMO的跨服战场系统设计
分布式·学习·游戏·面试·lua
2603_954708312 小时前
微电网分布式电源接入技术:光伏、风电的适配设计
人工智能·分布式·物联网·架构·系统架构·能源
团象科技2 小时前
回望2026出海分水岭:分布式云如何定义企业全球化终局竞争力
分布式
武超杰3 小时前
RabbitMQ 消息队列集成与使用
分布式·rabbitmq
China_Yanhy3 小时前
【云原生 AI 实战】EKS 搭建 GPU 超算集群:从零拉起节点到 PyTorchJob 分布式训练 (附 EFA 加速避坑指南)
人工智能·分布式·云原生
zhangzeyuaaa3 小时前
Kafka 单分区顺序消费的极限与突围:从原理到实战
分布式·kafka