2025-03-270

背景

为什么使用消息队列

消息队列

问题1:为什么使用消息队列

  • 解耦
  • 异步
  • 削峰

问题2:使用了消息队列会有什么缺点?

  • 系统可用性降低、系统复杂性增加;
    • 系统可用性降低:是指你的系统加一个消息中间件,那消息队列挂了,你的系统将会受到严重的影响,甚至导致整个系统无法正常的提供服务;
    • 系统的复杂性增加:是指引入了消息中间件,必须考虑数据的一致性问题、消息的重复消费,消息的可靠传输(传输的过程中不丢失)等一系列问题,
  • 所以要结合自己的业务场景去引入相关的技术,脱离业务的架构都是耍牛氓。

ISR (In-Sync Replicas)保持同步的副本集合

  • ISR 是和 Leader 保持同步的副本集合
  • Producer 设置 acks=all 时,通常需要 ISR 中副本确认后才算写入成功。
  • Leader 故障时,会从 ISR 中选举新的 Leader,从而减少数据丢失风险

Kafka 如何防止丢数据

1、生产者丢数据

在kafka生产中,基本都有一个leader和多个follwer。follwer会去同步leader的信息。 因此,为了避免生产者丢数据,做如下两点配置:

  • 第一个配置要在producer端设置acks=all。这个配置保证了,follwer同步完成后,才认为消息发送成功。
  • enable.idempotence=true 开启幂等,避免 Producer 重试导致重复写
  • retries > 0

2、消息队列丢数据

  • 通过副本和 ISR 保证数据高可用;

  • 针对消息队列丢数据的情况,无外乎就是,数据还没同步,leader就挂了,这时zookpeer会将其他的follwer切换为leader,那数据就丢失了。

  • 针对这种情况,应该做两个配置。

    • replication.factor参数,这个值必须大于1,即要求每个partition必须有至少2个副本
    • min.insync.replicas参数,这个值必须大于1,这个是要求一个leader感知到有至少一个follower还跟自己保持联系
  • 这两个配置加上上面生产者的配置联合起来用,基本可确保kafka不丢数据。

3、消费者丢数据

这种情况一般是自动提交了offset,然后你处理程序过程中挂了。kafka以为你处理好了。

  • offset:指的是kafka的topic中的每个消费组消费的下标。

    • 简单的来说就是一条消息对应一个offset下标,每次消费数据的时候如果提交offset,那么下次消费就会从提交的offset加一那里开始消费。

    • 比如一个topic中有100条数据,我消费了50条并且提交了,那么此时的kafka服务端记录提交的offset就是49(offset从0开始),那么下次消费的时候offset就从50开始消费。

  • 解决方案也很简单,改成手动提交offset即可。

消息队列挤压如何处理

常见影响包括:

  • 消息处理延迟持续升高
  • 实时业务变成准实时或离线业务
  • 消费者内存、CPU、线程池等资源耗尽
  • 消息过期、重复消费或丢失
  • 下游数据库、缓存、搜索引擎被集中流量冲击
  • 业务数据长时间处于不一致状态
  • 故障恢复后产生更严重的流量洪峰

如何做

阶段目标关键措施
紧急止损快速恢复,避免事态恶化定位瓶颈:监控生产/消费qps,单条耗时,分区进度
根源优化提升系统处理能力
长期预防杜绝问题反复发生

第一阶段:及时止损

1. 快速确认积压范围

首先确认积压是否真实存在,并判断影响范围。

重点关注以下指标:

指标说明
消息堆积数量当前未消费消息总量
最老消息年龄队列中最早未消费消息等待了多久
生产速率每秒新增消息数量
消费速率每秒成功消费消息数量
消费失败率消费异常、重试、超时比例
消费延迟消息从生产到消费完成的时间
消费者实例数当前正常运行的消费者数量
分区积压分布是否只有部分分区或队列积压
下游依赖状态数据库、缓存、接口等是否异常

需要优先回答以下问题:

  • 是所有队列积压,还是单个 Topic、Queue 或分区积压?
  • 是生产流量突然升高,还是消费能力下降?
  • 消费者是否存活?
  • 消息是否仍在持续增长?
  • 是否存在大量失败重试?
  • 下游依赖是否已经达到容量上限?
  • 消息是否允许延迟、丢弃、跳过或降级处理?

2. 立即限制新增流量

