問題
前一陣子公司項目做了一次壓力測試, 中間出現了一個問題: 在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 的命令, 真的是好用.