首页 / 业界 / 正文

消息队列积压排查与扩容的一线实战笔记

上周三凌晨两点,手机震得床板发颤。
运维群@所有人,Kafka积压了1200万条订单消息,前端用户付完款等十分钟都看不到支付成功通知,客服后台已经被骂爆了。
那时候我刚喝了半罐冰可乐,蹲在服务器机房外的台阶上。手都抖。
之前碰到过太多次类似的场景,很多团队的第一反应是喊运维加机器,把消费者实例拉满,仿佛只要算力够,所有积压都能瞬间消下去。

别上来就盲目扩容


很多人碰到积压的第一反应,是盯着消费者实例的CPU、内存曲线看半天,觉得利用率没跑满就是不够,吭哧吭哧申请资源加实例。上次我们团队第一次碰到积压的时候,就干过这种蠢事:一口气加了20个消费者实例,结果积压没降多少,反而把下游的用户中心库打挂了——每个消费者都在疯狂查用户表的余额字段,数据库连接数直接拉满,所有查询都超时。
说白了,大部分时候积压的根因,根本不在消费端的算力上。
Kafka消费组积压监控 Grafana面板截图

真正值得先盯的指标,是单条消息的处理耗时分布。如果90%的消息处理只需要10毫秒,剩下10%的消息要卡5秒,哪怕你把CPU跑满到100%,整体消费速度也提不上来。上次排查到最后发现,是上周刚上线的优惠券核销逻辑,加了个第三方的规则校验接口,超时时间设成了5秒,高峰期第三方接口抖动,每个碰到校验的消息都要卡满超时时间,10个消费者轮下来,每秒只能处理2条消息,积压不涨才怪。
这种情况你加多少消费者都没用,瓶颈在外部依赖的稳定性上,跟本地算力半毛钱关系没有。

排查路径的几个关键锚点


别拿着监控面板瞎点,顺着几个核心锚点查,基本10分钟就能定位到根因。
先扒消费端的线程栈,jstack打个快照,看所有工作线程的阻塞点。如果大量线程卡在socketRead的调用上,不用想,肯定是下游依赖慢;如果线程在频繁抢本地锁、或者做大对象序列化,那才是本地处理逻辑的问题。
很多人查半天只盯着消费端,完全忘了看broker的状态。之前碰到过一次离谱的积压,查了半小时消费端都没找到问题,最后发现是broker所在的服务器磁盘写满了,消息从生产端写到broker的速度远低于正常水平,看起来像是消费端慢,其实是broker压根没把消息存下来。
RocketMQ消息积压排查流程泳道图
RocketMQ消息积压排查流程泳道图

还有个很容易被忽略的点——消息本身的大小异常。之前有个业务方图省事,把整个用户的全量行为日志序列化之后塞进消息体,单条消息2M,比正常消息大了20倍,网络传输都要卡半天,消费端的处理速度自然上不去。碰到这种写烂代码的业务方,真的想顺着网线爬过去敲他脑袋。

扩容不是堆机器,是算清楚边界


等真的把根因摸清楚,确定是消费端算力不够,再谈扩容的事。
别上来就申请几十台机器往上堆。比如Kafka的消费组规则很简单,每个分区同一时间只能被一个消费者实例消费,你给一个只有32个分区的topic加100个消费者,有68个实例全程闲着,纯浪费钱。扩容前先数清楚topic的分区数,消费者实例数最多和分区数持平,多花的每一分钱都是冤枉钱。
就算真的要扩容,也不能猛冲。之前我们团队有过教训,找到根因之后为了快速消掉积压,一下子把消费速度提了3倍,直接把下游的营销库打满了,表锁锁了半小时,业务又停了一轮。那次之后我们定了个死规矩,扩容的时候消费速率的提升,每10分钟不能超过之前的30%,哪怕积压再多,也不能不管下游的承受能力硬冲。
说回上次那1200万的积压。我们最后没加一台机器,只是把那个第三方优惠券接口的超时时间从5秒改成200毫秒,把高频使用的优惠券规则加了本地缓存,10分钟之后积压就消下去了,前后总共花了20分钟。要是当时上来就盲目加20台机器,不仅钱花了,说不定还要把下游库打挂,指不定要熬到天亮。
做技术这么多年,最烦的就是碰到问题就条件反射喊扩容的人。机器是公司的,资源是大家的,总不能一碰到问题就掏大喇叭喊老板加钱买机器吧?找对根因,比什么都强。