当 HTTP 连接池遇上 KeepAlive 时
最近在使用netty作為http客戶端通過pool連接tomcat的時(shí)候,出現(xiàn)了很多Connection reset by peer 的IOException的異常。便對(duì)問題的根源做了細(xì)致的調(diào)研。
1. 連接種類
一般連接主要分為長連接,短連接和http的keepalive連接。
1.1 長連接:建立完連接后,該連接不再進(jìn)行釋放。
-
優(yōu)點(diǎn)
-
性能較高,不需要重復(fù)建立tcp連接或者關(guān)閉tcp連接
-
基本上不會(huì)出現(xiàn)CLOSE_WAIT和TIME_WAIT的問題
-
-
缺點(diǎn):一般需要一個(gè)連接池來維護(hù)長連接(一般有數(shù)據(jù)庫連接池,http的連接池等) 復(fù)雜度較高
1.2 短連接:每次請求均需要tcp三次握手建立連接,業(yè)務(wù)執(zhí)行,tcp四次揮手關(guān)閉連接。
-
優(yōu)點(diǎn):實(shí)現(xiàn)簡單。
-
缺點(diǎn):
-
性能較差。大部分都是tcp層面上的交互(新建和關(guān)閉tcp連接)
-
系統(tǒng)會(huì)出現(xiàn)大量的tcp的狀態(tài)是:TIME_WAIT 如果沒有設(shè)置SO_RESUSEADDR ,很容易出現(xiàn)端口被占滿的情況。(在關(guān)閉完連接時(shí),tcp狀態(tài)是TIME_WAIT,只有等2個(gè)MSL后,才會(huì)進(jìn)行close掉)
-
1.3 http的keepalive:用于http協(xié)議。在http 1.1中,為了解決長連接提出的。
-
優(yōu)點(diǎn):用于維護(hù)長連接,提升性能
-
缺點(diǎn):需要在header中進(jìn)行控制,需要交互控制,相對(duì)復(fù)雜。
2 keepalive機(jī)制
提到keepalive, 容易對(duì)下面三種機(jī)制混淆:
-
keepalived
-
tcp 的 keepalive
-
http 的 keepalive
2.1 keepalived
用途:高可用,一般是和lvs一起使用。
具體可參考:http://outofmemory.cn/wiki/keepalived-configuration
2.2 tcp的keepalive
用途:socket連接的保活。在新建socket的時(shí)候,可以設(shè)置SO_KEEPALIVE 進(jìn)行打開。
keepalive主要有三個(gè)參數(shù):
-
tcp_keepalive_time: 一個(gè)連接需要TCP開始發(fā)送keepalive探測數(shù)據(jù)包之前的空閑時(shí)間。以秒為單位
-
tcp_keepalive_probes: 發(fā)送TCP keepalive探測數(shù)據(jù)包的最大數(shù)量,默認(rèn)是9.如果發(fā)送9個(gè)keepalive探測包后對(duì)端仍然沒有響應(yīng),便發(fā)送RST關(guān)閉掉連接。
-
tcp_keepalive_intvl: 發(fā)送兩個(gè)TCP keepalive探測數(shù)據(jù)包的間隔時(shí)間,默認(rèn)是75秒
2.3 http的 keepalive
用途:http的長連接,在http 1.0中使用的為短連接:每一次請求均需要新建tcp連接,http協(xié)議數(shù)據(jù)的發(fā)送接收,關(guān)閉tcp連接。 該種機(jī)制性能很 低,在http 1.1協(xié)議中引入了keepalive機(jī)制來保持tcp連接。
在http1.0中,全部是短連接,如果想建立長連接,需要在header里面加上keepalive,這樣web服務(wù)器看到這個(gè)字段,不會(huì)立馬關(guān)閉連接。而是將tcp連接維持一段時(shí)間。如果需要關(guān)閉,則在header中寫 keepalive close,來告訴 客戶端需要關(guān)閉該連接。
在http1.1中,默認(rèn)會(huì)實(shí)現(xiàn)keepalive,如果使用的是http1.1協(xié)議,header是不需要加上keepalive的。
3. tomcat8對(duì)keepalive的實(shí)現(xiàn)
3.1 http 1.0實(shí)現(xiàn)
tomcat8中,如果發(fā)送的是http1.0的協(xié)議。 tomcat8返回的均是1.1的協(xié)議。并且不管請求的header有沒有Connection:keepalive ,均會(huì)在返回的header中加上connection:close 。下面是訪問tomcat8的截圖:
-
GET請求是http 1.0,但是返回的是1.1的協(xié)議:
?
-
返回的header里面有Connection:close
?
3.2 http 1.1實(shí)現(xiàn)
tomcat8主要有兩個(gè)參數(shù)來控制keepalive的機(jī)制。keepAliveTimeout 和maxKeepAliveRequests
-
keepAliveTimeout: 默認(rèn)和soTimeout 值保持一致,該值為20000ms,也就是在這么長時(shí)間內(nèi)沒有通信,tomcat會(huì)關(guān)閉掉該連接。設(shè)置為-1 則代表不會(huì)關(guān)閉該連接。
-
maxKeepAliveRequests?:默認(rèn)為100,也就是在keepAliveTimeout時(shí)間內(nèi),如果使用次數(shù)超過100,則會(huì)關(guān)閉掉該連接。設(shè)置為-1,則代表不會(huì)關(guān)閉連接。在關(guān)閉后,會(huì)在返回的header上面加上Connection:close 。
如果需要tomcat保持長連接:可配置 maxKeepAliveRequests = "-1" keepAliveTimeout=-"-1" ,則tomcat8不會(huì)關(guān)閉掉該連接。
4. 連接池對(duì)keepalive的處理
主要需要處理兩個(gè)地方:
-
1:maxKeepAliveRequests?連接達(dá)到默認(rèn)的設(shè)置次數(shù)。則會(huì)在header上面加Connection:close。
-
在接收web服務(wù)器返回的數(shù)據(jù)時(shí),需要檢查一下header里面是否有Connection:close,如果close,則需要將該連接從連接池里物理關(guān)閉掉。否則容易出現(xiàn)connection reset by peer的異常。
-
-
2:keepAliveTimeout?超過該時(shí)間沒有流量,則會(huì)關(guān)閉掉連接。
-
tomcat在連接空閑超過該時(shí)間后,會(huì)主動(dòng)關(guān)閉掉連接。會(huì)向客戶端發(fā)送FIN命令。
-
如果是IO(同步socket):則在獲取連接的時(shí)候需要檢查一下該socket的連接狀態(tài)。 因?yàn)閠cp在底層已經(jīng)關(guān)閉了該連接。 如果不檢查的話,則會(huì)SocketCloseException的錯(cuò)誤。
如果是NIO(異步channel) :則在selector的時(shí)候,read數(shù)據(jù)的時(shí)候,會(huì)返回-1,然后將該連接從連接池給物理關(guān)閉掉。
5. Connection reset by peer異常
異常場景:
-
1: 當(dāng)我們往一個(gè)對(duì)端已經(jīng)close的通道寫數(shù)據(jù)的時(shí)候,對(duì)方的tcp會(huì)收到這個(gè)報(bào)文,并且反饋一個(gè)reset報(bào)文,當(dāng)收到reset報(bào)文的時(shí)候,繼續(xù)做select讀數(shù)據(jù)的時(shí)候就會(huì)拋出Connect reset by peer的異常。該異常為jdk拋出的異常。在native代碼里面拋出。
-
2:嘗試和未開放的服務(wù)器端口建立tcp連接時(shí),服務(wù)器tcp將會(huì)直接向客戶端發(fā)送reset報(bào)文
-
3:ack報(bào)文丟失,并且超出一定的重傳次數(shù)或時(shí)間后,會(huì)主動(dòng)向?qū)Χ税l(fā)送reset報(bào)文釋放該TCP連接
連接池出現(xiàn)該異常分析:
-
1:由于客戶端在收到Connection:close的header時(shí)候并沒有物理關(guān)閉該連接,而是將該連接返回到了連接池中。
-
2:下一個(gè)請求拿到該連接發(fā)送數(shù)據(jù),由于tomcat的該socket通道已經(jīng)關(guān)閉,tomcat接收到該連接時(shí),便會(huì)回復(fù)一個(gè)RST。
-
3:客戶端在讀取數(shù)據(jù)(RST的時(shí)候,內(nèi)部會(huì)調(diào)用(JDK)SocketChannel.read的時(shí)候拋出 java.io.IOException(Connection reset by peer)
總結(jié)
以上是生活随笔為你收集整理的当 HTTP 连接池遇上 KeepAlive 时的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 基于 Netty 如何实现高性能的 HT
- 下一篇: MyBatis 与 Hibernate