当消费速度明显低于生产速度时,第一目标不是盲目扩容,而是先阻止积压继续扩大。

可采取以下措施:

2.1 对生产端限流

根据业务优先级限制消息生产速度:

  • 对非核心业务启用限流
  • 降低批量任务执行频率
  • 暂停历史数据补偿任务
  • 暂停低优先级消息生产
  • 降低接口入口流量
  • 对同类重复事件进行合并
  • 对高频状态变更进行采样或去抖

原则:

消费速率无法恢复前,不要让生产速率无限增长。

2.2 区分消息优先级

将消息划分为不同优先级:

  • 核心交易消息
  • 用户实时通知
  • 数据同步消息
  • 日志与埋点消息
  • 离线统计消息
  • 可重建、可丢弃消息

优先保障核心消息,必要时暂停或丢弃低优先级消息。

如果消息中间件支持优先级队列,可直接使用优先级机制;如果不支持,可拆分为不同 Topic 或 Queue。

2.3 暂停非必要消费者逻辑

消费链路中可能包含多个非核心操作,例如:

  • 写业务数据库
  • 更新缓存
  • 调用外部接口
  • 发送通知
  • 记录审计日志
  • 生成统计数据

故障期间可临时关闭非必要步骤,只保留核心处理逻辑。

例如:

text
正常流程: 消费消息 → 写数据库 → 更新缓存 → 调用第三方接口 → 记录统计 → 发送通知 应急流程: 消费消息 → 写数据库 → 结束 后续通过补偿任务完成缓存、通知和统计处理。

3. 恢复消费者可用性

优先排查消费者是否存在以下问题:

  • 实例宕机
  • 消费线程全部阻塞
  • 线程池耗尽
  • 内存溢出
  • Full GC
  • CPU 使用率过高
  • 网络连接中断
  • 消费组异常
  • 分区未正确分配
  • 消费者频繁重平衡
  • 消费逻辑死循环
  • 单条异常消息持续失败
  • 下游依赖超时

紧急处理方式包括:

  • 重启异常消费者实例
  • 将异常实例摘除
  • 回滚最近发布版本
  • 恢复消费组配置
  • 调整线程池和连接池
  • 临时关闭高耗时业务逻辑
  • 隔离持续失败的异常消息
  • 修复数据库或下游依赖连接

注意:

重启只能解决临时性故障。如果根因是代码缺陷、容量不足或下游瓶颈,单纯重启会再次积压。


4. 谨慎扩容消费者

扩容是处理积压最直接的方法之一,但扩容前必须确认消费能力是否能随实例数量线性增长。

扩容前检查:

  • 消息分区数是否足够
  • 消费者数量是否已经超过分区数
  • 数据库连接池是否允许更多并发
  • 下游接口是否支持更高请求量
  • Redis、Elasticsearch 等依赖是否有余量
  • 消费过程是否存在全局锁
  • 是否存在单分区热点
  • 是否受单机磁盘、网络或 CPU 限制

例如,Kafka 一个分区在同一消费组内通常只能被一个消费者实例消费。

如果:

text
Topic 分区数 = 8 消费者实例数 = 8

继续扩容到 16 个实例,通常不会带来明显提升,因为多余实例无法获得分区。

正确做法可能是:

text
增加分区数 → 扩容消费者 → 验证分区数据是否均衡

但增加分区可能影响消息顺序,需要提前评估。


5. 开启批量消费和批量写入

如果单条消息处理开销较大,可临时切换为批量模式。

例如:

text
单条消费: 读取 1 条消息 → 写数据库 1 次 → 提交消费位点 1 次 批量消费: 读取 500 条消息 → 批量写数据库 → 批量提交消费位点

批量处理可减少:

  • 网络往返次数
  • 数据库事务次数
  • 消费位点提交次数
  • 序列化与反序列化开销
  • 线程上下文切换

需要注意:

  • 批次不能过大
  • 控制单批次执行时间
  • 批量失败时需要支持拆分重试
  • 防止单条异常消息导致整批失败
  • 做好幂等控制

6. 隔离异常消息

某些积压由“毒消息”引起,即特定消息持续处理失败,导致消费者不断重试。

常见异常消息包括:

  • 字段缺失
  • 格式错误
  • 数据无法反序列化
  • 业务状态非法
  • 数据量异常大
  • 关联数据不存在
  • 触发程序缺陷

