背景
在实际开发中有一款设备管理软件,一般情况下接入的设备并不多,最多几十台;最近有一个项目中,需要接入2000台这样的设备,运行一段时间后,会大量抛出Too many open files;导致设备无法正常使用,重启服务器后才恢复。
Tomcat下抛出Too many open files问题解析
分析
查看 ulimit -n 指令,发现文件句柄默认是1024,查看对应Tomcat进程(如下)
cat /proc/1787/limits #查看当前应用进程的句柄是4096lsof -p 1787 | wc -l #其中1787是对应的进程
发现已经超出4096了,导致后续已经无法操作文件句柄了,所以系统抛出异常,设备故障发生。
什么时候发生的
经过排查,发现是由一个操作引发的“时间同步”操作---因为系统是局域网内,设备终端的时间需要和系统保持一致,所以有时间同步操作这一功能,由于没有压测大量设备的情况,所以代码中也存在一定的隐患
private static final ThreadPoolExecutor deviceOpeartor=(ThreadPoolExecutor)Executors.new CachedThreadPool(); publicstaticFutureaddTask(PoolTypetype,Callabletask){ ThreadPoolExecutorpool=getPool(type); returnpool.submit(task); }
采用了默认的线程池newCachedThreadPool,池的大小是INT.MAX最大值(就是没限制了), 导致时间同步的任务加进去后,一下子全部执行了。
同时,Tomcat对webapp有一套自己的WebappClassLoader,它在启动的过程中会打开应用依赖的jar包来加载class信息, 但是过一段时间就会把打开的jar包全部关闭从而释放资源。然而如果后面需要加载某个新的class的时候,会把之前所有的jar包全部重新打开一遍, 然后再从中找到对应的jar来加载。加载完后过一段时间会再一次全部释放掉。
所以应用依赖的jar包越多,同时打开的文件句柄数也会越多。 2000台设备时间同步,一下子打开了过多的文件,导致文件句柄一下子超出了。故障就发生了
如何解决
1、代码优化
首先是代码层面的优化,采用阻塞队列
private static final ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("device-pool-%d").build();private static final ThreadPoolExecutor deviceThreadPool = new ThreadPoolExecutor(5, 100, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingDeque(2048), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
同时对于频繁的点击时间同步按钮,需要有机制通知之前队列中的任务还在执行,等待任务全部执行完毕后,再下一次操作
2、系统层面的调优
在不同批次的服务器上,有的服务器Max Open Files设置是65536;
但是一批新机器上的Max OpenFiles都被误设置为4096了。
这个需要运维帮忙重新修复一下,需要重启才会生效;具体的操作可以如下参考:
追加linux系统操作的句柄数 vim /etc/security/limits.conf追加* soft nofile 65536* hard nofile 65536
星号代表全局,soft为软件,hard为硬件,nofile为这里指可打开文件数。
配置修改完之后,需要重启后生效 以上操作是开发人员在排查的过程中可以自己来设置,具体系统层面的最好运维出面
#############################################################
要使limits.conf文件配置生效,必须要确保pam_limits.so文件被加入到启动文件中。 vim /etc/pam.d/login session required /lib/security/pam_limits.so
注意:这个一般最好不要编辑,会导致系统问题(可能无法启动,这个最好由运维来操作)
有时,发现上面设置了还是无效,还有一个办法,在Tomcat下bin中的startup.sh中来追加一行配置
ulimit -n 40960 #在os400=false这一行之前,加上文件句柄数,这个是针对当前Tomcat这个进程下的 os400=false
在实际使用过程中,会遇到各式各样的问题,尝试不同的方法去分析解决它