1、闡述
內部架構:Tomcat應用程序---> nginx ---> 其他Tomcat應用程序,內部Tomcat應用通過nginx調用其他應用。
HTTP插件:HttpClient 4.2.3
關閉連接的代碼:httpClient.getConnectionManager().closeIdleConnections(5, TimeUnit.SECONDS);
2、說明
要說明的是CLOSE_WAIT產生的原因和服務器、nginx、其他配置無關,是HttpClient的getConnectionManager引起的。
3、排查思路
這個問題已經困擾我很久了,查看過網上的很多辦法,也試過很多方法。
比如:修改服務器內核、修改nginx配置文件、更改nginx版本,都是沒有用的,還是上面那句話和服務器、nginx無關。
最后決定自己分析請求,查找真正的根本原因,以下為排查的最終步驟
4、問題排查
首先確認CLOSE_WAIT產生的鏈接,鏈接的IP和端口
由上圖看出是本機鏈接nginx 81端口造成的CLOSE_WAIT
抓包分析其中一個CLOSE_WAIT所用的本機端口:
抓包分析正常關閉的請求:
分析不正常端口41584,晚上22點01分02秒請求連接,22點01分02秒傳輸數據結束,22點02分07秒,nginx發送關閉連接的包,Tomcat同意關閉,問題就出現在這里,在Nginx請求關閉連接后,Tomcat並沒有回復同樣關閉連接的包,沒有完成四次握手,故產生了CLOSE_WAIT。
分析所有正常連接發現沒有產生CLOSE_WAIT的端口都是Tomcat主動關閉的,產生CLOSE_WAIT的都是nginx主動關閉,Tomcat被動關閉的。
再次分析所有的不正常端口
發現Tomcat周期性的向Nginx發送關閉連接的請求,但是Nginx回復Reset包,說白了就是Tomcat請求關閉連接,但是Nginx說我沒有這個鏈接(已經在前面主動關閉),所有CLOSE_WAIT會一直存在,直至兩個小時以后系統強制關閉。至於為什么會周期性的一起並發的關閉的連接,而不是一個一個關閉,或者為什么在收到Nginx關閉連接請求,Tomcat不關閉,看上述Java代碼:httpClient.getConnectionManager().closeIdleConnections(5, TimeUnit.SECONDS);
這段代碼表示調用httpClient的getConnectionManager,然后利用closeIdleConnections進行關閉空閑連接,5代表是五秒(不知道解釋的對不對)。
網上查找getConnectionManager,說是httpclient的鏈接池管理工具。就是把請求都扔里面,然后Manager幫你做相關處理。
但是上述代碼寫的是5秒之內連接空閑就會關閉,httpclient又是一個很成熟的技術,於是沒有懷疑這個的問題(我不是開發,代碼層我無法分析)。
繼續分析其他正常關閉的包,發現並不是所有正常關閉的連接都是五秒關閉的,而產生CLOSE_WAIT的,一般請求關閉都是超過65秒的(65是nginx keepalive timeout的值),為了確認問題的根源,我把nginx的keepalive timeout設置為240秒(Nginx主動關閉連接后,最長Tomcat第一次發送關閉連接的包據數據傳輸完畢的時間間隔為3分28秒),實時查看CLOSE的增長變得緩慢,改為360秒,幾乎不怎么增長,但是還有增長,索性改為0,過了一個多小時,只會下降,不會增多,所以斷定是HTTPCLIENT出現的問題。
5、繼續分析
查看httpclient官方文檔:http://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/conn/ClientConnectionManager.html
上面說在給定的時間內(上述代碼的五秒)鏈接沒有被使用,就會在池中關閉連接。同時也會關閉過期的連接。
看解釋說只要連接5秒沒有被使用,就會關閉連接,不會大於65秒的,至此又回到詫異懵逼中。。。
再次回想連接池中周期性發送FIN包,讓我判斷沒有在五秒內關閉連接只有兩種可能:一、配置沒有生效,二、HTTPCLIENT空閑連接檢測機制。
首先從服務器端快速解決:修改linux內核net.ipv4.tcp_tw_recycle 和 net.ipv4.tcp_timestamps 為 0,sysctl -p使之生效。
然后把自己的想法說給開發人員說后,無法斷定空間連接檢測的機制是什么,於是決定修改代碼,換用另一種關閉的方式(沒有時間考慮上面的兩個想法):將所有完成請求的連接通過httpclient的releaseConnection和SHUTDOWN進行關閉,修改完成並在測試環境部署(測試環境也同樣有CLOSE_WAIT),運行至今改過代碼的並無產生任何CLOSE_WAIT。
6、總結
CLOSE_WAIT產生的原因是由代碼引起的,目前能確認的是HTTPCLIENT的getConnectionManager的連接池引起的,但是為什么設置的5秒沒有生效,空閑連接的檢測機制是什么,這些還無法得知。
截止到2018年6月4日,自從改了代碼和內核參數以后,運行半年,在沒有出現過過多CLOSE_WAIT的情況。