Linux下 too many open file错误排查总结
遇到的问题
本项目使用JAVA作为后台系统编程语言,使用tomcat作为中间件。遇到的问题是本系统在个别linux服务器上启动时报 too many open file错误。而且不管有问题还是没问题的这些Linux服务器安装的linux系统版本也是一模一样的,但同样的安装包启动后就会导致有个别几个报错。很是奇怪,所以有了一段艰难的排错过程。
首先是网上找有关too many open file的文章,发现有人说 使用 ulimit -n
可以查看当前系统允许的最大数量,我一查发现是1024,也就是系统默认的。但是我使用 lsof -p xxx | wc -l
命令查看java进程所占用的数量是4303,嗯,确实超过了1024,然后我找代码的原因。使用 lsof -p xxx > 1.txt
,把文件占用的详细情况输出到文件中,发现文件中存在大量的 pipe/eventpoll(或inode)。然后初步怀疑是后台使用NIO导致的,于是我先注释掉了一些使用NIO作为服务的spring bean相关代码。再次启动服务 lsof -p xxx | wc -l
一看发现果然少了,由4303变为了3950。但是这一次系统正常启动了,没有报 too many open file错误。WTF?系统仍然还是1024的限制啊,为啥占用3950就可以了呢。接着找资料吧。。。后找到 open file 数量有soft 和hard之分 。再次使用命令 ulimit -Sn
得到1024,ulimit -Hn
得到4096
原来soft 和hard 搞的鬼,soft限制是软限制 ,是可以允许进程超过它的,但是不能高于hard。而/etc/security/limits.conf
文件中没有相关配置,所以soft默认是1024,hard模式是4096。so 4303 就报错 ,3950就没事。于是迅速修改了 /etc/security/limits.conf
中soft 跟hard的配置(双双改成了65535)。我又把注释掉spring bean放开,再次启动tomcat ,嗯,终于可以正常启动了,于是把修改方式告知现场工程师等待验证。2小时后,现场工程师打电话说还是启动服务还是报错啊,特么的什么情况,我赶紧检查下他是否修改正确,发现配置确实没问题,尴尬…。百思不得其解,到底是啥情况,什么鬼。
继续找资料,发现还存在 /etc/security/limits.d/
这么一个目录,这个目录下可能会存在*-nproc.conf
*-nofile.conf
文件。如果存在后者,这个文件中的配置项会影响/etc/security/limits.conf
中的配置。抱着试试的态度我看了下 /etc/security/limits.d/
目录,发现服务器上只有
20-nproc.conf文件,并没有nofile.conf文件。难道又是没配置默认4096,于是我手动创建了此文件,把soft hard配置上。现场工程师再次测试,发现,(⊙o⊙)…,还是不行啊。
于是跟现场工程师沟通他是如何操作的,发现他的启动服务方式跟我自己测试的时候是不一样的,他是使用service方式来重启的 ,而我是直接使用tomcat启动脚本来启动的。难道是因为这个?但是service服务启动调用的也是tomcat 的启动脚本啊,不太可能是这个原因吧。抱着怀疑的态度继续找资料,果不其然,一篇资料上提到了 如果使用service自启动服务的话,它的文件描述符限制是单独控制的,不设置默认是4096。又是4096 ,程序占用的4303还是逃不过这个4096。修改这个数值需要在service文件的[Service]模块下添加该服务的单独配置LimitNOFILE=XXXX,于是又修改此项为65535。这一次直接使用service方式来启动检验下,终于好了!不管使用service启动还是tomcat脚本启动都没问题了。
以上就是排查此问题的全部过程,前前后后查资料 实际动手测试花费了3天时间。o(╥﹏╥)o
下面总结下,此次排查问题所学习到的一些命令。
相关的Linux命令
1.lsof -p pid | wc -l
查看某个pid进程所占用的文件描述符的总数
2.lsof|awk '{print $2}'|sort|uniq -c|sort -nr|more
显示所有进程所占用文件描述符的列表,以降序排列
3.lsof -p xxx > /home/1.txt
导出某个pid进程所占用的文件描述符详细情况到/home/1.txt 文件中
4.ulimit -n
等价于 ulimit -Sn
查看系统当前open file的soft 限制
5.ulimit -Hn
查看系统当前open file的hard 限制
6.ulimit -a
查看系统当前有关ulimit的所有配置参数
7.cat /proc/pid/limits
查看pid进程所限制的limit数量
遗留问题
目前只是解决了linux系统关于open file限制的问题。但是本系统在不同linux服务器上占用文件描述符大小不同的问题依旧存在。后边几天我又研究了下,因为我发现不同的服务器占用的文件描述符多的服务器它的CPU核数就高。这就很奇怪了,我开始怀疑到程序中哪块的代码会用到CPU核数了。后来发现后台使用了mina框架的SimpleIoProcessorPool类,跟踪这个类发现,这个类会初始化创建一个IoProcessor的数组用来处理IO请求,而数组的大小默认为服务器CPU内核数 + 1
,
/* */ public class SimpleIoProcessorPool<S extends AbstractIoSession>
/* */ implements IoProcessor<S>
/* */ {
/* 79 */ private static final Logger LOGGER = LoggerFactory.getLogger(SimpleIoProcessorPool.class);
/* */
/* */
/* 82 */ private static final int DEFAULT_SIZE = Runtime.getRuntime().availableProcessors() + 1;
其中会初始化一个pool[]的数组,实例化很多IoProcessor对象
for (i = 1; i < this.pool.length; i++) {
/* */ try {
/* 216 */ if (usesExecutorArg) {
/* 217 */ this.pool[i] = ((IoProcessor)processorConstructor.newInstance(new Object[] { this.executor }));
/* */ } else {
/* 219 */ this.pool[i] = ((IoProcessor)processorConstructor.newInstance(new Object[0]));
/* */ }
/* */ }
/* */ catch (Exception e) {}
/* */ }
到此我严重怀疑就是这个东西导致的。因为现场的服务器CPU核数为144,也就是说初始化一次这个类默认就会初始化145个IoProcesser对象。
参考资料:
[1]: linux 文件句柄数查看命令.
[2]: Open Files – ulimit, lsof
[3]: Linux 文件句柄的这些技术内幕,只有 1% 的人知道
[4]: /etc/security/limits.conf 详解与配置
[5]: centos 7修改程序能打开的最大文件数