【面试亮点】线上GC问题排查&止损&解决(heap space OOM排查&止损&解决)
许多同学总和我抱怨说面试的时候没有线上实际排查解决gc问题的经验,我这里分享我团队的一次比较好的从 发现问题->及时止损->排查问题->修复问题->复盘 全流程的实践经验,希望能帮到大家.
问题现象: CPU飙至99% 70%服务节点逐渐不可用
凌晨的时候,被电话叫醒,一看指标: CPU.busy 好家伙很多机器节点cpu使用率直接飙高到99.97%,而且raptor(理解为监控大盘工具)大量的失败请求,大概有70%的节点直接不可用!差点给我吓尿了.PM的电话,上下游的电话,各种拉群拉会的.肾上腺素顿时飙高
有问题 , 先止损, 再修复
大家在面对线上问题的时候,第一要务是迅速止损,尽可能降低资损,先保证没有增量问题的时候,再去排查解决存量问题. 不是所有节点不可用,意味着是代码导致机器出了问题,而不是mysql这样的共享资源,因为如果mysql不可用应该是所有节点的请求都会失败. 于是我们迅速查看了服务代码的发布情况,果然在前一天该服务进行了代码上线,因为之前该服务没有过这样的问题,经过短暂的排查后迅速读出结论应该是这次代码发布导致的线上问题,于是迅速执行下述止损流程:
1.禁用问题节点,防止请求打到问题节点上
2.扩容机器,注意扩容时用的包用老的包
3.禁用所有存量的,非扩容节点
4.回滚所有非扩容节点包
至此不再有增量问题,开始排查存量问题,该修数据修数据,该改bug改bug
问题排查
我们这个服务并 不是计算密集型的服务,怎么会有cpu.busy(占用率超过90%) 呢?有经验的老码农可能已经意识到了,这种情况大概率是内存在做垃圾回收,大量的垃圾回收算法会消耗大量的cpu资源.于是我们去查了一下大盘中JVM相关的一些监控,果然发现了大量的FULL GC,而且每次FULL GC的持续时间有5~6S.并且产生了"heap space OutOfMemory"报错,根据时间线推断是因为多次full gc后,仍然无法从堆内存中申请到够用的空间,从而导致堆内存溢出,节点彻底不可用.. 仔细梳理报错,发现居然还伴随着long-SQL,根据监控大盘的报错,是有batch Insert的时候向数据库插入的记录过多,导致了long-SQL.
于是我们重新CR了上线的代码,很快锁定了问题.导致这次问题的罪魁祸首居然是线程池.这次同学上线的代码大题逻辑是如下:
1.查询数据库,获取一些数据
2.根据数据库数据和请求数据做某种业务逻辑运算
3.把运算结果插入数据库表中
很常见的操作,但该同学考虑到性能问题,于是采用了线程池操作
根据业务逻辑给处理的数据分组,对于每组数据开启一个线程去处理
至此该同学的设计还是合理的
但遗漏了一个至关重要的点!
!!!!!
batch insert插入的数据不能过多,否则会导致long-sql!所以有batch insert要提前评估数据量啊!!!
!!!!!
吐了,于是导致多次full gc再到后来heap space OOM的场景就可以复现了
1.从内从中读取了大量的数据,存储到内存堆区域
2.没有评估到batchInsert的数据量(多的有数千行),发生long-sql,时间有5s~6s
3.大量的long-sql任务导致很多任务持续时间长,从而导致线程池队列中任务数迅速增加
4.大量线程任务没执行完,GCRoot不会回收他们栈中索引指向的堆区域
5.当堆剩余空间不足以支持新的线程任务的申请内存空间时,发生full gc
6.多次full gc之后剩余空间仍然不够用 发生heap space OutOfMemory 堆内存溢出
7.节点不可用!
修复问题
找到了问题发生的原因,解决的方法也呼之欲出了.
不过这里说一点,解决这种问题不要总想着去优化JVM分区的一些占比和大小.
这些东西有经验的程序员都知道主要是装逼用的,如果实际的线上问题若非必要不建议改JVM参数,为了解决一个问题去修改JVM参数导致个别机器和公司统一容器配置不同可能会引发更多的问题,弊大于利,而且真要遇到那种问题你连排查都没法排查只能找基建同学,而在大厂跨部门的事情扯皮沟通拉会太多了,等问题解决了,基本服务已经不可用很久了.所以实际解决还是从代码层面来
短期方案:
1.对batch Insert插入的数据做分组,多次批量插入,解决long-sql,避免线程池任务大量积压在队列中
2.扩容,增加50%的节点数(一般扩大heap space的手段不是直接改大机器配置而是多加几台机器)长期方案:
1.重构这一块代码
2.首先看能否和PM沟通,修改交互手段,避免一个线程一次处理如此多的数据
3.重新设计,合理评估线程池参数和batch insert的性能
4.完善监控告警
5.和QA沟通在测试的时候重点测试性能这一块
复盘
又到了最恶心的COE复盘环节了,各种文档写不停,这里我们在wiki里只列一下最重要的问题
1.为什么没有相应的监控指标告警?导致没有在问题出现前提前发现问题并止损?
long-sql的指标是有的,只不过是warn级别的告警,因为是老服务历史包袱比较重,long-sql较多,于是本次服务的long-sql在开发测试时候都不太明显.2.没有灰度吗?为什么直到出现这么大问题的时候才发现问题并回滚?
有灰度,灰度期间没有该问题,这种情况是因为在流量高峰请求参数过大导致的,后续会拉长灰度周期,多观察后再全量.3.方案设计的时候为什么没有预估到这种情况?
这种大参数场景极少,就出现过这一次,超出预期了.4.类似的问题以后怎么避免?解决的流程能否沉淀成SOP?
1.和QA沟通开启上线前long-sql扫描工具,每次上线前checklist强制流程必须过一下long-sql
2.每次使用线程池必须评估下是否会有这种线程池任务积压,每个任务指向一个大对象导致heap OOM场景