什么是KeepAlive?
首先,我們要明確我們談的是TCP的 KeepAlive 還是HTTP的 Keep-Alive。TCP的KeepAlive和HTTP的Keep-Alive是完全不同的概念,不能混為一談。實(shí)際上HTTP的KeepAlive寫法是Keep-Alive,跟TCP的KeepAlive寫法上也有不同。
TCP的keepalive是側(cè)重在保持客戶端和服務(wù)端的連接,一方會不定期發(fā)送心跳包給另一方,當(dāng)一方端掉的時(shí)候,沒有斷掉的定時(shí)發(fā)送幾次心跳包,如果間隔發(fā)送幾次,對方都返回的是RST,而不是ACK,那么就釋放當(dāng)前鏈接。設(shè)想一下,如果tcp層沒有keepalive的機(jī)制,一旦一方斷開連接卻沒有發(fā)送FIN給另外一方的話,那么另外一方會一直以為這個(gè)連接還是存活的,幾天,幾月。那么這對服務(wù)器資源的影響是很大的。
HTTP的keep-alive一般我們都會帶上中間的橫杠,普通的http連接是客戶端連接上服務(wù)端,然后結(jié)束請求后,由客戶端或者服務(wù)端進(jìn)行http連接的關(guān)閉。下次再發(fā)送請求的時(shí)候,客戶端再發(fā)起一個(gè)連接,傳送數(shù)據(jù),關(guān)閉連接。這么個(gè)流程反復(fù)。但是一旦客戶端發(fā)送connection:keep-alive頭給服務(wù)端,且服務(wù)端也接受這個(gè)keep-alive的話,兩邊對上暗號,這個(gè)連接就可以復(fù)用了,一個(gè)http處理完之后,另外一個(gè)http數(shù)據(jù)直接從這個(gè)連接走了。減少新建和斷開TCP連接的消耗。
二者的作用簡單來說:
HTTP協(xié)議的Keep-Alive意圖在于短時(shí)間內(nèi)連接復(fù)用,希望可以短時(shí)間內(nèi)在同一個(gè)連接上進(jìn)行多次請求/響應(yīng)。
TCP的KeepAlive機(jī)制意圖在于?;?、心跳,檢測連接錯(cuò)誤。當(dāng)一個(gè)TCP連接兩端長時(shí)間沒有數(shù)據(jù)傳輸時(shí)(通常默認(rèn)配置是2小時(shí)),發(fā)送keepalive探針,探測鏈接是否存活。
總之,記住HTTP的Keep-Alive和TCP的KeepAlive不是一回事。
tcp的keepalive是在ESTABLISH狀態(tài)的時(shí)候,雙方如何檢測連接的可用行。而http的keep-alive說的是如何避免進(jìn)行重復(fù)的TCP三次握手和四次揮手的環(huán)節(jié)。
TCP的KeepAlive
1.為什么要有KeepAlive?
在談KeepAlive之前,我們先來了解下簡單TCP知識(知識很簡單,高手直接忽略)。首先要明確的是在TCP層是沒有“請求”一說的,經(jīng)常聽到在TCP層發(fā)送一個(gè)請求,這種說法是錯(cuò)誤的。
TCP是一種通信的方式,“請求”一詞是事務(wù)上的概念,HTTP協(xié)議是一種事務(wù)協(xié)議,如果說發(fā)送一個(gè)HTTP請求,這種說法就沒有問題。也經(jīng)常聽到面試官反饋有些面試運(yùn)維的同學(xué),基本的TCP三次握手的概念不清楚,面試官問TCP是如何建立鏈接,面試者上來就說,假如我是客戶端我發(fā)送一個(gè)請求給服務(wù)端,服務(wù)端發(fā)送一個(gè)請求給我。。。
這種一聽就知道對TCP基本概念不清楚。下面是我通過wireshark抓取的一個(gè)TCP建立握手的過程。(命令行基本上用TCPdump,后面我們還會用這張圖說明問題):

現(xiàn)在我看只要看前3行,這就是TCP三次握手的完整建立過程,第一個(gè)報(bào)文SYN從發(fā)起方發(fā)出,第二個(gè)報(bào)文SYN,ACK是從被連接方發(fā)出,第三個(gè)報(bào)文ACK確認(rèn)對方的SYN,ACK已經(jīng)收到,如下圖:

