Redian新闻
>
实战总结|记一次消息队列堆积的问题排查

实战总结|记一次消息队列堆积的问题排查

科技

阿里妹导读


本文记录了一次问题的排查过程,从中可以学到不少共性的方法论和经验教训,所以做了提炼归纳。(文末有活动)

一、背景

本次问题涉及的系统链路如上图,各系统的基本职责为:
  • Proxy:提供请求代理服务。统一代理发往下游系统的各类请求,从而让上游系统无需感知下游各服务的使用差异;同时提供精细化的限流、导流等服务特性;

  • 拉图 SDK:面向各存储平台提供统一的图片下载功能。它通常与图像类算法模型被前后编排在一起;

  • 预估引擎:提供模型推理服务。支持各种机器学习、深度学习算法模型的工程部署;

二、问题排查

说明:本文用到的消息队列内部称 MetaQ,外部开源版即为 RocketMQ,下文中统一用 “MQ” 指代。


2.1 问题描述

某天上午收到报警通知,提示 Proxy 系统的入口 MQ 有堆积。打开控制台,发现 500 台 Proxy 机器中,有 1 台机器堆积十分严重,而其余机器均运行正常:


2.2 一句话根因

先开门见山直接讲问题根因(详细排查流程可参考后文内容):
  • 个别机器堆积:该机器有消费线程被卡住,虽然其余线程正常消费,但 MQ 机制决定消费位点并不前进;

  • HTTP 下载卡住:所使用的 HttpClient 版本有 bug,特定情况下超时不生效,可能导致线程一直卡住;


2.3 排查流程