处理方式:

  1. 设置最大重试次数
  2. 超过次数后进入死信队列
  3. 记录失败原因和原始消息
  4. 正常队列继续消费
  5. 后续人工或自动补偿

示例:

text
正常队列 → 消费失败 → 重试队列 → 达到最大重试次数 → 死信队列 → 人工修复或补偿任务处理

避免以下模式:

text
失败 → 立即重试 → 再次失败 → 无限重试

无限重试会放大故障,并阻塞正常消息。


7. 建立临时快速消费通道

当积压量非常大,而原消费链路短时间内无法消化时,可建立临时消费集群。

临时集群可采用简化逻辑:

text
消息队列 → 临时消费者集群 → 写入临时存储 → 后台慢速处理

例如先将消息快速落入:

  • 临时数据库
  • 对象存储
  • 文件系统
  • 新的中转队列
  • 大数据存储系统

这样可以快速释放原始消息队列容量,再通过离线任务逐步处理。

适用场景:

  • 队列磁盘容量即将耗尽
  • 消息即将过期
  • 原消费逻辑严重依赖慢速系统
  • 大量历史消息需要迁移
  • 需要优先恢复实时消息处理

8. 防止恢复后的流量洪峰

积压恢复过程中,消费者可能向下游集中发送大量请求。

需要设置恢复速率上限:

text
恢复消费速率 ≤ 下游系统可承受速率

可以采用:

  • 消费端限速
  • 分批恢复消费者
  • 动态调整消费并发
  • 按时间段回放历史消息
  • 实时消息与历史消息分开处理
  • 对下游接口设置并发上限
  • 使用漏桶或令牌桶控制速率

推荐采用“双通道模式”:

text
实时通道:优先处理新消息 历史通道:限速处理积压消息

这样可以避免新消息继续等待在旧消息之后。


9. 评估积压清理时间

可使用以下公式估算:

text
净消费速率 = 消费速率 - 生产速率 预计清理时间 = 当前积压消息数 ÷ 净消费速率

示例:

text
当前积压:3,600,000 条 生产速率:1,000 条/秒 消费速率:2,000 条/秒 净消费速率: 2,000 - 1,000 = 1,000 条/秒 预计清理时间: 3,600,000 ÷ 1,000 = 3,600 秒 约 1 小时

如果消费速率小于或等于生产速率:

text
消费速率 ≤ 生产速率

则积压永远无法清理完,必须限流、优化或扩容。


10. 及时止损检查清单

markdown
- [ ] 确认积压 Topic、Queue、消费组和分区范围 - [ ] 确认当前生产速率和消费速率 - [ ] 确认最老消息等待时间 - [ ] 检查消费者实例是否正常 - [ ] 检查最近是否发生发布或配置变更 - [ ] 检查数据库、缓存和外部接口状态 - [ ] 暂停非核心生产任务 - [ ] 对生产端进行限流 - [ ] 回滚异常版本 - [ ] 隔离毒消息 - [ ] 开启死信队列 - [ ] 临时扩容消费者 - [ ] 验证分区数是否支持扩容 - [ ] 开启批量消费和批量写入 - [ ] 对历史积压消息限速回放 - [ ] 防止下游系统被恢复流量打垮 - [ ] 持续计算预计积压清理时间

第二阶段:根源优化

1. 判断是生产突增还是消费下降

积压问题通常可归为两类。

类型一:生产速率异常升高

可能原因:

  • 营销活动流量突增
  • 上游批量补发数据
  • 定时任务集中启动
  • 重复发送消息
  • 生产端死循环
  • 重试机制配置错误
  • 数据同步任务全量执行
  • 业务拆分后消息数量倍增

类型二:消费能力下降

可能原因:

  • 消费者实例减少
  • 消费逻辑变慢
  • 数据库性能下降
  • 外部接口超时
  • 锁竞争严重
  • 线程池或连接池耗尽
  • JVM GC 异常
  • 分区分配不均
  • 消费者频繁重平衡
  • 单条消息处理时间变长
  • 消息体积增大

需要结合历史基线进行判断:

text
积压变化 + 生产速率变化 + 消费速率变化 + 单条处理耗时变化 + 错误率变化

2. 分解消费链路耗时

不要只统计“消费接口总耗时”,应将消费过程拆解。

例如:

