1. 程式人生 > >當 HTTP 連線池遇上 KeepAlive 時

當 HTTP 連線池遇上 KeepAlive 時

最近在使用netty作為http客戶端通過pool連線tomcat的時候,出現了很多Connection reset by peer 的IOException的異常。便對問題的根源做了細緻的調研。

1. 連接種類

一般連線主要分為長連線,短連線和http的keepalive連線。

1.1 長連線:建立完連線後,該連線不再進行釋放。

  • 優點

    • 效能較高,不需要重複建立tcp連線或者關閉tcp連線

    • 基本上不會出現CLOSE_WAIT和TIME_WAIT的問題

  • 缺點:一般需要一個連線池來維護長連線(一般有資料庫連線池,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中進行控制,需要互動控制,相對複雜。

2 keepalive機制

提到keepalive, 容易對下面三種機制混淆:

  • keepalived

  • tcp 的 keepalive

  • http 的 keepalive

2.1 keepalived

用途:高可用,一般是和lvs一起使用。

具體可參考:http://outofmemory.cn/wiki/keepalived-configuration

2.2 tcp的keepalive

用途: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秒

2.3 http的 keepalive

用途: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的。

3. tomcat8對keepalive的實現

3.1 http 1.0實現

tomcat8中,如果傳送的是http1.0的協議。 tomcat8返回的均是1.1的協議。並且不管請求的header有沒有Connection:keepalive ,均會在返回的header中加上connection:close 。下面是訪問tomcat8的截圖:

  • GET請求是http 1.0,但是返回的是1.1的協議:

     

  • 返回的header裡面有Connection:close

     

3.2 http 1.1實現

tomcat8主要有兩個引數來控制keepalive的機制。keepAliveTimeout 和maxKeepAliveRequests

  • keepAliveTimeout: 預設和soTimeout 值保持一致,該值為20000ms,也就是在這麼長時間內沒有通訊,tomcat會關閉掉該連線。設定為-1 則代表不會關閉該連線。

  • maxKeepAliveRequests :預設為100,也就是在keepAliveTimeout時間內,如果使用次數超過100,則會關閉掉該連線。設定為-1,則代表不會關閉連線。在關閉後,會在返回的header上面加上Connection:close 。

如果需要tomcat保持長連線:可配置 maxKeepAliveRequests = "-1" keepAliveTimeout=-"-1" ,則tomcat8不會關閉掉該連線。

4. 連線池對keepalive的處理

主要需要處理兩個地方:

  • 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,然後將該連線從連線池給物理關閉掉。

5. 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)