1、该机器消费速度太慢?
第一反应是这台机器的消费太慢。同样的消息量,别的机器能快速消化,而它由于消费慢于是持续堆积。然而在 MQ 控制台详细对比后,发现该机器的业务处理时长、消费 TPS 都与其他机器相近,且不同机器的规格本身也是一样的:
其次,观察 Arthas 画出的火焰图,右边两根比较细长的火焰即为我们系统的业务逻辑(火焰比较高是因为 RPC 的调用栈比较深)。从图中可看出,火焰图并没有明显的“平顶效应”,火焰的宽度都比较窄,也可说明该机器在执行业务逻辑时中并没有明显的耗时卡点,由此也可证明该机器并不是因为业务处理慢而造成的堆积:
2、系统指标是否正常?
登录机器,发现 CPU、MEM、LOAD 均正常,且与正常机器相近,没有发现明显线索:
且该机器并无明显的 Full GC:
3、限流导致?
Tip:Proxy 在代理请求时会有限流机制,超出限额的流量会触发阻塞等待,从而保护下游同步服务。
因此,假如今天系统流量很大,超出了下游服务能承受的限制,则超出部分的请求会触发 RateLimiter 限流而阻塞。于是大量 MQ 消费者线程被阻塞,故 Proxy 系统整体的消息消费速度会变慢,最终体现的结果即为入口 topic 堆积。
然而,不管是看该机器的日志还是监控,因限流而阻塞的现象都并不严重,比较通畅:
其次,如果真是因为今天系统入口流量大导致,那应该所有 Proxy 机器(MQ 消费者)都会出现程度相近的堆积。不应该全堆在同 1 台机器上,而其他 499 台机器都正常。因此,排查系统入口流量太大的可能性。
4、MQ 数据倾斜?
预期应该是 500 台 Proxy 机器均分入口 MQ 里面的消息,有没有可能是数据分配时出现了倾斜,导致这台机器分到的消息量太多,其他机器分到的消息很少?
查看 Proxy 上游系统的代码,该系统在向 Proxy 发消息时并没有做自定义 Shuffle,于是会使用 MQ 默认的selectOneMessageQueue策略。而该策略会把当前消息发到基于index % queue_size选出的队列中。进一步查看 index 的逻辑,发现它初始化时以一个随机数为起点,每次访问时会 +1 自增:
结合以上两点,实现的效果即为:对各个 queue 从左到右、均匀地分发消息,并通过 % 实现自动循环:
综上可知,MQ 默认 shuffle 策略为:把消息均分到各个 queue 中。因此,可排除因为 MQ 发送消息数据倾斜,导致我们那台 Proxy 机器堆积的可能性。
5、CPU steal ?
Tip:CPU steal 表示虚拟机中运行的进程被宿主机上的其他进程/虚拟机占用了 CPU 时间的百分比,高 CPU steal 值通常意味着虚拟机中的进程性能下降。
比如机器规格本身写的是 4C,实际因为被 steal 后能用的可能只有 2C。所以体现的结果就是单个请求的 RT 无明显变化(不同 C 之间差异不大),但 C 的量变少了,所以这台机器整体吞吐变小、消费能力变弱,导致堆积。
但排查发现,st 正常,排除这种可能:
6、找到线索:MQ 消费位点没变!
兜兜转转一圈都没找到异常点,由于问题现象是 MQ 堆积,所以又想到可以去查看中间件的日志寻找线索。果然,通过定向搜索卡住机器的 queueId,发现这个队列的消费位点长时间不推进,一直卡在某个位置:
Tip:MQ 拉消息的机制是,拉到的消息会先存放在内存中容量为 1000 的 cache 中,然后这些内存中的消息将被消费者线程消费。当 cache 满了时,就不会再从 queue 中拉取。
由此怀疑:是否因为 Proxy 消费停止 or 消费极慢,导致本地缓存一直是满的,于是 MQ 会停止向 queue 中拉消息,于是 offset 一直没变?
但上文已提到,不管从 MQ 控制台、系统监控指标、机器日志来看,该机器都是正常的,与其余机器没什么区别。那为什么该机器的消费位点就是不动,导致堆积越来越严重?
7、找到根因:某个消费者线程卡住了
Tip:对于本地缓存中的消息,MQ 会开多线程(线程数量由用户指定)去拉取消费。且为了保证消息不丢失,offset 记录的只是最靠前的那条消息。
首先,这个机制是合理的。因为在 At Least Once 语义下,消息允许被消费多次但不允许被遗漏。假设现在有两个线程同时拉取了前后两条消息,且后面的消息先执行完毕。由于前一条消息的执行可能会出现异常,所以不能直接用后者的偏移量去更新 offset,否则消费失败的消息就找不回了。因此,消费位点 offset 的含义是:在该位置及之前的所有消息都已经被正确消费(有点类似 Flink 的 Watermark 机制)。
根据以上机制,回到本问题,假如这台 Proxy 机器的众多 MQ 消费者线程中有任意一个被卡住,那么整个 queue 的消费位点将永远停留在被卡住的消息对应的 offset。此时虽然其他线程都在持续正常消费,但并不能往前推进 offset。另一方面,由于上游还在源源不断往队列中发消息,于是消息只进不出,堆积量 = 最新入口消息的offset - 消费位点offset只增不减,体现在控制台上就是堆积越来越严重!
基于此分析,通过 jstack 查看所有 MQ 消费者线程的状态,的确发现第 251 号线程永远处于 runnable 状态!
有理由怀疑,它就是被卡住的消费线程。因为在 Proxy 系统的业务场景中,绝大部分耗时都在 RPC 同步调用深度学习模型这个环节(快的几百毫秒,慢的可能要几秒),因此线程大部分时间应该处于等待同步调用返回的 waiting 状态!而此处 251 线程永远是 runnable,一定有问题。
进一步打印详情,可找到线程卡住的具体代码位置:
这里getImageDetail方法内部会通过 HTTP 下载图片,以便深度学习模型进行预估。其次,搜索业务日志也发现,搜不到这个线程的日志。因为从昨晚 10 点开始卡住后,该线程就不会产生任何新的日志了,随着机器日志滚动清理,现在所有日志都将不含该线程的内容:
至此,Proxy 系统个别机器 MQ 堆积严重的问题根因找到:该机器的某个消费者线程在通过 HTTP 下载图片时一直卡住,导致整个 queue 的消费位点无法往前推进,从而持续堆积。
8、为什么 HTTP 会一直卡住?
至此,虽然堆积的根因已经找到,且经过尝试短期内可通过应用重启/机器下线等方式临时解决。但长远来看始终存在隐患,因为近期每隔几天时不时就会出现相同问题。需要彻底查清楚 HTTP 卡住的根因,才能从根本上解决本次问题。
通过几次蹲点,捞到了一些会导致线程卡住的图片 URL。发现它们都不是内部的图片地址,且地址也打不开,也不是 jpg 形式结尾的格式:
https://ju1.vmhealthy.cnhttps://978.vmhealthy.cnhttps://xiong.bhjgkjhhb.shop

但问题是,即便是输入这种打不开的极端 URL,由于我们对 HttpClient 设置了 5s 超时,顶多也就会卡住 5s 啊,为什么好像超时机制没生效,一直卡住了十多个小时?
Tip:HTTP 会需要先建立连接,再进行数据传输。由此对应着两种超时:1、连接超时,针对建立连接这一阶段;2、socket 超时,针对数据传输过程中的超时。
经检查,目前代码中只设置了 socket timeout,没设置 connection timeout,因此怀疑上面那些请求直接卡在了前面的 connect 阶段,而又由于没设置 connect 超时,所以请求一直卡住?然而,修改代码后重新尝试请求这些地址,发现还是会一直卡住,需要进一步排查。
9、找到 HTTP 卡住的根因
Tip:找到根因。在特定版本的 HttpClient 中,基于 SSL 连接的请求有 bug:实际会先尝试建立连接,再设置超时时长,先后顺序反了。因此若在连接时卡住,超时还没来得及设置,本次 HTTP 请求就会一直卡下去...
再回过头看上面那几个卡住的 URL,无一例外都是https开头的!破案了,原来是项目用的 HttpClient 有 bug。在升级了 HttpClient 版本后,重新在预发构造测试请求,线程不再卡住,且全部发到线上环境后,近期频繁出现的少量机器堆积问题再也没有出现过。
至此,经历了许多曲折,问题终于彻底解决。


2.4 通盘梳理

