这里分享一个线上问题案例及解决过程。
小k来到新公司不久,一天早上,小K收到一封报警邮件,提示某项目出现较多异常。小k点击邮件查看详情,发现提示out of menery错误。此时同事们还没来,小k暗自吐槽:“怎么都没来,这个项目代码自己也还不熟悉阿”。但是小k转念一想:“毕竟也是有2年开发经验的人,自己先排查下吧”
于是小k开始查看日志系统,发现报错基本集中在个别机器上。小k打开该项目的机器监控,发现有两台机器的内存使用率高于正常水平。进一步发现这两台机器的线程数量远高于其他机器以及此前的正常水平。
小k心中有了方向,进一步针对机器的线程收集监控,获取到异常机器的大量线程来自同一个线程池。此时小k的组长大M来了,小k把相关问题以及排查结果向大M进行了说明,大M找到项目中使用该线程池的地方,小k暗叹:“还是对项目熟悉定位问题比较快哇”。然后两个人一起看起了代码。不一会,就发现了问题,当然代码的创造者早已离开。
关键代码如下:
ExecutorService executor = Executors.newFixedThreadPool(size);
问题在于这里的size是会根据请求中某参数的数量动态创建的。原先的的请求内参数并没有特别多,创建了定长线程池执行完后可以安全释放。但是现在遇到两个参数过多的请求(并没有对请求方限制批量处理的的数量),导致创建过多线程消耗过多内存,程序异常,也无法执行下面的对线程池的回收和销毁。
确定了问题之后,组长让我对这里进行了修改,添加了创建线程数量的限制,防止线程泄露。上线后项目也恢复了正常。
小k在这次问题排查中学到了很多:
- 创建线程池本身要注意,要合理限制创建线程的最大数量,防止消耗过多的资源;
- 线程池使用中要确保在正常和异常情况下对线程池进行释放;
- 批量接口最好对最大请求数量进行约定和限制,防止不可控的异常;
小k整理了下思路,继续开始一天愉快得工作,直到深夜….