text
总耗时 = 拉取消息耗时 + 反序列化耗时 + 参数校验耗时 + 数据库查询耗时 + 数据库写入耗时 + 缓存访问耗时 + 外部接口调用耗时 + 消费结果提交耗时

建议记录以下指标:

环节建议指标
消息拉取拉取条数、拉取耗时
反序列化失败率、平均耗时
业务处理平均值、P95、P99
数据库查询QPS、慢查询、连接池等待时间
数据库写入单条耗时、批量耗时、锁等待
外部接口成功率、超时率、P99
位点提交成功率、提交耗时
重试重试次数、重试消息比例

通过链路追踪定位真正瓶颈,避免无效扩容。


3. 优化数据库访问

数据库通常是消息消费链路中最常见的瓶颈。

3.1 消除逐条查询

低效模式:

text
消费 1000 条消息 → 查询数据库 1000 次 → 更新数据库 1000 次

优化模式:

text
消费 1000 条消息 → 批量查询 → 内存中完成匹配 → 批量新增或更新

3.2 避免重复查询

对于稳定性较高的数据,可使用:

  • 本地缓存
  • Redis 缓存
  • 批次级缓存
  • 预加载字典
  • 请求级上下文缓存

3.3 优化索引

重点检查:

  • 查询条件是否命中索引
  • 更新条件是否命中索引
  • 是否存在全表扫描
  • 是否存在隐式类型转换
  • 是否存在索引失效
  • 是否存在大量回表
  • 是否存在热点行更新

3.4 缩短事务范围

避免在长事务中执行:

  • 外部接口调用
  • 大量循环处理
  • 文件操作
  • 网络请求
  • 消息发送

推荐:

text
先完成数据准备 → 开启事务 → 执行必要数据库操作 → 快速提交事务 → 执行非事务操作

3.5 合理使用批量写入

批量写入需要控制批次大小。

例如:

text
每批 200~1000 条

实际大小应根据以下因素压测确定:

  • 单条消息大小
  • SQL 长度
  • 数据库参数限制
  • 事务日志压力
  • 锁冲突情况
  • 单批执行时间

4. 优化外部接口调用

消费链路同步调用外部接口时,很容易因外部系统抖动产生积压。

优化方式:

4.1 设置严格超时

必须设置:

  • 连接超时
  • 读取超时
  • 总调用超时
  • 单消息总处理超时

禁止无限等待。

4.2 使用熔断与降级

当外部依赖异常时:

text
调用失败率达到阈值 → 熔断 → 停止持续调用 → 消息进入补偿队列 → 外部系统恢复后重新处理

4.3 将慢调用异步化

原流程:

text
消费消息 → 调用外部接口 → 等待响应 → 提交消费结果

优化后:

text
消费消息 → 写入本地任务表或二级队列 → 提交消费结果 异步任务 → 调用外部接口 → 失败重试

4.4 控制重试策略

推荐使用指数退避:

text
第 1 次重试:1 秒后 第 2 次重试:5 秒后 第 3 次重试:30 秒后 第 4 次重试:5 分钟后

同时设置:

  • 最大重试次数
  • 最大重试时间
  • 随机抖动
  • 死信队列
  • 可观测的失败原因

5. 优化消费并发模型

5.1 避免串行处理

低效模式:

text
拉取一条 → 处理一条 → 提交一条

可优化为:

text
批量拉取 → 线程池并发处理 → 批量提交

5.2 区分 CPU 密集和 I/O 密集任务

CPU 密集型任务包括:

  • 压缩解压
  • 加解密
  • 大量计算
  • 复杂数据转换

线程数不宜远高于 CPU 核数。

I/O 密集型任务包括:

  • 数据库访问
  • 缓存访问
  • 网络请求
  • 文件读写

可适当增加并发,但需要受下游容量约束。

5.3 拆分不同耗时任务

不要让轻量消息与重型消息共用同一个消费通道。

例如拆分为:

text
fast-topic:轻量、实时任务 slow-topic:耗时、批量任务 retry-topic:失败重试任务 dead-letter-topic:人工处理任务

5.4 避免全局锁

检查消费代码是否存在:

  • synchronized 全局锁
  • 分布式锁范围过大
  • 单例资源串行访问
  • 热点 Key 锁竞争
  • 数据库热点行锁

如果不同消息之间没有严格顺序依赖,应尽可能降低锁粒度。


6. 解决分区热点问题

