碰到一个问题,现场医院的接口服务经常因为内存满的原因导致容器自动重启。这边监控容器重启后会清除历史日志,所以也看不到错误信息。新到公司咱也不知道docker编排文件为啥这样写,只能靠自己逐步分析了。

看监控图里的内存使用量,每个机器是2G,使用量逐步上升最终导致容器崩溃重启。

定位问题

首先想到是mysql的问题,vpn登录跳板机连接现场数据库,查看对应库的运行情况。

通过SELECT * FROM information_schema.INNODB_TRX来查看事务运行情况,发现有很多RUNING正在运行中状态的事务,拿到事务id手动一个个kill杀掉,服务恢复正常。

但这样只是治标不治本,总不能每次内存满了都人工手动杀吧。

后面连接现场服务环境,准备去看对应GC垃圾回收信息。

先是通过top | grep java找到项目进程id(因为是单机grep java,如果是多服务检索项目名更好),再top -Hp pid查找该进程下线程运行情况,输出大写的P查看线程占用cpu最高的或者通过大写M查看内存占用最高的线程,拿到线程id,再printf "%x\n" sid得到对应十六进制值,通过jstack PID|grep [SID(线程号十六进制)] -A 10查看堆栈日志信息。(jstack -F pid好像能直接查看挂死的线程)

java.lang.Thread.State: TIMED_WAITING (parking)爆了很多这个信息,定位是线程池的问题,创建了很多线程并阻塞占用了内存资源。

进项目代码排查,发现是有个@Async异步任务的问题。

看代码后发现,原来是之前人写的功能,执行任务时没有指定线程池的缘故,这就产生了两个很大的问题:

1.看@Async源码,未指定线程池情况下,默认线程数只有1。

        

2.未指定线程池,默认使用的线程池是SimpleAsyncTaskExecutor。我们不看这个类的源码,只看它上面的文档注释,如下:


主要说了三点

  1. 为每个任务新起一个线程

  2. 默认线程数不做限制

  3. 不复用线程

就这三点,只要任务耗时长一点,服务器就给你来个OOM

解决方法:使用自定义的线程池, 加入如下代码后问题解决。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