一、背景
先是现场反馈隔一段时间程序就OOM了,需要重启,小伙伴通过dump文件及MAT工具定位到某个定时任务生成了大量的java对象导致OOM,但是为啥会生成这么多对象,分析了一段时间也没找到根因。了解到在最新版应用中增加了一个配置,可以关闭这个功能(现场本身也没用到这个功能),升级即可避免该问题,但是我们要升级吗?不,我们要先尝试找到根因,因为新版本只是增加了一个开关,并不是解决了这个问题,所以升级只是把问题从我们眼前给挪到了屁股后面,万一哪天引发大面积系统异常咋办?
大概跟小伙伴了解了一下目前的情况,发现虽然有一些排查结论,但始终没有哪个结论是有可能合理导致多线程队列任务堆积过多的,这就尴尬了,该问题已经排查好几天了,总不能让现场天天重启应用吧?让现场升级最新版本又不甘心。咋整?提升优先级,把手头其他事儿放放,先搞这个问题吧。
二、排查过程
2.1、代码逻辑简绍
这个定时任务负责定时把系统中变更的材料推送给第三方。大概逻辑是定时从Redis中获取到近期更新的材料清单,然后封装成一个任务扔到一个线程里面(用Executors.newFixedThreadPool创建的一个线程池--这是明面上的罪魁祸首,更加体现了我们不要用Executors创建线程池),在线程里面进行一系列处理后,再对任务进行一层封装,丢到另一个公用线程池中去执行第三方通知发送任务。
2.2、定位问题所处流程
花了半小时梳理了一遍代码逻辑,在感觉可能会出问题的地方都增加上了日志,并且每个日志位置都增加了前后关联的参数输出(这一步很有必要,因为是多线程,而且还是两层多线程处理,如果没有前后关联的参数输出,会导致你不知道单个任务的整个生命周期是否正确结束),发给现场更新,跑一遍把日志收集回来(还有很多其他方法可以0侵入的定位问题,不一定非要给现场发日志,但我们这个场景简单,更新日志比较方便,就不采用那些通用的方法了)。
日志是收集回来了,但是吧!好几个任务的全生命周期日志都打印出来了,所以并不是通用性的问题,既然有成功的,那说明仅存在部分异常任务。也没有什么有用的错误日志,怎么继续定位异常数据呢?日志如下:
前后关联参数日志输出的优势就体现出来了。拿任务生命周期的部分阶段寻找后继流程日志,发现有某个材料的某个流程丢失了后继流程日志输出,这会是问题吗?日志如下:
既然有一个线程卡在这一步,线程池中其他线程是否也卡在这一步了呢?线程池中的线程名称都是有序的,置换一下最后的序号,反向搜索一下线程名称,看看是不是所有线程的最后一个日志都是MaterialOperationalNoticeService-准备处理材料变化?从下面的日志来看,我们的猜想是对的,所有的线程最后一步都卡在了同一个位置。
2.3、定位具体问题代码
再加一波日志,因为已经定位到某个方法内部了,那就针对该问题代码块增加更详细的日志即可(所以第一次的日志范围很重要,太细吧太啰嗦,太粗又不好定位问题代码块,推荐最低每个主要层级代码块都记录日志,像方法里面虽然代码行数比较多,但只是一些初始化对象,校验参数,格式化等明显不会出问题的代码,可以不加日志。把握这个度,就得靠经验了,采坑越多,经验越足,所以采坑不一定是坏事儿)
先来一波正常日志瞅一下
既然知道每个线程最后一步肯定会卡主,那直接使用线程名称反向查找,定位到问题具体步骤,日志如下:
2.4、定位根因
处理材料变化2 和 处理材料变化3 之间就是一个sql查询,而且是一个具有3千6百多万数据的大表。这就简单了,看一下执行计划吧,果然是顺序扫描
但是这么重要的字段不可能不建索引呀,确认了下对应列确实有索引
这特么就尴尬了,索引失效了。从统计数来看,实际的数据量与统计信息差异不大,就几十条,想更新一下统计信息来着,更新表统计信息执行了一个多月小时都没搞定(sybase应该也有类似postgresql的maintenance_work_mem的参数,用于控制维护数据库时可用的内存大小,也可能是其他参数影响的,但是懒得找了,暴力解决吧),直接重建索引(sybase的DBCC REINDEX执行了10多个小时也没成功,最后执行了一下更新索引统计数解决的),搞定!
三、结语
这事儿难吗?其实不难,有思路的话,几个小时就搞定了。你要抱着一种我一定要解决掉这个问题,没有问题是我解决不了的,越是别人解决不了的问题我才越要解决掉他的心态,才能快速成长。
该妥协的时候要妥协,不该妥协的时候,一定要死磕到底。
不要用Executors创建线程池,第一层线程池用Executors创建的线程池,公用线程池是直接实例化的ThreadPoolExecutor,并且合理限制了核心线程数、最大线程数及阻塞队列的边界(1024个),如果第一层线程池也限制边界,最起码现场不会出现OOM,仅仅是某个功能不好用,而不是整个应用挂掉。多线程的讲解请参考我的另一篇帖子 https://blog.csdn.net/leandzgc/article/details/103082437
相信自己,别人既然能写出来这段代码,无论多复杂,我们一定能梳理清楚。