最近线上运行的两个服务都出现了oom的情况,具体的报错在于线程池创建线程时 内存不够用导致服务假死。 实际追查过程中才发现问题根本不是内存泄露,而是对于部分参数的设置存在问题。因此记录一下查找问题的过程。
1,第一时间发现线上部分请求出现超时不回包的问题,发现服务里面存在oom的情况, 查看代码的部署记录, 服务线上部署了5个节点,并且已经正常运行了2个月,其它4个节点正常运行。从nginx上,将服务节点放下来,然后分析问题。
2,dump出内存堆栈信息,通过jhat分析堆栈信息,发现内存当中存在大量的vertx web框架的Context对象,初步怀疑是vertx web框架的内存泄露导致的问题。(因为这个诊断导致走了弯路)
怀疑1 : vertx web框架存在问题
3, 对vertx web框架做了压测,发现vertx web没有存在内存泄露的问题。
4, 查看了vertx web框架中出现内存泄露的上线问信息。发现一个问题点, vertx web的work线程队列是与socket绑定的,怀疑可能是nginx反向代理的时候,是开启了keepavliede, 可能会导致同一时间的所有请求都发送到一个work线程上,导致内存溢出。
怀疑2 : 怀疑vertx web框架的work线程分配有问题,同一个socket大量请求会堵塞线程队列。(从实际上看,这种怀疑是站不住脚的,以内http协议,即使开通了keepalive,一个socket也是一个一个请求-应答,不会同时传递多个请求过来)。
5, 重新压测,在vertx web框架上配置nginx,配置keepalive,保持与线上环境一致, 压测结果也没有出现内存泄露的问题。
从压测的情况来讲,无法确定vertx web框架存在问题,问题回到原点。
6,再次查看jstack 记录。 发现其中有一个业务线程阻塞。 此业务逻辑是采用并行的方式查询数据,之后用FutureTask get的方式获取返回结果。
7 分析出现业务线程阻塞的原因,查看了具体代码,错误日志。 问题的原因刚好与oom的日志错误对应上, oom日志上线程池创建线程时报oom, 可能的原因是这个并行操作,放入线程池成功,但是启动线程池失败, 那么调用get方式获取结果的时候,因为实际没有启动线程来执行业务,导致一致阻塞。 结合内存泄露的数据分析,也就比较清晰了, vertx web框架的一个业务线程在执行过程中,出现了假死阻塞,导致这个线程的队列一直在增加数据,导致从dump看起来vertx框架内存泄露, 实际情况是在vertx 内存泄露之前,已经有内存不够用的情况,这只是表现,而非原因。
教训1: 错误日志很多时间只是问题的表现,可能不是实际发生问题的根本原因,可能只是实际问题的附带产物。
教训2: 做FutureTask get 方式获取并行请求数据时,一定要设置超时时间,否者一个请求出现问题,就会导致整个线程堵塞,问题累计,可能导致一些奇奇怪怪的问题出现,比如这错的服务假死。
8,回过头来看看创建线程的地方,线程池是定制实现的,查看了问题情况,发现可能的问题原因在于线程池队列的设置上。线程池队列设置过长(20000),另外使用了多个线程池(1,mq消费者,2 rpc框架的客户端),都设置得比较长。
9,查看出现系统oom时的系统请求量,时间是在18:44分, 这个时间点有部分定时器调用,导致并发量急剧增加,另外 系统节点的内存时间也是比较少,只配置了500M。
10 初步怀疑 是因为服务的内存配置太少(500M),导致在rpc框架,并发请求线程池中设置的队列大小不合理, 修改设置,将队列大小由20000调整为500, 修改了部分线程数大小(rpc框架的客户端,如果不做异步请求,实际上是不需要业务线程池,因为它只将返回结果放入一个map当中,由netty的io线程完成即可)。
11 将修改后的程序代码发布到线程做测试,运行近一周时间,一切正常,后续继续追踪这个问题。
教训3: 对于线程池参数一定要按照实际的情况设置,做压测时候的参数设置,往往在实际使用过程中并不是最优,需要根据实际环境使用调整。
注: 有网友提示可能是linux的线程数限制导致无法创建线程的问题。linux的线程数默认是1024个(root用户无限制),有可能是 服务器是上部署了太多了应用,导致线程数达到临界点。 查看了相关修改linux用户线程数限制问题, 调整了线上服务器用户线程数限制为10240