资讯动态

消息积压全链路排查:从Lag定性到应急止血与架构根治

发布时间:2026/10/27 3:05:39 来源:尧图企业网站定制
消息积压这件事做过消息队列运维和业务开发的人基本都躲不开。半夜被 Lag 曲线拉起来的告警叫醒打开监控一看消费位点落后生产位点几百万条业务方在群里追问订单为什么不动了——这个场景我经历过不止一次。消息积压表面上看是队列里堆了太多没被消费的消息本质上却是整条链路处理能力与输入流量之间的失衡是系统在用最直白的方式告诉你某个环节的吞吐上限被顶穿了。这篇内容我想把这件事从头到尾拆一遍包括积压发生的那一刻该怎么止血、怎么判断根因、怎么从架构上把积压的土壤消掉以及我在实际处理中踩过的那些坑。适合正在做消息中间件运维、写消费者业务代码、或者负责系统稳定性的人看也适合刚接触消息队列不久、想搞清楚 Lag 到底意味着什么的朋友。文中涉及的工具以常见的几款开源消息中间件为主思路是通用的换成你手上的那套也一样成立。1. 先把积压这件事定性清楚别急着动手1.1 积压不是故障是系统发出的一级信号我见过太多团队一看到积压就条件反射地去扩消费者实例扩完发现 Lag 一点没降然后开始怀疑中间件有问题。这种反应的问题在于跳过了定性这一步。消息积压本身不是一种故障它是一条链路在告诉你输入速率大于处理速率而这个失衡可能来自三个完全不同的方向生产端突然放量、消费端能力退化、或者容量本来就不够。这三种情况的处置手段差别极大用错药不但没用还可能把问题放大。我的习惯是先花五分钟做一次快速定性问自己三个问题这条队列最近一周的日均生产速率是多少现在的生产速率是多少消费速率是掉下去了还是一直没变Lag 是突然垂直拉起还是缓慢爬升了一个多星期。这三问基本能把方向锁死。突然垂直拉起且生产速率飙升大概率是生产端的问题比如一次批量任务、一场促销活动、或者上游某个服务在疯狂重试。Lag 缓慢爬升而生产速率平稳说明消费能力在退化可能是下游数据库变慢、也可能是消息体悄悄变大了。生产速率和消费速率都在涨但 Lag 一直在那是容量设计的问题扩容是唯一解。定性这一步还有一个容易被忽略的价值判断这条积压消息到底能不能丢、能不能延后。有些队列里跑的是日志、埋点、推荐特征回填这类消息延迟几分钟甚至几小时对业务无感处理节奏可以从容一些。有些队列里跑的是支付回调、库存扣减、订单状态流转积压十分钟就可能引发资损或客诉那就必须按应急流程走。我见过一个团队把两类消息混在同一个 Topic 里结果一次日志侧积压把订单消费也拖住了这种设计本身就是隐患。判断积压严重程度看 Lag 的绝对值意义不大要看预计清空时间。一个 500 万条的 Lag如果生产速率是 0、消费速率是 5 万条每秒一百秒就清完了另一个 50 万条的 Lag生产速率 6 万、消费速率 5 万那就是永远清不完只会越堆越多。1.2 三类积压的定性判断与对应处置方向把积压分成三类是我这些年用得最顺手的一套分类方法几乎每次都能在十分钟内落到某一类上。第一类是突发流量型。特征是生产速率在短时间内出现数倍甚至数十倍的尖峰消费速率保持平稳Lag 呈近乎垂直的上升曲线。典型触发场景包括上游做了一次全量数据刷新、某个定时任务集中触发、上游服务在故障恢复后把积压的请求一次性重放、或者电商大促的开场瞬间。这类积压的处置核心是扛过去因为尖峰通常会自己回落重点是保证在尖峰期间不丢消息、不把下游打挂。第二类是消费能力退化型。特征是生产速率基本稳定消费速率明显下降Lag 稳定爬升。这类最有迷惑性因为你盯着生产端看半天看不出问题。真实原因往往藏在下游数据库连接池被别的业务占满了、某个第三方接口的响应时间从 50 毫秒涨到了 800 毫秒、消费者的线程池里有线程卡在了一次没有设置超时的网络调用上、或者某台消费节点的 GC 时间突然变长。这类积压的处置核心是找到那个变慢的点扩容往往治标不治本因为你只是把更多实例挂到了同一个瓶颈上。第三类是容量设计不足型。特征是生产速率和消费速率都在正常范围内但生产速率长期稳定地高于消费速率Lag 在几天甚至几周内持续爬升中间可能有短暂回落但趋势一直是向上的。这类积压是最温柔的因为它给你留了充足的响应时间但也是最容易被忽视的很多团队是靠某天 Lag 撞到了磁盘水位线才发现。处置核心是扩容 优化单条处理成本两个方向一起使劲。1.3 排查的第一现场四个必须同时看的指标监控面板上只放一条 Lag 曲线是不够的那只告诉你结果不告诉你原因。我要求自己负责的每个消费链路至少同时盯四个指标Lag 绝对值、Lag 的一阶导数、生产速率、消费速率。这四个指标放在一张图上形态非常直观。生产速率和消费速率的交叉点就是积压开始的时刻这个时刻对应的生产速率值往往就直接告诉你尖峰有多大。我印象比较深的一次生产速率曲线在某个整点突然从 8000 涨到 9 万一查是某个统计任务在整点做了全量重算连带着把变更消息全推了一遍。如果只看 Lag 曲线你只知道从整点开始积压了但知道生产速率涨了十倍处置方向立刻就清晰了。除了速率还有两个指标必须看单条消息的平均处理耗时以及消费端的错误率。平均耗时可以直接算出理论消费上限比如单条耗时 5 毫秒单实例单线程理论上是 200 条每秒如果你开的是 20 个线程理论上限就是 4000 条每秒和实际消费速率一比就知道还有多少余量。错误率则能解释很多消费速率没掉但 Lag 在涨的情况——如果消费失败后进入重试重试本身也在消耗消费能力而且重试的消息如果反复失败还会形成毒消息占着线程不放。再补一个经常被漏掉的指标消费者组的 rebalance 次数。Kafka 这类基于消费者组协调的中间件一次 rebalance 期间整个组是停止消费的。如果消费节点频繁上下线、或者某次处理单批消息的时间超过了 max.poll.interval.ms就会触发反复 rebalance表现出来就是消费速率周期性掉到零再恢复Lag 呈阶梯状上涨。这个现象不看 rebalance 指标是查不出来的我遇到过一次排查了整整一个下午才定位到是批量处理太大导致心跳超时。2. 拆开看积压到底从哪一段漏出来的2.1 生产端放量五种常见但容易被误判的场景生产端的问题看起来最好查实际上误判率不低。因为生产速率上涨这个现象背后可能是五种完全不同的原因。最常见的是批量任务集中触发。定时任务、离线补偿、数据迁移这类作业往往一次性把大量消息塞进队列量级可能是平时的几十倍。这类流量的特点是持续时间短、可预测性强如果你提前知道任务什么时候跑完全可以把它安排到低峰期或者干脆限制它的发送速率。第二种是上游故障恢复后的消息重放。上游服务挂了一段时间恢复后它会把积压的请求全部重发这时候你在消费端看到的就是一次尖峰。这种情况的危险在于重放的消息里可能有大量重复如果消费端没有做幂等下游会被打两遍。我处理过的一次就是上游重试逻辑没有做退避故障期间积累了十几万次重试请求恢复瞬间全部打了出来。第三种是业务流量真实上涨。促销、投放、拉新活动带来的真实增量这类没什么好说的属于业务增长带来的正常压力考验的是容量规划做得够不够。第四种是生产者侧的重试风暴。生产端发送失败后立即重试、重试再失败再重试如果没有退避和上限会形成自我放大的流量。而且这类流量往往是重复消息消费端处理一遍就够了多出来的全是无效负载。第五种比较隐蔽是消息拆分逻辑变化。比如某个服务从一次操作发一条消息改成了一次操作按明细发 N 条消息代码上线时没人注意结果消息量直接翻了十几倍。这类问题靠监控只能看到生产速率上涨只有翻变更记录才能发现。2.2 消费端变慢六个真实原因和它们的表征差异消费端变慢是积压排查里最难的一块因为变慢的原因太多而表现出来的现象都是消费速率下降。我把常见原因整理成下面这张表配上各自的表征特征实际排查时可以对着症状找。原因典型表征快速验证方法下游存储变慢单条处理耗时 P99 上涨错误率可能不变看数据库慢查询日志、连接池等待时间消费线程池打满消费速率平稳但低于预期队列长度持续增长看线程池活跃数与队列积压量同步外部调用阻塞部分实例消费速率骤降其他实例正常看单实例维度的消费速率分布消息体变大网络流量上涨反序列化耗时占比高看平均消息大小的时间序列消费节点资源不足CPU 或内存使用率触顶GC 频繁看 GC 日志与容器资源限制消费者数少于分区数部分分区有消费者部分分区空闲对比分区数与活跃消费者数下游存储变慢是出现频率最高的一个。消费逻辑里通常有一次或多次数据库写入一旦数据库的响应时间从几毫秒涨到几十毫秒单条处理耗时直接翻十倍消费能力同比例下降。这类问题的迷惑性在于消费端自己的 CPU、内存、线程数看起来全都正常只有耗时指标在涨。线程池打满则是另一种形态。很多消费框架的设计是 poll 线程拉取消息后丢给业务线程池处理如果线程池满了poll 线程要么阻塞、要么继续拉取导致内存里堆消息。表现出来可能是消费速率看起来很平稳但队列里的消息就是清不掉因为实际处理能力被线程池大小锁死了。消费者数少于分区数这个坑我单独拎出来说因为它是新手最常踩的。Kafka 里一个分区在同一时刻只能被同组内的一个消费者消费如果你有 32 个分区但只起了 8 个消费者实例那就有 24 个分区处于无人消费的状态你扩到 32 个实例才能把分区吃满再往上扩实例完全没有意义。RocketMQ 的队列模型同理。所以扩容之前第一件事是确认分区数和消费者数的关系。2.3 中间件自身那些不该背锅却被冤枉的情况积压发生了很多人第一反应是中间件是不是有问题。绝大多数情况下不是但确实有几种情况值得确认一下。磁盘 IO 打满是一个。消息中间件要把消息持久化到磁盘如果磁盘的写入吞吐被别的进程抢走了或者磁盘本身接近写满写入就会变慢进而影响整体的吞吐。我遇到过一次是同一台机器上跑了一个日志采集进程把磁盘 IO 吃掉了大半导致消息写入延迟飙升表现出来像积压实际是写入侧的问题。网络带宽打满也会。消息体大的场景下比如图片特征、大 JSON单条几 MB几百 MB 每秒的带宽很快就到顶了这时候不管是生产还是消费都会变慢。还有一种情况是分区或队列的倾斜。消息按 key 分区如果某个 key 的消息量特别大那个分区就会成为热点其他分区早就消费完了就它一个还在拼命追。表现出来是整体 Lag 很高但按分区拆开看Lag 全集中在一两个分区上。这种情况扩容消费者没用因为热点分区只能被一个消费者处理正确的做法是重新设计 key 或者做二次拆分。判断是不是中间件自身的问题有个简单办法看同集群里其他 Topic 的表现。如果只有你这一个 Topic 积压其他都正常那基本可以排除中间件如果所有 Topic 同时出现异常才需要往中间件和基础设施的方向查。3. 应急止血积压发生后的一小时怎么打3.1 先定优先级保业务、保数据、还是保下游积压发生后的第一个动作不是敲命令是定优先级。这三个目标很多时候是冲突的想尽快清空积压就要加大消费并发但并发一上去下游数据库可能被打挂想保住下游就得限速消费但积压会继续堆着消息可能超过保留期被删除。我的判断顺序是这样的。先看消息有没有时效性如果消息超过一定时间就失去价值比如实时的位置上报、心跳数据那这类消息在积压时可以走降级通道直接丢弃或落冷存储优先保住链路通畅。如果消息是有状态流转意义的订单状态、库存变更那绝对不能丢宁可延迟也要保证最终被消费。如果消息背后还挂着一堆下游依赖那还要额外考虑下游的承受能力因为清积压的过程本质上是一次流量洪峰的重放。这个判断必须在动手之前做完而且要跟业务方对齐。我见过一次事故的复盘运维看到积压直接扩了十倍的消费者半小时把三百万条积压清完了结果下游的库存服务被打穿连锁引发了更大范围的问题。清积压的速度不是越快越好要和下游的承载能力匹配。3.2 消费端扩容的正确姿势与分区数计算扩容是最直接的止血手段但要做对几个细节。第一步是确认分区数。假设某条链路峰值生产速率是 12 万条每秒单分区实测消费能力是 3000 条每秒那么理论上至少需要 40 个分区。考虑到积压清空期间需要超出生产速率的额外能力我一般会留 1.5 倍冗余也就是 60 个分区。如果你现在的分区数是 32那就得先把分区扩到 60再起 60 个消费者实例。这个计算看着简单但很多团队跳过了单分区实测消费能力这一步直接拍脑袋按 CPU 核数算算出来的数字往往偏乐观。第二步是确认扩容后的实例真的能分担负载。Kafka 在扩分区之后新分区会被分配给消费者但已有序的消息不会重新分布老分区里堆积的消息还是由原来的消费者处理。所以扩完分区之后最好做一次消费者组的主动重平衡或者干脆滚动重启一遍消费者让分配更均匀。第三步是给扩容设一个上限。消费者实例不是越多越好因为每个实例都要和中间件维持连接、拉取数据、提交位点实例数上去之后协调开销也会上去。而且真正的瓶颈往往在下游你扩到 60 个实例下游数据库的连接池可能只支持 40 个并发写入多出来的 20 个实例只是在排队。这里还有个细节值得说如果下游确实撑不住但积压又必须清可以用消费加速 下游旁路的组合。具体做法是消费者不再逐条调用下游接口而是把消息批量写到一个临时的缓冲存储里再由另一个独立的、速率可控的进程从缓冲里慢慢消费。这样消费位点能快速推进业务侧的实际处理速率则由旁路进程控制等于给下游加了一个缓冲池。这个方案我在两次大促前的压测里用过效果不错代价是要额外维护一套旁路逻辑和对应的数据一致性校验。3.3 临时降级与旁路消费的几种组合拳除了扩容还有几种临时手段可以用各有适用边界。提高批量拉取量是最省事的一种。把单次拉取的消息条数从 500 提到 5000、把批量处理的批大小调大能显著降低单位消息的处理开销因为网络往返、位点提交、数据库事务这些固定成本被摊薄了。代价是端到端延迟会上升而且一旦处理失败要重试的是一整批重复消费的范围变大。适合对延迟不敏感的场景。关闭非关键消费逻辑也是一种。很多消费者里除了核心业务逻辑还夹着一堆统计上报、日志落库、特征回写之类的旁支操作。积压期间把这些旁支临时关掉只保留核心链路单条处理耗时可能直接砍掉一半。我一般会建议在代码里就把这些逻辑做成可配置的开关出事时一个配置项就能关掉不用临时改代码发版。降低位点提交频率要谨慎。把自动提交改成手动批量提交能减少提交开销但如果提交策略没设计好一次失败可能导致大量消息重复消费。我的原则是可以批量提交但必须业务侧幂等而且提交的间隔不能太长否则故障恢复时的重放量会失控。优先级队列分流适用于消息有明确轻重缓急的场景。给关键业务单独开一条队列和一组消费者把非关键的消息放到另一条队列两条链路互不影响。这个方案需要提前规划事后临时加比较麻烦但如果你的系统里有明显的核心/非核心消息混杂值得提前做。3.4 应急处理里三个不能碰的禁忌第一个禁忌是不清点就批量丢弃。有些团队为了快速止血会直接把位点跳到最新等于把中间所有消息全部丢弃。这个操作如果发生在订单、支付、库存这类链路上后果是数据永久不一致靠事后补偿几乎不可能补回来。如果确实要跳过必须先确认消息类型、评估影响范围、并且有完整的数据修复方案。第二个禁忌是无限扩消费者。前面说过消费者数超过分区数没意义就算分区足够下游也未必扛得住。更麻烦的是突然起大量消费者会引发集中重平衡整个组可能停摆几十秒到几分钟本来只是积压结果变成了短时不可用。第三个禁忌是在没有监控的情况下直接改消费逻辑。积压现场最忌讳的就是边改代码边发版因为你无法区分问题是积压本身的还是新代码引入的。我的做法是应急阶段只做参数调整和实例扩缩代码改动一律走正常发布流程而且要拆成小步、可回滚。4. 根治思路把积压的土壤从架构上消掉4.1 消费能力的弹性设计从固定实例到按 Lag 伸缩应急手段解决的是当下真正让积压不再成为事故的是让消费能力具备弹性。最基础的一层是消费者实例数可动态调整。这要求消费端是无状态的处理逻辑不依赖本地内存或本地文件位点提交交给框架统一管理。做到这一点之后就可以基于 Lag 做自动扩缩容Lag 超过阈值就扩低于阈值一段时间就缩。这套机制落地时有两个参数要调好一个是扩容的触发阈值不能设得太低否则频繁抖动另一个是缩容的冷却时间要足够长避免刚缩完又积压。但弹性伸缩有个硬约束分区数。消费者数不能超过分区数所以分区数必须按未来的峰值容量来规划而不是按当前需要。我一般的做法是按预计一年后的峰值流量 × 1.5 冗余 ÷ 单分区消费能力来定分区数宁可前期多一些分区每个分区的存储和协调开销其实不大也不要等到流量上来了才发现分区不够用——扩分区虽然支持但会打乱按 key 的顺序性需要业务侧配合。再往上一层是消费能力的隔离。核心业务和非核心业务不要共用同一组消费者和同一批资源。核心链路给独立的消费者组、独立的资源配额非核心链路即使积压到天上也不会影响核心。这个在资源紧张的时候价值特别大我见过太多次因为一条日志队列把整个集群的资源吃满导致订单消息消费不动的案例。4.2 批量与异步把单条消息的处理成本压到极致消费能力的公式很简单消费速率 并发数 ÷ 单条处理耗时。扩容是加大分子优化单条耗时是缩小分母后者往往收益更高而且成本更低。批量化是最有效的一招。单条处理 5 毫秒其中可能有 3 毫秒是网络往返和事务开销真正业务逻辑只有 2 毫秒。如果改成批量处理 500 条网络往返还是那一次事务开销被摊薄单条平均耗时可能降到 1 毫秒以内等于消费能力提升五倍。批量化要注意三个点批大小不能无限大否则单批处理时间太长会触发消费组的超时重平衡批内要能容忍部分失败不能一条失败整批回滚除非业务上确实要求强一致批处理的边界要和位点提交的边界对齐避免出现处理了一半但位点提交了的情况。异步化是另一招。消费逻辑里如果有多个独立的操作比如写数据库、发通知、更新缓存串行执行的总耗时是三者之和改成并行或者异步之后总耗时接近三者最大值。我做过一次改造把一个串行三步的消费逻辑改成并行单条耗时从 18 毫秒降到 6 毫秒同样的实例数消费能力翻了三倍。减少不必要的同步调用也值得做。消费逻辑里调第三方接口是很危险的因为第三方接口的响应时间你控制不了。如果非调不可至少要设置明确的超时、加上熔断降级并且把调用结果异步化——先把消息处理完、位点提交掉再通过另一条链路去调用失败就重试。这样第三方的抖动不会传导成消费积压。4.3 削峰填谷与背压让流量进来之前就变平滑前面说的都是提升消费能力另一条路是让生产端的流量变平滑。生产端限流是最直接的。在发送消息之前加一层令牌桶或者漏桶把瞬时速率压到一个可控的水平。这个做法听起来像是限制业务但在保护整体稳定性上非常有效。关键在于限流的策略要合理核心消息优先放行非核心消息在高峰期降速限流要有明确的降级行为是丢弃、是延迟发送、还是转本地队列都要提前定好。消息分级是另一个思路。同一个业务的消息按重要性分到不同的 Topic核心消息走独立的高优先级队列保证不被非核心流量挤占。这在电商场景里特别常见订单创建、支付回调这类消息和浏览埋点、推荐日志这类消息混在一起早晚会出问题。背压机制是很多人忽略的一环。当消费端处理不过来的时候应该能反向告诉生产端慢一点。消息队列本身其实提供了一定的背压信号比如队列长度、Lag但生产端通常不感知这些。要真正实现背压需要在业务层做消费端把当前的处理水位上报到一个共享的地方生产端在发送前检查水位超过阈值就降速或者直接拒绝。这套机制在流式计算里比较常见在普通的业务消息场景里落地得不多但对于流量波动大的系统值得考虑。4.4 消息分片与热点隔离解决倾斜带来的局部积压分区倾斜是积压问题里最容易被忽视又最难处理的一种。表现是整体 Lag 不算特别高但按分区拆开看某几个分区的 Lag 是其他分区的几十倍。原因通常出在 key 的选择上。Kafka 按 key 的哈希值分区如果 key 的分布不均匀比如以商户 ID 为 key 但某几个大商户的消息量占了总量的一半那几个商户对应的分区就会成为热点。RocketMQ 按队列轮询也一样热点队列会先堆起来。解决思路有三个。第一种是换 key 或者加盐把原本集中的 key 打散比如在 key 后面拼一个随机后缀让消息分散到多个分区。代价是同一个实体相关的消息不再有序如果业务依赖顺序就不能这么做。第二种是二级拆分。对热点 key 单独处理比如把大商户的消息再按订单号哈希拆成多个子分区消费端聚合处理。实现复杂度上升但能保住局部的顺序性。第三种是热点单独隔离。把已知的大客户、大商户单独分到一组专属的分区和消费者上让它们自己去消化自己的量不影响其他客户。这个方案在 SaaS 场景里很常见做起来也比较干净。分区倾斜有一个很实用的自查方法把某个 Topic 一周内每个分区的总消息量拉出来做个分布图。如果最大值和最小值差距超过 3 倍就说明 key 的设计有问题即使现在没出积压将来流量涨上来也一定会出。5. 监控、告警与演练让积压在你睡着之前被发现5.1 该盯哪些指标阈值怎么定才不误报监控指标的选择上我坚持三个层次的原则。第一层是结果指标也就是 Lag 本身。但绝对值的阈值很难定因为不同链路的量级差几十倍。我推荐用预计清空时间作为告警指标公式是 Lag ÷ (消费速率 - 生产速率)。当消费速率大于生产速率时这个值就是清空积压需要的时间当消费速率小于生产速率时这个值是负数或者无穷大说明积压永远清不完。用这个指标的好处在于它自带上下文不同量级的链路可以用同一套阈值比如预计清空时间超过 30 分钟就告警非常直观。第二层是过程指标包括生产速率、消费速率、单条处理耗时、消费者实例数、rebalance 次数、消费失败率。这些指标不直接告警但出问题时它们是定位的主要依据。我特别建议把单条处理耗时的分位数P50、P95、P99都画出来因为平均值会骗人——平均值 5 毫秒但 P99 是 800 毫秒说明有少量消息处理极慢这些慢消息会占着线程不放拖累整体吞吐。第三层是资源指标包括消费者的 CPU、内存、GC 时间、线程池活跃数、下游数据库的连接池等待时间。这些指标的价值在于提前预警比如 CPU 使用率持续在 70% 以上说明距离瓶颈不远了这时候就该考虑扩容或者优化而不是等积压了再动手。告警阈值我一般是这么定的先用一周的正常数据算出基线告警阈值设在基线的 2 到 3 倍同时加一个持续时间条件比如连续 5 分钟超过阈值才告警避免瞬时抖动引发的误报。上线之后再根据实际误报率做两轮调整一般能把误报率压到可以接受的水平。5.2 告警降噪怎么让真正重要的告警不被淹没积压告警最容易出现的问题不是漏报是太多人已经麻木了。我见过一个团队的告警群里每天几十条积压告警大部分是某个日志队列的正常波动结果真正的订单积压告警发出来没人看硬是拖了四十分钟才有人处理。降噪的核心思路是分级 分组 收敛。分级指按业务影响把告警分成 P0、P1、P2P0 走电话、P1 走即时消息、P2 只发邮件或者看板不同级别对应不同的响应要求。分组指按链路的负责人路由告警别把所有人的告警都发到一个群里。收敛指同一链路在短时间内产生的多条告警合并成一条并带上最新的数值避免刷屏。还有一个很有效的做法是给告警加上上下文信息。一个只写着订单队列 Lag 超过阈值的告警收到的人还得自己去查是哪条链路、量级多大、生产速率多少。如果告警里直接带上订单创建队列 Lag 42 万条生产速率 8 千条每秒消费速率 6 千条每秒预计永远清不完收到的人立刻就知道严重程度和大致方向响应速度会快很多。5.3 压测与故障演练在没有流量的日子里把问题暴露出来积压的处理能力是练出来的不是想出来的。我建议每个季度至少做一次针对性的压测和演练。压测的做法是在测试环境按峰值的 1.5 倍往队列里灌消息看消费端能不能跟上同时观察单条处理耗时、下游数据库的负载、线程池水位。这个过程能暴露很多平时看不到的问题比如批处理大小设置不合理、数据库连接池不够、某个下游接口在高压下响应时间暴涨。我做过一次压测发现消费速率在灌到 8 万条每秒的时候突然掉到 2 万查了半天是数据库的连接池只有 20 个连接线程全在等连接。故障演练则是另一种思路主动制造消费端不可用比如停掉一半消费者实例看 Lag 的上涨速度和告警的触发情况或者人为让下游变慢加延迟看消费端会不会有熔断降级再或者模拟一次消息洪峰看自动扩容机制能不能及时生效。这类演练的价值在于验证机制的有效性而不是验证代码的正确性。演练之后一定要复盘把暴露出来的问题列成清单定期跟进修复。我见过太多团队做了演练、出了报告然后就搁置了下一次真出事的时候报告里写的问题一个都没改。6. 排查速查表与那些年踩过的坑6.1 积压问题快速定位速查表下面这张表是我这些年用得最多的基本覆盖了八成以上的积压场景。实际排查时按从上到下的顺序过一遍通常能在二十分钟内锁定方向。现象组合最可能的原因建议动作生产速率飙升、消费速率平稳上游放量或重试风暴联系上游确认必要时生产端限流生产速率平稳、消费速率下降下游变慢或消费端阻塞查下游 RT、线程池水位、GC 时间生产与消费速率都平稳但 Lag 持续涨容量不足扩分区 扩消费者实例Lag 集中在少数分区分区倾斜或热点 key检查 key 分布考虑加盐或二级拆分消费速率周期性掉到 0频繁 rebalance检查 max.poll.interval.ms 与批处理大小错误率高且伴随重试毒消息或下游拒绝检查死信队列隔离问题消息单实例消费速率差异巨大节点资源不均或网络问题检查单节点资源与网络链路用这张表有个前提你手上得有前面说的那些指标。如果只有一条 Lag 曲线这张表就只能猜。所以监控建设是排查能力的基础设施这个投入不能省。6.2 我踩过的几个真实坑第一个坑是扩了消费者忘了看分区数。那次是半夜起来处理积压看到 Lag 涨得飞快第一反应就是把消费者实例从 16 个扩到 64 个扩完盯了十分钟Lag 一点没降。后来才想起来这个 Topic 只有 16 个分区多出来的 48 个实例全在空转。正确的顺序是先扩分区再扩实例而且扩分区之后要做一次重平衡让新分区被分配出去。这个教训让我在应急手册里加了一条扩容前先确认分区数。第二个坑是批处理太大导致 rebalance。有一次优化消费性能把批大小从 500 调到 5000消费速率确实上去了但没过多久就开始出现周期性的消费停滞。查了很久才定位到是单批处理时间超过了 max.poll.interval.ms消费组认为这个消费者挂了把它踢出组触发重平衡。重平衡期间整个组停摆等恢复之后又重新开始形成恶性循环。最后的解决办法是把批大小降回 2000同时把 max.poll.interval.ms 调大两个参数配合着调才稳。第三个坑是位点提交策略导致的重复消费。有段时间发现某个消费者反复处理同一批消息日志里同样的订单 ID 出现了好几次。查下来是提交位点的时机不对处理完业务逻辑之后异步提交但异步提交还没成功实例就被重启了导致位点回退。改成同步提交之后问题消失代价是吞吐略降。后来我用的方案是处理完立即提交同时业务侧保证幂等两手都要抓。第四个坑是监控只看总量不看分布。有一次 Lag 总量看起来不算高几十万条按平时的消费速率十分钟就能清完结果两个小时都没清掉。把分区拆开看才发现其中一个分区的 Lag 是四十万其他分区早就清空了而那一个分区对应的正好是一个大客户它的消息量占了这个 Topic 的近一半。这就是典型的分区倾斜扩容完全无效最后是临时给这个客户单独开了一条链路才解决。第五个坑是把积压消息当成可以随便丢的。早期有一次处理积压为了快速恢复直接把位点跳到了最新跳之前确认过是非核心的统计队列。结果那条队列里混着一批用户行为数据下游的数仓团队按这份数据算报表跳完之后那天的报表数据出现了明显偏差查了两天才对上账。从那之后我定了个规矩任何位点跳过的操作必须先把要跳过的消息 dump 出来存档哪怕当时看着没用。6.3 几条写在手册首页的经验处理积压这件事技术手段之外更重要的是几条原则性的判断。先定性再动手。这句话我重复了很多遍因为跳过这一步的代价实在太大。判断是突发型、退化型还是容量型决定了后面所有动作的方向五分钟的定性可以省下两小时的瞎折腾。扩容不是万能的。下游有瓶颈的时候扩容只会让更多实例一起等那个瓶颈。判断瓶颈在哪比增加并发更重要。清积压的速度要和下游的承受能力匹配。积压的消息不是消失了是延后处理了你把它们一口气放出来等于把之前攒的流量一次性还给下游下游如果本来就脆弱这一下就崩了。留足冗余但别过度设计。分区数按一年后的峰值来规划是合理的但按五年后的来规划就浪费了。容量规划的本质是给自己留出响应时间而不是追求一劳永逸。把每次积压都变成一次改进。每次处理完积压我都会花半小时做个简单记录什么时间、什么现象、什么原因、怎么解决的、暴露了什么短板。这些记录积累下来比任何文档都有价值因为它们是真实发生过的。最后分享一个小习惯。我在每个消费链路的代码里都会留一个急停开关——一个配置项打开之后消费者会停止处理新消息但保持连接这样可以在不重启、不影响位点的情况下迅速让消费端停止对下游施压。这个开关在大促前的压测、下游故障时的应急都救过场。实现起来很简单就是在处理循环里加一个配置判断成本几乎为零但关键时刻能省下很多麻烦。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