即使队列整体消费能力足够,单个分区过热也会导致局部积压。

常见原因:

  • 按用户 ID 分区,但少数用户流量极大
  • 固定业务 Key 导致大量消息进入同一分区
  • 分区策略不均匀
  • 空 Key 消息集中到默认分区
  • 消息顺序要求过强

优化方式:

  • 优化分区 Key
  • 对热点 Key 增加随机后缀
  • 将热点租户拆分为独立 Topic
  • 对大客户使用专属队列
  • 只在必要范围内保证顺序
  • 将全局顺序降级为用户级或订单级顺序
  • 增加分区并重新分布数据

例如:

text
原分区 Key: tenantId 优化后: tenantId + hash(orderId) % 8

需要注意:增加随机维度可能破坏同一 Key 的严格顺序。


7. 优化消息体积

消息体积变大会影响:

  • 网络吞吐
  • Broker 存储
  • 消息拉取速度
  • 序列化速度
  • 消费者内存
  • GC 压力

建议:

  • 消息只传递必要字段
  • 避免在消息中放完整大对象
  • 大文件仅传存储地址
  • 使用紧凑的序列化协议
  • 删除重复字段
  • 避免多层嵌套数据
  • 对大消息单独建立队列
  • 设置消息大小上限

推荐模式:

json
{ "eventId": "evt_123", "bizId": "order_456", "eventType": "ORDER_PAID", "occurredAt": "2026-07-15T10:00:00Z", "dataVersion": 3 }

消费者根据 bizId 按需读取完整业务数据,而不是在消息中传递完整对象。


8. 处理重复消息和无效消息

积压中可能存在大量重复消息。

常见原因:

  • 生产端重复发送
  • 消费确认超时
  • 网络抖动
  • 生产者重试
  • 消费者处理成功但位点提交失败
  • 上游补偿任务重复执行

建议通过以下字段实现幂等:

text
eventId requestId businessId + eventType + version

典型幂等方式:

  • 数据库唯一索引
  • Redis SET NX
  • 幂等记录表
  • 业务状态机校验
  • 版本号校验
  • 去重窗口

注意:

消息队列通常只能保证至少一次投递,业务系统必须具备幂等处理能力。


9. 重新评估消息顺序要求

严格顺序会显著限制并发能力。

需要明确:

  • 是否真的需要全局顺序?
  • 是否只需要同一用户内有序?
  • 是否只需要同一订单内有序?
  • 是否只需保证状态版本单调递增?
  • 是否可以通过版本号丢弃旧消息?

推荐优先使用局部顺序:

text
全局顺序 ↓ 租户级顺序 ↓ 用户级顺序 ↓ 订单级顺序

顺序范围越小,可用并发度越高。


10. 根因分析模板

markdown
## 消息积压根因分析 ### 1. 事件概述 - 发生时间: - 恢复时间: - 影响时长: - 影响业务: - 影响消息量: - 最大消息延迟: - 是否造成数据丢失: - 是否造成重复处理: ### 2. 现象 - 生产速率: - 消费速率: - 峰值积压量: - 消费失败率: - 最老消息年龄: - 异常消费者数量: - 下游异常情况: ### 3. 直接原因 例如: - 数据库连接池耗尽 - 消费代码发布后单条处理耗时从 20ms 上升到 500ms - 上游全量补偿任务产生突发流量 - 单条异常消息无限重试 - 消费组频繁重平衡 ### 4. 根本原因 例如: - 缺少容量评估 - 缺少消费延迟告警 - 重试机制设计不合理 - 缺少死信队列 - 发布前未进行压测 - 上下游缺少流量保护 - 消费逻辑与外部依赖强耦合 ### 5. 应急处理 - 限流措施: - 扩容措施: - 回滚措施: - 降级措施: - 消息隔离措施: - 数据补偿措施: ### 6. 改进项 | 改进项 | 负责人 | 截止时间 | 状态 | |---|---|---|---| | 增加消费延迟告警 | | | | | 增加死信队列 | | | | | 优化批量写入 | | | | | 完善容量压测 | | | |

第三阶段:长期预防

1. 建立完整监控体系

消息队列不能只监控“消息数量”,应建立覆盖生产、存储、消费和业务结果的指标体系。

1.1 生产端指标

  • 消息生产速率
  • 生产成功率
  • 生产失败率
  • 生产重试次数
  • 单条消息大小
  • 批量发送大小
  • 生产延迟