但是數(shù)據(jù)實(shí)際上并沒有傳輸,請求是有數(shù)據(jù)的,第四個(gè)報(bào)文才是數(shù)據(jù)傳輸開始的過程,細(xì)心的讀者應(yīng)該能夠發(fā)現(xiàn)wireshark把第四個(gè)報(bào)文解析成HTTP協(xié)議,HTTP協(xié)議的GET方法和URI也解析出來,所以說TCP層是沒有請求的概念,HTTP協(xié)議是事務(wù)性協(xié)議才有請求的概念,TCP報(bào)文承載HTTP協(xié)議的請求(Request)和響應(yīng)(Response)。
現(xiàn)在才是開始說明為什么要有KeepAlive。鏈接建立之后,如果應(yīng)用程序或者上層協(xié)議一直不發(fā)送數(shù)據(jù),或者隔很長時(shí)間才發(fā)送一次數(shù)據(jù),當(dāng)鏈接很久沒有數(shù)據(jù)報(bào)文傳輸時(shí)如何去確定對方還在線,到底是掉線了還是確實(shí)沒有數(shù)據(jù)傳輸,鏈接還需不需要保持,這種情況在TCP協(xié)議設(shè)計(jì)中是需要考慮到的。
TCP協(xié)議通過一種巧妙的方式去解決這個(gè)問題,當(dāng)超過一段時(shí)間之后,TCP自動(dòng)發(fā)送一個(gè)數(shù)據(jù)為空的報(bào)文給對方,如果對方回應(yīng)了這個(gè)報(bào)文,說明對方還在線,鏈接可以繼續(xù)保持,如果對方?jīng)]有報(bào)文返回,并且重試了多次之后則認(rèn)為鏈接丟失,沒有必要保持鏈接。
2.怎么開啟KeepAlive?
KeepAlive并不是默認(rèn)開啟的,在Linux系統(tǒng)上沒有一個(gè)全局的選項(xiàng)去開啟TCP的KeepAlive。需要開啟KeepAlive的應(yīng)用必須在TCP的socket中單獨(dú)開啟。Linux Kernel有三個(gè)選項(xiàng)影響到KeepAlive的行為:
- tcp_keepalive_time 7200// 距離上次傳送數(shù)據(jù)多少時(shí)間未收到新報(bào)文判斷為開始檢測,單位秒,默認(rèn)7200s
- tcp_keepalive_intvl 75// 檢測開始每多少時(shí)間發(fā)送心跳包,單位秒,默認(rèn)75s
- tcp_keepalive_probes 9// 發(fā)送幾次心跳包對方未響應(yīng)則close連接,默認(rèn)9次
TCP socket也有三個(gè)選項(xiàng)和內(nèi)核對應(yīng),通過setsockopt系統(tǒng)調(diào)用針對單獨(dú)的socket進(jìn)行設(shè)置:
- TCPKEEPCNT: 覆蓋 tcpkeepaliveprobes
- TCPKEEPIDLE: 覆蓋 tcpkeepalivetime
- TCPKEEPINTVL: 覆蓋 tcpkeepalive_intvl
舉個(gè)例子,以我的系統(tǒng)默認(rèn)設(shè)置為例,kernel默認(rèn)設(shè)置的tcpkeepalivetime是7200s, 如果我在應(yīng)用程序中針對socket開啟了KeepAlive,然后設(shè)置的TCP_KEEPIDLE為60,那么TCP協(xié)議棧在發(fā)現(xiàn)TCP鏈接空閑了60s沒有數(shù)據(jù)傳輸?shù)臅r(shí)候就會發(fā)送第一個(gè)探測報(bào)文。
3. 需要注意,KeepAlive的不足和局限性
其實(shí),tcp自帶的keepalive還是有些不足之處的。
keepalive只能檢測連接是否存活,不能檢測連接是否可用。例如,某一方發(fā)生了死鎖,無法在連接上進(jìn)行任何讀寫操作,但是操作系統(tǒng)仍然可以響應(yīng)網(wǎng)絡(luò)層keepalive包。
TCP keepalive 機(jī)制依賴于操作系統(tǒng)的實(shí)現(xiàn),靈活性不夠,默認(rèn)關(guān)閉,且默認(rèn)的 keepalive 心跳時(shí)間是 兩個(gè)小時(shí), 時(shí)間較長。
代理(如socks proxy)、或者負(fù)載均衡器,會讓tcp keep-alive失效
基于此,我們旺旺需要加上應(yīng)用層的心跳。這個(gè)需要自己實(shí)現(xiàn),這里就不展開了。
HTTP的Keep-Alive
1. HTTP為什么需要Keep-Alive?
通常一個(gè)網(wǎng)頁可能會有很多組成部分,除了文本內(nèi)容,還會有諸如:js、css、圖片等靜態(tài)資源,有時(shí)還會異步發(fā)起AJAX請求。只有所有的資源都加載完畢后,我們看到網(wǎng)頁完整的內(nèi)容。然而,一個(gè)網(wǎng)頁中,可能引入了幾十個(gè)js、css文件,上百張圖片,如果每請求一個(gè)資源,就創(chuàng)建一個(gè)連接,然后關(guān)閉,代價(jià)實(shí)在太大了。
基于此背景,我們希望連接能夠在短時(shí)間內(nèi)得到復(fù)用,在加載同一個(gè)網(wǎng)頁中的內(nèi)容時(shí),盡量的復(fù)用連接,這就是HTTP協(xié)議中keep-alive屬性的作用。
- HTTP的Keep-Alive是HTTP1.1中默認(rèn)開啟的功能。通過headers設(shè)置"Connection: close "關(guān)閉
- 在HTTP1.0中是默認(rèn)關(guān)閉的。通過headers設(shè)置"Connection: Keep-Alive"開啟。
對于客戶端來說,不論是瀏覽器,還是手機(jī)App,或者我們直接在Java代碼中使用HttpUrlConnection,只是負(fù)責(zé)在請求頭中設(shè)置Keep-Alive。Keep-Alive屬性保持連接的時(shí)間長短是由服務(wù)端決定的,通常配置都是在幾十秒左右。
TCP連接建立之后,HTTP協(xié)議使用TCP傳輸HTTP協(xié)議的請求(Request)和響應(yīng)(Response)數(shù)據(jù),一次完整的HTTP事務(wù)如下圖:

