1. 程式人生 > >java.net.SocketException: Too many open files

java.net.SocketException: Too many open files

原因: 作業系統的中開啟檔案的最大控制代碼數受限所致,常常發生在很多個併發使用者訪問伺服器的時候。因為為了執行每個使用者的應用伺服器都要載入很多檔案(new 一個socket 就需要一個檔案控制代碼),這就會導致開啟檔案的控制代碼的缺乏。

解決方式:

a) 儘量把類打成 jar 包,因為一個 jar 包只消耗一個檔案控制代碼,如果不打包,一個類就消耗一個檔案控制代碼。

b) java 的 GC 不能關閉網路連線開啟的檔案控制代碼,如果沒有執行 close()則檔案控制代碼將一直存在,而不能被關閉。

也可以考慮設定 socket 的最大開啟 數來控制這個問題。對作業系統做相關的設定,增加最大檔案控制代碼數量。

ulimit -a 可以檢視系統目前資源限制,ulimit -n 10240 則可以修改,這個修改只對當前視窗有效。

------------------------------------

檢視採集資料的tomcat日誌,習慣性的先翻到日誌的最後去檢視有沒有異常的列印,果然發現了好幾種異常資訊,但是最多還是這個:

“Too manay open files” 問題很明顯啊,檔案描述符超出限制導致無法開啟檔案或建立網路連線,這個問題又會導致一些其它問題的產生,肯定是ulimit沒有優化,於是檢查ulimit的設定;

open files竟然是65535,已經做過了優化,是不是先啟動的tomcat等服務,然後才對ulimit做的優化?有可能,這樣的話重啟一下服務就ok了,於是將全部服務重啟了一遍,執行正常了,不一會報表就顯示資料了,然後告訴技術支援,問題已經解決了,然後就去處理別的case了;

結果還不到20分鐘,技術支援說,報表又沒有資料了,於是又打資料採集的應用的tomcat日誌檢視,發現了一堆異常,全都是一個錯:

這個異常非常多,看報錯資訊,是tomcat的connector在執行寫操作的時候發生了Broken pipe異常,connector是tomcat處理網路請求的,難道是網路出問題了,但是為什麼發生異常的都是寫,讀就沒問題呢?為了判斷是不是網路問題,於是用wget命令在本地訪問了一下伺服器的一個介面,結果發現等了好久都沒有響應,正常情況下應該是馬上就有響應的,這說明不是網路的原因,是伺服器的問題,又用命令查看了下當前tcpip連線的狀態:

CLOSE_WAIT 狀態的連線竟然有3853個,這太不正常了,這說明是客戶端先關閉了連線,伺服器端沒有執行關閉連線的操作,導致伺服器端一直維持在CLOSE_WAIT的狀態,如果不對作業系統的keepalive做優化,這個狀態預設會維持兩個小時,查看了下系統的設定:

果然是7200秒,這就解釋通了,為什麼第一次檢視tomcat日誌最後報錯都是“Too manay open files”異常,一定是在兩個小時內,close_wait狀態暴增,導致檔案描述符超過了65535的最大限制;

而這個狀態應該就是broken pipe 異常導致的,是什麼導致的broken pipe異常呢?為什麼探針關閉了連線,但是資料採集伺服器卻沒有關閉連線?報異常的是tomcat的connector,tomcat不可能會忘記呼叫close方法去關閉連線,排除了程式的問題,也想不出來是什麼導致的了;

於是去拿了往採集伺服器上傳資料的探針的日誌檢視,竟然有大量的一個異常:

都是read time out異常,那麼問題就明確了,  是探針端讀取超時了,斷開了連線,而這時候資料採集伺服器還在處理請求,它並不知道探針端已經斷開了連線,處理完請求後再將處理結果發給探針,就broken pipe了;

原來這個異常是客戶端讀取超時關閉了連線,這時候伺服器端再向客戶端已經斷開的連線寫資料時就發生了broken pipe異常!

探針讀超時的時間是2分鐘,伺服器為什麼這麼長的時間都沒有響應呢?於是使用jstack命令匯出了tomcat的執行緒棧資訊進行分析,最後發現程式碼中有耗時的操作加了鎖,導致執行緒阻塞(保密原因,在這裡就不貼程式碼了);

這裡總結一下,給我發私信的有些朋友沒有get到Broken piple問題的重點,並不是只有超時才會導致這個問題,只要是連線斷開,再往這個斷開的連線上去執行寫操作,都會出現這個異常,客戶端超時斷開只是其中的一種情況:

另外,當看到“Too manay open files”異常的時候,通常做法除了檢查ulimit系統限制外,還應該看一下程序開啟的檔案控制代碼數,cat /proc/sys/fs/file-nr命令檢視系統總控制代碼數,當前應用開啟的檔案控制代碼數使用ls -l /proc/<pid>/fd | wc -l命令,這裡還好忽略了這一步,否則可能又要花費一些時間來查詢系統真正的問題;

通過這個案例可知,排查問題時,在有些情況下,你第一眼看到的異常資訊未必就是問題的根源所在,而是後續一些連鎖反應,尤其是當大量出現同一個異常的情況下,不要看最後一條異常日誌,應該先去日誌裡面查詢第一齣現該異常的位置,看看這個異常發生之前系統的狀況