一、背景
在进行本地测试时,发现调用某个服务时会偶现调用失败的问题,报错信息如下:
从图中可知,是从注册中心未找到服务导致。一开始以为是有人在重新部署,后面发现这个问题出现的有些频繁,于是开始排查发布记录,发现近期并未有人重启服务,但是容器的重启次数高达 1521 次
二、问题跟进
首先想到两种原因:
- 容器网络出现问题,导致健康检查失败,被 k8s 重新拉起
- 容器因为某些原因挂掉了,导致无法响应请求(盲猜是 OOM 了)
第一种经过运维排查后排除。于是开始排查第二种情况,从监控上看到了 GC 回收时间高达 30s
由于是个老服务,且里面逻辑众多,又没有自动生成 dump 文件的配置,无法完全肯定是 oom 导致。于是修改了项目配置,将 Xms 和 Xmx
参数从 512m 修改为了 1024m,并添加上 oom 后自动 dump 的配置,重新发布了服务。
约半小时左右,服务又又又又重启了,这次捞到了对应的 dump 文件,开始进行分析。
三、问题分析
打开 jvisualvm ,将 dump 文件导入,看到了如下提示:
继续跟进,查看这个线程的状态,找到了对应的方法:
这是个简单的查询,根据 AgencyId 和 OrderId 进行查询,按照 id 进行倒序,然后在数据库中查询了下,发现由于没有创建索引,导致查询很慢。。。。
看似找到了问题,但有个疑问仍没有解决,一个慢 sql 是怎么导致整个服务 OOM 的?。。于是开始继续深挖代码。
四、问题解决
最终排查了下代码,找到了最终的原因:
- 这个查询语句属于一个订单查询的定时任务,里面逻辑比较复杂,且这个任务每隔 2 分钟会执行一次
- 由于是个老服务,之前的定时任务是 http 请求触发,每次执行之前不会判断上一次是否执行完成。。
- 有个慢 sql (单次执行 18s 左右,定时任务中会循环执行这个 sql 查询数据),导致每次定时任务执行时间很长,约 190s 左右
综合以上特性,已经大致猜到了问题产生的原因:
由于慢 sql 导致执行一次定时任务耗时较长,然后每隔 2 分钟会重新触发一次定时任务,导致多个定时任务逻辑同时执行,由于内部逻辑复杂,最终导致内存耗尽,服务宕机,然后被 k8s 重启,循环往复。。。
dump 文件的内容,也证实了这个猜测,确定存在多个线程在执行这个定时任务,OOM 时总共有 12 个线程正在执行。。
解决办法也很简单粗暴,增加表索引,缩短每次定时任务的执行时间,避免出现多个线程同时执行定时任务撑爆内存。
别问为什么不优化代码。。老服务,改不动啊改不动。。。