轉載: http://mp.weixin.qq.com/s?__biz=MzAwNzY4OTgyNA==&mid=2651824719&idx=1&sn=c854f9fb5d6a85e9dbac177e08f95ca9&scene=1&srcid=0827jY7OYr9z2q8SgfGyJUAi#rdjava
最近在使用netty做爲http客戶端經過pool鏈接tomcat的時候,出現了不少Connection reset by peer 的IOException的異常。便對問題的根源作了細緻的調研。redis
通常鏈接主要分爲長鏈接,短鏈接和http的keepalive鏈接。數據庫
1.1 長鏈接:創建完鏈接後,該鏈接再也不進行釋放。django
優勢:性能較高,不須要重複創建tcp鏈接或者關閉tcp鏈接bootstrap
基本上不會出現CLOSE_WAIT和TIME_WAIT的問題tomcat
缺點 : 通常須要一個鏈接池來維護長鏈接(通常有數據庫鏈接池,http的鏈接池等) 複雜度較高服務器
1.2 短鏈接:每次請求均須要tcp三次握手創建鏈接,業務執行,tcp四次揮手關閉鏈接。微信
優勢:實現簡單。
缺點:性能較差。 大部分都是tcp層面上的交互(新建和關閉tcp鏈接)
系統會出現大量的tcp的狀態是:TIME_WAIT 若是沒有設置SO_RESUSEADDR ,很容易出現端口被佔滿的狀況。(在關閉完鏈接時,tcp狀態是TIME_WAIT,只有等2個MSL後,纔會進行close掉)
1.3 http的keepalive:用於http協議。在http 1.1中,爲了解決長鏈接提出的。
優勢:用於維護長鏈接,提高性能
缺點: 須要在header中進行控制,須要交互控制,相對複雜。
提到keepalive, 容易對下面三種機制混淆:keepalived,tcp的keepalive,http的keepalive
用途:高可用,通常是和lvs一塊兒使用。具體可參考:http://outofmemory.cn/wiki/keepalived-configuration
用途:socket鏈接的保活。在新建socket的時候,能夠設置SO_KEEPALIVE 進行打開。
keepalive主要有三個參數:
tcp_keepalive_time: 一個鏈接須要TCP開始發送keepalive探測數據包以前的空閒時間。以秒爲單位
tcp_keepalive_probes: 發送TCP keepalive探測數據包的最大數量,默認是9.若是發送9個keepalive探測包後對端仍然 沒有響應,便發送RST關閉掉鏈接。
tcp_keepalive_intvl: 發送兩個TCP keepalive探測數據包的間隔時間,默認是75秒
用途:http的長鏈接,在http 1.0中使用的爲短鏈接:每一次請求均須要新建tcp鏈接,http協議數據的發送接收,關閉tcp鏈接。 該種機制性能很 低,在http 1.1協議中引入了keepalive機制來保持tcp鏈接。
在http1.0中,所有是短鏈接,若是想創建長鏈接,須要在header裏面加上keepalive,這樣web服務器看到這個字段,不會立馬關閉鏈接。而是將tcp鏈接維持一段時間。若是須要關閉,則在header中寫 keepalive close,來告訴 客戶端須要關閉該鏈接。
在http1.1中,默認會實現keepalive,若是使用的是http1.1協議,header是不須要加上keepalive的。
tomcat8中,若是發送的是http1.0的協議。 tomcat8返回的均是1.1的協議。而且無論請求的header有沒有Connection:keepalive ,均會在返回的header中加上connection:close 。下面是訪問tomcat8的截圖:
GET請求是http 1.0,可是返回的是1.1的協議:
返回的header裏面有Connection:close
tomcat8主要有兩個參數來控制keepalive的機制。keepAliveTimeout 和maxKeepAliveRequests
keepAliveTimeout: 默認和soTimeout 值保持一致,該值爲20000ms,也就是在這麼長時間內沒有通訊,tomcat會關閉掉該鏈接。設置爲-1 則表明不會關閉該鏈接。
maxKeepAliveRequests :默認爲100,也就是在keepAliveTimeout時間內,若是使用次數超過100,則會關閉掉該鏈接。設置爲-1,則表明不會關閉鏈接。在關閉後,會在返回的header上面加上Connection:close 。
若是須要tomcat保持長鏈接:可配置 maxKeepAliveRequests = "-1" keepAliveTimeout=-"-1" ,則tomcat8不會關閉掉該鏈接。
主要須要處理兩個地方:
1:maxKeepAliveRequests 鏈接達到默認的設置次數。則會在header上面加Connection:close。
在接收web服務器返回的數據時,須要檢查一下header裏面是否有Connection:close,若是close,則須要將該鏈接從鏈接池裏物理關閉掉。不然容易出現connection reset by peer的異常。
2:keepAliveTimeout 超過該時間沒有流量,則會關閉掉鏈接。
tomcat在鏈接空閒超過該時間後,會主動關閉掉鏈接。會向客戶端發送FIN命令。
若是是IO(同步socket):則在獲取鏈接的時候須要檢查一下該socket的鏈接狀態。 由於tcp在底層已經關閉了該鏈接。 若是不檢查的話,則會SocketCloseException的錯誤。
若是是NIO(異步channel) :則在selector的時候,read數據的時候,會返回-1,而後將該鏈接從鏈接池給物理關閉掉。
Connection reset by peer異常
異常場景:
1: 當咱們往一個對端已經close的通道寫數據的時候,對方的tcp會收到這個報文,而且反饋一個reset報文,當收到reset報文的時候,繼續作select讀數據的時候就會拋出Connect reset by peer的異常。該異常爲jdk拋出的異常。在native代碼裏面拋出。
2:嘗試和未開放的服務器端口創建tcp鏈接時,服務器tcp將會直接向客戶端發送reset報文
3:ack報文丟失,而且超出必定的重傳次數或時間後,會主動向對端發送reset報文釋放該TCP鏈接
鏈接池出現該異常分析:
1:因爲客戶端在收到Connection:close的header時候並無物理關閉該鏈接,而是將該鏈接返回到了鏈接池中。
2:下一個請求拿到該鏈接發送數據,因爲tomcat的該socket通道已經關閉,tomcat接收到該鏈接時,便會回覆一個RST。
3:客戶端在讀取數據(RST的時候,內部會調用(JDK)SocketChannel.read的時候拋出 java.io.IOException(Connection reset by peer)