1.2 Broker 指标

  • 队列积压量
  • 磁盘使用率
  • 网络吞吐
  • Broker CPU 和内存
  • 消息写入延迟
  • 消息读取延迟
  • 分区副本状态
  • 副本同步延迟
  • 消息保留时间
  • 分区数据分布

1.3 消费端指标

  • 消费速率
  • 消费成功率
  • 消费失败率
  • 单条处理耗时
  • P95、P99 处理耗时
  • 消费者实例数
  • 活跃线程数
  • 线程池队列长度
  • 数据库连接池使用率
  • 重试消息数量
  • 死信消息数量

1.4 业务指标

  • 消息端到端延迟
  • 业务完成率
  • 数据一致性延迟
  • 核心业务成功率
  • 待补偿任务数量
  • 超时业务数量

2. 以消息年龄作为核心告警指标

仅根据积压条数告警可能不准确。

例如:

text
积压 100 万条消息

如果消费速度很快,可能几分钟即可处理完。

而:

text
积压 1000 条消息

如果最老消息已经等待 2 小时,则可能已经严重影响业务。

因此建议重点监控:

text
最老未消费消息年龄

告警示例:

告警级别条件
提示最老消息年龄 > 1 分钟
警告最老消息年龄 > 5 分钟
严重最老消息年龄 > 15 分钟
紧急最老消息年龄 > 30 分钟

实际阈值应根据业务 SLA 制定。


3. 设置多级告警

避免积压达到严重程度后才告警。

推荐设置:

text
一级告警:消费速率连续下降 二级告警:生产速率持续高于消费速率 三级告警:积压量达到容量阈值 四级告警:消息年龄超过业务 SLA 五级告警:消息即将过期或磁盘即将耗尽

告警内容应包含:

  • 队列名称
  • 消费组名称
  • 当前积压量
  • 最老消息年龄
  • 当前生产速率
  • 当前消费速率
  • 预计清理时间
  • 失败率
  • 消费者实例数量
  • 最近发布记录
  • 推荐应急操作

4. 建立容量模型

系统设计时应评估:

text
峰值生产速率 平均生产速率 单实例消费速率 消费者最大扩容数 下游最大承载能力 允许的最大消息延迟 最大可接受积压量 消息保留时长 故障恢复时间

4.1 消费者容量计算

text
所需消费者数量 = 峰值生产速率 ÷ 单实例稳定消费速率 × 安全系数

示例:

text
峰值生产速率:20,000 条/秒 单实例稳定消费速率:2,000 条/秒 安全系数:1.5 所需实例数: 20,000 ÷ 2,000 × 1.5 = 15

建议至少准备 15 个实例的容量。

4.2 积压容忍能力计算

text
最大积压量 = 允许的最大恢复时间 × 净消费速率

例如:

text
要求 30 分钟内清理积压 净消费速率为 5,000 条/秒 最大积压量: 30 × 60 × 5,000 = 9,000,000 条

5. 保留容量余量

消费者不应长期运行在接近满负载状态。

建议正常运行时:

text
平均资源使用率控制在 50%~70%

保留余量用于应对:

  • 短时流量突增
  • 单实例故障
  • 发布重启
  • Broker 重平衡
  • 下游性能抖动
  • 历史消息补偿
  • 节假日和营销活动

如果消费者长期需要 90% 以上资源才能跟上生产速度,说明系统没有足够安全余量。


6. 建立自动扩缩容机制

可根据以下指标自动扩容:

  • 消息积压量
  • 最老消息年龄
  • 消费速率与生产速率差值
  • 单实例 CPU
  • 消费处理耗时
  • 消费线程池使用率

推荐扩容条件:

text
最老消息年龄持续升高 且 消费速率低于生产速率 且 下游系统仍有剩余容量

缩容时应避免频繁抖动,可设置:

  • 冷却时间
  • 最小实例数
  • 最大实例数
  • 连续多个周期满足条件后再缩容
  • 分批缩容

注意:

自动扩容必须与分区数量和下游容量联动,否则扩容可能无效,甚至放大故障。


7. 建立流量保护机制

生产端应具备:

  • 限流
  • 熔断
  • 降级
  • 采样
  • 合并
  • 去重
  • 延迟执行
  • 优先级控制

