震惊!神秘 Bug 竟导致 8 核服务器 load 破百!

好吧我承认我也加入了 “UC 震惊部”,那么大家请跟随我的文章,一步步走进当天的场景。

场景复现

阳光明媚的一个上午,身为“Bug 小王子”的我正在开开心心的敲着自己的新需求代码。此时,业务反馈群中突然反馈:页面响应变慢!业务报错!整个平台陷入僵死状态!我的心里顿时一惊,开始想早上吃了什么 开始想昨晚有没有更新,最近有没有上什么可能会导致平台卡死的需求。同时我的手上动作并没有停,快速的打开 4 台业务机器并输入了 top 指令。此时发现了一台服务器的如下一幕
业务机器
四台机器 CPU 几乎满载,但此时 load 还不高。但不到一分钟,1号机的 load 直接飙至 100+
 load 破百
是的你没看错,8 核服务器 load average 竟然破百了!我的天,这业务跑得动才有鬼呢!这时 8 个 CPU 的 us 也很快飙到了近 100;啥也别想了,抓紧时间查。但先让业务跑起来,于是先重启了两台机器,让两台服务器先恢复业务。

排查问题

在历经了怀疑昨日更新代码,怀疑MySQL、Redis、ES,怀疑阿里云宿主机等各种靠谱的离谱的原因之后(整个过程过于艰辛,此处不再赘述),开始了我“回归正轨”的排错历程。首先我在这台机器上使用 top -Hp [pid] 指令,查看 Java 进程里到底是哪几个线程把 CPU 跑的都要炸了。然后发现了几个 id 连着的线程。赶紧通过 jstack -l [pid] 命令去打一个新鲜的 ThreadDump 下来看。然后把上面 top 命令看到的几个线程 id 拿出来进行 16 进制化,并把转化后的去 threadDump 的日志文件中搜索。发现是 GC 线程:
threadDump
欸,奇了怪。这里为什么 GC 会占用这么高的 CPU 啊?再去服务器上 jstat -gcutil [pid] 看一下,不看不要紧,一看吓一跳!
gc
我滴个乖乖,这 E 区和 O 区全都近 100% 了,FGC 1-2 秒执行一次,这不卡死才有鬼呢!找到原因了,内存泄漏了呗(其实最终并不是这个原因)!由于前段时间自己为了降低 Redis 的 QPS,使用 Hutool 的缓存工具在内存中缓存了一份热点数据,那肯定就是这个问题咯。然后狠狠骂了 Hutool 一顿(对不起对不起,错怪你了)之后,暂时关掉本地缓存,重启,美滋滋的等着问题解决。是的,没过 5 分钟,又炸了。上服务器一看,依然是 GC 不掉。我急眼了,逼老子用 jmap 做 heapDump 是不是(jmap 会引起 STW,可能会导致应用问题,所以使用时需谨慎)!这时候也顾不上会不会 STW 了,毕竟 CPU 100% 了,就算不 STW 他也跑不动了。但是很快现实打了我的脸,CPU 都给你跑满了,你想做 dump?做梦!敲完 jmap -dump:format=b,file=filename [pid] 命令后,服务器毫不留情的扔了我一个错误(此处没有截图,所以大家只能看文字版啦)

20176: Unable to open socket file: target process not responding or HotSpot VM not loaded
The -F option can be used when the target process is not responding

大致意思就是 JVM 没响应,等着吧。经过重启、等待和不停的尝试后,终于赶在 CPU 跑满前做好了一个 dump。
看到 1.2 G 的 dump 文件被下载下来时,我仿佛能听到我电脑的哀嚎声。那也得干啊,打开 mat,熟悉的界面展现在我眼前。毫不犹豫,Leak Suspects Report (泄漏嫌疑人报告,即内存泄露分析,帮助查看哪些对象正在存活,和为什么不会被 GC)。
mat
当看到下面这张图时,我的内心是狂喜的。
逃逸
终于找到你了!让我们来看看到底是什么东西占了这么大内存还特喵的不给我释放!

泄露分析
泄露分析-2
到这里就已经能看到是 Mysql 的问题了,然后接着往下看堆栈
泄露分析-3
终于找到元凶了,原来是查询数据库的时候数据对象过大。但是为什么会这么大且回收不掉呢?我突然想到了之前排查问题时看到的有一个慢 SQL 返回了 2000w 的行数。接着往下看堆栈也印证了我的猜想,就是那里的业务问题。再往后找,就是前端参数漏传,导致一次性查询了表中所有数据约 2000w 行的数据量。
所以导致这次问题的罪魁祸首就是这个查询。几次查询把内存打满,这时 GC 会过来回收,但是因为在把行数据转化为对象数据,所以强引用回收不掉,于是 GC 就不停的过来空转。空转的结果就是把 CPU 打满,load 增高,导致刚才说的转化过程变慢。这边越慢,那边空转越快。于是这个恶性循环就造成了 load 飙高,CPU 打满,业务停摆的现象。

总结

总结一下本次事故的排查过程吧

  1. top [pid] 发现 load 增高,cpu 打满
  2. top -Hp [pid] 查找高 cpu 的线程 id
  3. jstack -l 做一个线程 dump
  4. 将步骤 2 中的 id 转化为 16 进制,在 jstack 中查询这个线程在做什么
  5. 发现是 gc 的线程后,jstat -gcutil [pid] 1s 查询每秒钟的内存回收情况
  6. 发现 E 区 和 O 区内存满载,回收不掉后。用 jmap -dump:format=b,file=filename [pid] 做 heapdump 查看问题
  7. 利用 mat 工具将 heapdump 打开。查看、定位泄露对象是什么。查看堆栈定位代码位置、业务逻辑等
  8. 追看慢 SQL 加以确认,修改逻辑加上验证,重启恢复

得到的教训就是这种大数据量的表和业务,一定要做查询上的限制,必须分页查询等。如果漏传参数一定要做兜底保护,避免这种查全表的情况出现。

唠唠叨叨用了一天的时间才找到问题解决问题,希望自己下次再碰到这种问题不会再去走弯路,可以快速找到吧。


读书越多越发现自己的无知,Keep Fighting!

欢迎友善交流,不喜勿喷~

Hope can help~

微信搜索 程序员的起飞之路 或微信扫描下方二维码可以加我公众号,保证一有干货就更新~
另外,关注公众号后回复“资料”关键字,可获取小弟多年收藏之精华(懂的都懂)
公众号

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值