从最外层 Proxy 个别机器堆积的表象,不断往内层排查中经历了许多关键节点,直到找到根源。目前所有问题已全部查清,现在站在上帝视角,从内向外完整的因果链条如下:
--> Proxy 系统会基于 HttpClient 去下载图片,然后调用图像类模型做预估
--> 所使用 HttpClient 版本有 bug,在访问 https 地址时,超时不会生效
--> Proxy 系统恰好遇到了少量 https 地址,且恰好卡住(低概率事件),于是会因超时不生效而一直卡住
--> 基于 MQ 的 At Least Once 机制,消费位点将一直停留在卡住消息所对应的 offset 处(尽管其余线程都在正常消费)
--> 上游系统仍然源源不断向 Proxy 发送消息,于是消息只进不出,堆积越来越严重
--> 堆积量超过一定阈值,触发监控报警,用户察觉

三、总结

本次问题的排查过程略微曲折,但也能学到不少共性的方法论和经验教训,在本节统一提炼归纳:
  • 善用排查工具:学会使用 jstack、Arthas、jprofile 等利器,在不同情景下善用合适的工具,能高效地发现异常点,找到线索,从而逐步找到问题根因;

  • 异常的敏感度:有时一些发现问题的线索其实早早就展现在了眼前,但当时因为各种原因(比如最初对问题的疑团很多,未排除的干扰因素多等)导致并不能立马发现线索,还需多积累经验;

  • 广泛查阅资料:除了内网查文档、找相关人员咨询,还要学会去外部英文网站查第一手资料;

  • 困难时要坚持:对一些隐蔽的问题,很多时候不是出现一次就能立马发现问题并解决的。可能需要前前后后经历了几轮排查,最终才能找到根因;

  • 补充底层知识:如果最开始能知道 MQ 的 offset 机制,一些问题就不会走那么多弯路了。后续在用各种中间件的过程中,要多去了解它们的底层原理;

  • 没有玄学问题:代码和机器不会骗人,一切“玄学”问题的背后,都有一套严谨又合理的成因;


参考:

  • HttpClient Bug:https://issues.apache.org/jira/browse/HTTPCLIENT-1478
  • Connection timeout v.s Socket timeout:https://stackoverflow.com/questions/7360520/connectiontimeout-versus-sockettimeout

有奖讨论


程序员最害怕遇到的代码错误是什么?你在写代码时最害怕遇到什么样的代码错误?又怎样开展代码问题排查?点击阅读原文来阿里云开发者社区分享你的观点吧,参与讨论即有机会获得精美礼品哦~

微信扫码关注该文公众号作者

戳这里提交新闻线索和高质量文章给我们。
相关阅读
三天出签!五年多次日本签证轻松到手(在美国办理日签实战详细实战攻略,2023版)我,从近百人的问题中,总结出了2个百试不厌提升方法!程序员必须了解的消息队列之王-Kafka业务高速增长,如祺出行如何用腾讯云消息队列 RocketMQ 应对挑战​“国际大体联对成都大运会很满意”|记者看大运记一次对某企业的渗透测试程序员必须了解的消息队列之王——Kafka我的消息队列专栏Amazon SQS 支持从死信队列重新生成消息7月 · 北京 | 快速提升实战能力,“刑事合规业务实战课程”启动招生面试官:消息队列中如何保证消息的顺序性?鹤冲天 记梦总结|工作中常见的沟通协作原则与方法《译林》2023年第5期,两个绝望的女人,最后一次消失机会每日原则:为了分清楚哪些是人手不足的问题,哪些是能力不够的问题一國兩制模式下:香港的民生應該更好实战总结|复杂系统设计原则与案例中国越来越多的优秀运动人才从校园孕育而生|记者看大运难忘|这10条自驾公路过分美丽!去一次铭记一生!赛场内外各国青年结下深厚友谊!|记者看大运叔叔的问题,阿姨的问题,还是谁的问题实战总结|一次访问Redis延时高问题排查与总结(续)中国对笑果文化与李昊石的处罚实战总结|记一次glibc导致的堆外内存泄露俞敏洪:中国孩子的问题,基本上都是家长的问题体育交流互鉴推动世界多彩发展|记者看大运消息队列CKafka跨洋数据同步性能优化罗翔毕业致辞刷屏:如果世界接受不了我们的理想,不是我们的问题,是世界的问题Slack利用自定义的跟踪架构来协助排查消息通知问题体验丨大家给评评理:这是香港国泰航空的问题,还是我的问题!实战总结|抽丝剥茧,记一次神奇的崩溃医生娘親舅大 (七)賢妻良母10 年 MQ 老兵:消息队列如何学、如何选?| 极客时间记一次容器环境下出现Address not available
logo
联系我们隐私协议©2024 redian.news
Redian新闻
Redian.news刊载任何文章,不代表同意其说法或描述,仅为提供更多信息,也不构成任何建议。文章信息的合法性及真实性由其作者负责,与Redian.news及其运营公司无关。欢迎投稿,如发现稿件侵权,或作者不愿在本网发表文章,请版权拥有者通知本网处理。