消费端应具备:

  • 最大并发限制
  • 下游请求速率限制
  • 单消息处理超时
  • 重试次数限制
  • 死信队列
  • 毒消息隔离
  • 熔断与降级

Broker 侧应具备:

  • 存储容量告警
  • 消息大小限制
  • 消息保留策略
  • 队列长度限制
  • 租户配额
  • 生产与消费流量配额

8. 完善重试体系

建议将重试与主消费队列分离。

推荐架构:

text
主队列 → 消费失败 → 1 分钟重试队列 → 5 分钟重试队列 → 30 分钟重试队列 → 死信队列

每次失败应记录:

json
{ "eventId": "evt_123", "retryCount": 3, "firstFailedAt": "2026-07-15T10:00:00Z", "lastFailedAt": "2026-07-15T10:30:00Z", "failureCode": "DATABASE_TIMEOUT", "failureMessage": "database connection timeout" }

重试必须满足:

  • 有最大次数
  • 有退避时间
  • 有随机抖动
  • 有错误分类
  • 有死信队列
  • 有人工处理入口
  • 有补偿结果记录

9. 建立消息生命周期管理

消息应明确以下生命周期状态:

text
已生产 → 已存储 → 已拉取 → 处理中 → 处理成功 → 处理失败 → 等待重试 → 死信 → 已补偿

同时明确:

  • 消息保留多久
  • 重试消息保留多久
  • 死信消息保留多久
  • 是否支持消息回放
  • 回放是否会重复执行
  • 如何校验回放结果
  • 如何清理历史消息
  • 如何审计消息处理状态

10. 定期进行容量压测和故障演练

建议定期验证:

  • 峰值生产能力
  • 峰值消费能力
  • 单实例稳定消费速率
  • 最大扩容速度
  • 数据库最大写入能力
  • 外部接口最大调用能力
  • 队列最大存储能力
  • 积压恢复时间
  • 消费者宕机恢复能力

可演练以下故障:

  • 消费者全部停止 10 分钟
  • 数据库响应时间增加 10 倍
  • 外部接口持续超时
  • Broker 单节点故障
  • 单条异常消息无限失败
  • 消息生产流量突然增加 5 倍
  • 单个分区产生热点
  • 消费者频繁重平衡

演练目标不是“系统完全不出问题”,而是验证:

  • 告警是否及时
  • 自动扩容是否生效
  • 限流是否生效
  • 降级是否可用
  • 死信队列是否正常
  • 恢复过程是否会冲击下游
  • 运维手册是否可执行

11. 发布前进行性能回归

消费代码变更可能导致单条处理耗时明显增加。

发布前应对比:

text
变更前平均耗时 变更后平均耗时 变更前 P99 变更后 P99 变更前吞吐量 变更后吞吐量 数据库调用次数变化 外部接口调用次数变化 消息体积变化 内存占用变化

可设置发布门禁:

text
单条消费耗时增长超过 20% 或 吞吐量下降超过 15% 或 错误率超过阈值 → 阻止发布

12. 完善消息队列应急预案

应急预案至少应包含:

markdown
# 消息队列积压应急预案 ## 基本信息 - 队列名称: - 消费组名称: - 业务负责人: - 技术负责人: - 消息中间件负责人: - 下游系统负责人: ## 核心指标 - 正常生产速率: - 峰值生产速率: - 单实例消费速率: - 正常消费者数量: - 最大消费者数量: - 消息延迟 SLA: - 消息保留时长: ## 应急操作 1. 检查消费者存活状态 2. 检查最近发布和配置变更 3. 检查数据库、缓存和外部接口 4. 判断是否需要生产端限流 5. 判断是否需要回滚 6. 判断是否需要消费者扩容 7. 判断是否需要隔离异常消息 8. 判断是否需要暂停非核心逻辑 9. 评估积压清理时间 10. 持续观察下游系统负载 ## 禁止操作 - 未确认下游容量前无限扩容消费者 - 未进行幂等校验前重复回放消息 - 直接删除核心业务消息 - 对异常消息进行无限重试 - 在没有备份的情况下重置消费位点 - 同时进行多项高风险变更

四、整体处理决策流程

