问题
前一阵子公司项目做了一次压力测试, 中间出现了一个问题: 在50多个并发的时候会出现 java.io.IOException: 打开的文件过多 这个异常. 但是在没有并发的时候是不会出现这个问题的. 这个问题的出现使得项目压力测试没有办法进行下去, 所以必须要尽快解决掉.
尝试查找原因1
首先我们这次的压力测试只是测试项目的首页, 这个页面仅仅是一个商品的列表. 没有在代码中写任何读取文件的操作, 商品的数量也不是很多, 只有10多款商品. 在自己开发用的电脑上也没有出现过这种问题. 所以比较头疼. 上网查了一下, 大部分说的也都是有什么文件打开之后没有关闭, 或者有什么连接池打开之后没有关闭. 针对我们的项目没有什么参考价值. 之后又看到可能是操作系统的中打开文件的最大句柄数受限所导致的, 于是就修改了服务器的最大文件句柄数量. 但是依然没有什么用. 只不过之前是 50 并发就会出问题, 现在变成了70, 80 才会出问题. 根本原因还是没有解决.
尝试查找原因2
无脑上网搜索并尝试了之后, 没有解决问题. 于是试着自己分析一下. 因为这个异常是提示 打开的文件过多 那么一定是有一个文件打开的后并别没有关闭导致的, 那么我能不能知道服务器上所有被打开的文件, 然后查看是哪个文件打开了那么多次呢? 于是上网搜了一下, 果然是有方法的. 可以使用 lsof 命令.
lsof命令用于查看你进程开打的文件
-a:列出打开文件存在的进程;
-c<进程名>:列出指定进程所打开的文件;
-g:列出GID号进程详情;
-d<文件号>:列出占用该文件号的进程;
+d<目录>:列出目录下被打开的文件;
+D<目录>:递归列出目录下被打开的文件;
-n<目录>:列出使用NFS的文件;
-i<条件>:列出符合条件的进程。(4、6、协议、:端口、 @ip )
-p<进程号>:列出指定进程号所打开的文件;
-u:列出UID号进程详情;
-h:显示帮助信息;
-v:显示版本信息。
于是我在服务器上找了一下我们项目的进程号
ps -ef | grep `项目路径`
找到进程号: 22041
查看这个进程所打开的文件
lsof -p 22041
结果:
这里发现有两个xml文件被打开了非常多次, 所以有理由怀疑是因为这两个文件在打开后没有关闭造成了java.io.IOException: 打开的文件过多 这个异常.
开始解决问题
知道了问题所在, 解决起来就有思路. 首先这两个文件是 tiles 框架的配置文件, Tiles 是一种JSP布局框架. 大家可以自行百度. 这里我们用了spring 集成 tiles 中的配置. 配置项是这样的 ( 啊~ 这个配置项被层层包装在公司的框架jar包中, 一个包一个包的找, 找了半天. 愁死我了~ )
<!-- 定义Tiles模板 -->
<bean id="tilesConfigurer" class="org.springframework.web.servlet.view.tiles2.TilesConfigurer">
<property name="checkRefresh" value="true" />
<property name="definitions">
<list>
<value>/WEB-INF/layouts/**/tiles-*.xml</value>
</list>
</property>
</bean>
在这里 发现 这个配置的 checkRefresh 属性比较可疑, 我怀疑是不是在每次请求都会读取文件并且没有关闭这个文件. 但是在代码中我没有找到相应打开并读取文件的地方, 于是尝试把这个属性去掉. 并且再次发版并测试. 问题已经被解决, 再次使用 lsof 命令后没有再出图中的文件被大量打开的问题.
另外
因为 lsof 命令是linux 系统中独有的, win系统下怎么排查问题呢?
win 系统下也有一个类似 lsof 的软件 叫做 procexp. 这个软件也可以检索当前系统中打开了那些文件, 还是挺好用的.
总结一下
出现这个问题的时候因为是第一次遇到, 直接上网搜索出来的内容大部分都是要改操作系统文件句柄数量的, 并没有从根本上解决这个问题, 所以还是要具体分析问题的根本原因. 另外要多学习 linux 的命令, 真的是好用.