前段時間在項目維護當中遇見一個問題,方便以後再次遇見類似的問題,可以參考解決問題的思路,記錄如下:
問題概述:
維護項目當中,客戶這邊發來消息,稱自己的網站登錄不上去了,提示用戶名密碼錯誤,我就登錄到服務器上查看系統日誌和報錯情況,如圖:
這個異常來源部署的一個jar包,用於讀取數據存入mongodb,再看了Tomcat也報錯了,錯誤是:java.net.SocketException。看完這樣的問題後,沒有確切的解決方案,自己一臉懵逼無奈的重新啓動服務,可以正常登陸。沒有什麼問題是重啓解決不了的。
問題分析:
雖然重啓服務能暫時以最快的速度解決問題,但是問題很容易復現,很明顯,這個並不是根本的解決方案。
需要進行進一步的分析:
總結下來根據我們當時的代碼和部署的系統有可能出現的原因:
1:因爲首先看到的是mongodb的問題,所以猜測是mongodb鏈接數不夠,而需要等待的鏈接數過多,導致資源無法儘快釋放。
2:因爲代碼內部使用有很多的 HttpClient請求,調用另一個數據庫的數據,可能是請求量過多,或者是請求未正常的關閉。導致內部資源泄露
3:系統併發量太大,鏈接數過多,部分系統或者非系統請求無法正常的釋放關閉,而又持續請求,導致socket鏈接不斷進行積壓,從而導致系統崩潰。
根據以上分析,我們對系統進行了內部的測試。讓錯誤重現。
通過cmd輸入netstat -an 發現有大量處於TIME_WAIT狀態的TCP鏈接,也就是那些Socket未釋放的鏈接
那麼TIME_WAIT狀態的來由是什麼呢?
TCP鏈接需要三次握手,四次揮手。可以參考下面流程圖:
三次握手建立連接示意圖
四次握手關閉連接示意圖
從上面的三次握手建立連接示意圖中可以知道,只要client端和server端都接收到了對方發送的ACK應答之後,雙方就可以建立連接,之後就可以進行數據交互了,這個過程需要三步。
而四次握手關閉連接示意圖中,TCP協議中,關閉TCP連接的是Server端(當然,關閉都可以由任意一方發起),當Server端發起關閉連接請求時,向Client端發送一個FIN報文,Client端收到FIN報文時,很可能還有數據需要發送,所以並不會立即關閉SOCKET,所以先回復一個ACK報文,告訴Server端,“你發的FIN報文我收到了”。當Client端的所有報文都發送完畢之後,Client端向Server端發送一個FIN報文,此時Client端進入關閉狀態,不在發送數據。
Server端收到FIN報文後,就知道可以關閉連接了,但是網絡是不可靠的,Client端並不知道Server端要關閉,所以Server端發送ACK後進入TIME_WAIT狀態,如果Client端沒有收到ACK則Server段可以重新發送。Client端收到ACK後,就知道可以斷開連接了。Server端等待了2MSL(Max Segment Lifetime,最大報文生存時間)後依然沒有收到回覆,則證明Client端已正常斷開,此時,Server端也可以斷開連接了。2MSL的TIME_WAIT等待時間就是由此而來。
我們知道了TIME_WAIT的由來,TIME_WAIT 狀態最大保持時間是2 * MSL,在1-4分鐘之間,所以當系統併發過大,Client-Server連接數過多,Server端會在1-4分鐘之內積累大量處於TIME_WAIT狀態的無法釋放的socket連接,導致服務器效率急劇下降,甚至耗完服務器的所有資源,最終導致No buffer space available (maximum connections reached?): connect
問題的發生
最終解決辦法:
1:關閉不需要的鏈接:
檢查在代碼當中是否有請求完的鏈接沒有正常關閉,如:HttpClient請求中鏈接是否正常關閉,
Mongodb讀取數據時鏈接是否正常關閉,及時關閉請求鏈接和clean,使用
try{}catch(){}finally{}
釋放請求,及時回收資源,避免內存溢出。
2:通過修改註冊表進行配置,減少等待時間
通過regedit啓動註冊表編譯器找到如下路徑:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters
添加參數:
(1)新建
值名稱:MaxUserPort
值類型:DWORD
值數據:65534(十六進制是FFFE)
有效範圍:5000 - 65534 (十進制)
默認:0x1388 5000(十進制)
(2)新建
值名稱:TCPTimedWaitDelay
值類型:DWORD
值數據:0000001e(30)