text
发现消息积压 │ ▼ 确认生产速率、消费速率、消息年龄 │ ▼ 消费端是否故障? ┌────┴────┐ │ │ 是 否 │ │ 恢复消费者 判断生产是否突增 回滚版本 │ 修复依赖 ┌───┴───┐ │ │ │ │ 是 否 │ │ │ │ 限流/暂停 分析消费瓶颈 │ 非核心流量 │ └──────────┬───────────┘ ▼ 是否存在异常消息? ┌────┴────┐ │ │ 是 否 │ │ 隔离到死信队列 继续分析 │ │ └────┬────┘ ▼ 下游是否有扩容余量? ┌────┴────┐ │ │ 是 否 │ │ 扩容消费者 降级消费逻辑 增加并发 限速处理 │ │ └────┬────┘ ▼ 批量消费、批量写入 │ ▼ 实时消息与历史消息分流 │ ▼ 持续计算预计清理时间 │ ▼ 积压恢复至正常水平 │ ▼ 根因分析与长期整改

五、核心原则总结

1. 及时止损阶段

核心目标:

阻止积压继续扩大,并保障核心业务。

关键动作:

  • 限制新增消息
  • 恢复消费者
  • 回滚异常版本
  • 隔离毒消息
  • 暂停非核心逻辑
  • 谨慎扩容
  • 批量处理
  • 对历史消息限速回放
  • 避免下游被流量洪峰冲垮

2. 根源优化阶段

核心目标:

找到消费吞吐下降或生产流量异常的真实原因。

关键动作:

  • 对比生产速率和消费速率
  • 拆解消费链路耗时
  • 优化数据库访问
  • 优化外部接口调用
  • 改造重试机制
  • 处理分区热点
  • 降低锁竞争
  • 优化消息体积
  • 完善幂等与去重
  • 缩小消息顺序范围

3. 长期预防阶段

核心目标:

让积压能够提前发现、自动控制并快速恢复。

关键动作:

  • 监控消息年龄和积压趋势
  • 设置多级告警
  • 建立容量模型
  • 保留资源余量
  • 配置自动扩缩容
  • 完善限流、熔断和降级
  • 建立分级重试与死信队列
  • 定期进行压测和故障演练
  • 设置发布性能门禁
  • 完善应急预案和责任人机制

六、一句话总结

消息队列积压处理的正确顺序是:先限流止血、恢复消费并隔离异常消息,再定位生产与消费链路瓶颈,最后通过容量治理、监控告警、分级重试、自动扩缩容和故障演练建立长期防线。

做什么

kafka为什么快

  • 磁盘顺序写,也就是追加写
  • 零拷贝
    • 少了两次cpu拷贝
    • 使用sendfile实现
  • Page Cache(内核缓存)
    • kafka吸入page Cache
    • 然后才异步刷盘
  • 批量发送
    • 一次发送多条,减少tcp次数
  • partition 并行

默认发送多少条

  • 按批次大小 batch.size 和等待时间 linger.ms 控制。
    • 默认 batch.size 为 16KB,
    • linger.ms 为 0ms

Kafka 如何保证消息顺序?

  • 只能保证同一个分区Partition顺序写
    • 所以必须对key进行hash计算或分区策略选择分区
    • 想要保证顺序写,必须将相同key的消息,写入同一个分区

Consumer Group 规则

  • 关键规则:
    • 同一个 Consumer Group 中,一个 partition 同一时刻只能被一个 consumer 消费
  • 面试总结:
    • Consumer Group 用来实现同一份数据的并行消费。同组内多个 Consumer 共同消费 topic 的不同 partition,但一个 partition 在同一时刻只能分配给组内一个 Consumer

Kafka 如何避免重复消费?

  • 只能At Least Once
  • 所以业务处理必须要幂等
    • 数据库唯一索引
    • 通过setnx 上锁
    • 状态机判断

Kafka 为什么不删除消息?

  • 模式是移动offset进行消费
  • kafka会设置数据到期时间

Kafka 为什么是 Pull 模型?

  • 让 Consumer 自己控制拉取速度和批量大小,
  • 可以更好地适配不同 Consumer 的处理能力,
  • 避免 Broker 过快推送导致 Consumer 被压垮。

Rebalance 是什么

  • 是 Consumer Group 内 partition 重新分配的过程,
  • 通常发生在 Consumer 加入、退出、宕机或 Partition 数变化时。
  • Rebalance 会带来短暂停顿,
  • 因此需要合理设置心跳、session timeout 和 Consumer 数量

本文作者:曹子昂

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!