這張圖我簡化了HTTP(Req)和HTTP(Resp),實(shí)際上的請求和響應(yīng)需要多個(gè)TCP報(bào)文。
從圖中可以發(fā)現(xiàn)一個(gè)完整的HTTP事務(wù),有鏈接的建立,請求的發(fā)送,響應(yīng)接收,斷開鏈接這四個(gè)過程,早期通過HTTP協(xié)議傳輸?shù)臄?shù)據(jù)以文本為主,一個(gè)請求可能就把所有要返回的數(shù)據(jù)取到,但是,現(xiàn)在要展現(xiàn)一張完整的頁面需要很多個(gè)請求才能完成,如圖片.JS.CSS等,如果每一個(gè)HTTP請求都需要新建并斷開一個(gè)TCP,這個(gè)開銷是完全沒有必要的。
開啟HTTP Keep-Alive之后,能復(fù)用已有的TCP鏈接,當(dāng)前一個(gè)請求已經(jīng)響應(yīng)完畢,服務(wù)器端沒有立即關(guān)閉TCP鏈接,而是等待一段時(shí)間接收瀏覽器端可能發(fā)送過來的第二個(gè)請求,通常瀏覽器在第一個(gè)請求返回之后會立即發(fā)送第二個(gè)請求,如果某一時(shí)刻只能有一個(gè)鏈接,同一個(gè)TCP鏈接處理的請求越多,開啟KeepAlive能節(jié)省的TCP建立和關(guān)閉的消耗就越多。
當(dāng)然通常會啟用多個(gè)鏈接去從服務(wù)器器上請求資源,但是開啟了Keep-Alive之后,仍然能加快資源的加載速度。HTTP/1.1之后默認(rèn)開啟Keep-Alive, 在HTTP的頭域中增加Connection選項(xiàng)。當(dāng)設(shè)置為Connection:keep-alive表示開啟,設(shè)置為Connection:close表示關(guān)閉。