TCP的keepalive&HTTP的keep-alive

最近工做中遇到一個問題,想把它記錄下來,場景是這樣的:html

 

nginx_lvs_client

 

從上圖能夠看出,用戶經過Client訪問的是LVS的VIP, VIP後端掛載的RealServer是Nginx服務器。 Client能夠是瀏覽器也能夠是一個客戶端程序。通常狀況下,這種架構不會出現問題,可是若是Client端把請求發送給Nginx,Nginx的後端須要一段時間才能返回結果,超過1分30秒就會有問題,使用LVS做爲負載均衡設備看到的現象就是1分30秒以後, Client和Nginx連接被斷開,沒有數據返回。緣由是LVS默認保持TCP的Session爲90s,超過90s沒有TCP報文在連接上傳輸,LVS就會給兩端發送RESET報文斷開連接。LVS這麼作的緣由相信你們都知道一二,我所知道的緣由主要有兩點:nginx

1.節省負載均衡設備資源,每個TCP/UDP的連接都會在負載均衡設備上建立一個Session的結構, 連接若是一直不斷開,這種Session結構信息最終會消耗掉全部的資源,因此必須釋放掉。 2.另外釋放掉能保護後端的資源,若是攻擊者經過空連接,連接到Nginx上,若是Nginx沒有作合適 的保護,Nginx會由於連接數過多而沒法提供服務。

這種問題不僅是在LVS上有,以前在商用負載均衡設備F5上遇到過一樣的問題,F5的Session斷開方式和LVS有點區別,F5不會主動發送RESET給連接的兩端,Session消失以後,當連接中一方再次發送報文時會接收到F5的RESET, 以後的現象是再次發送報文的一端TCP連接狀態已經斷開,而另一端卻仍是ESTABLISH狀態。git

 

知道是負載均衡設備緣由以後,第一反應就是經過開啓KeepAlive來解決。到此這個問題應該是結束了,可是我發現過一段時間總又有人提起KeepAlive的問題,甚至發現因爲KeepAlive的理解不正確浪費了不少資源,本來能使用LVS的應用放在了公網下沉區,或者換成了商用F5設備(F5設備的Session斷開時間要長一點,默認應該是5分鐘)。因此我決定把我知道的KeepAlive知識點寫篇博客分享出來。github

 

爲何要有KeepAlive?

在談KeepAlive以前,咱們先來了解下簡單TCP知識(知識很簡單,高手直接忽略)。首先要明確的是在TCP層是沒有「請求」一說的,常常聽到在TCP層發送一個請求,這種說法是錯誤的。TCP是一種通訊的方式,「請求」一詞是事務上的概念,HTTP協議是一種事務協議,若是說發送一個HTTP請求,這種說法就沒有問題。也常常聽到面試官反饋有些面試運維的同窗,基本的TCP三次握手的概念不清楚,面試官問TCP是如何創建連接,面試者上來就說,假如我是客戶端我發送一個請求給服務端,服務端發送一個請求給我。。。這種一聽就知道對TCP基本概念不清楚。下面是我經過wireshark抓取的一個TCP創建握手的過程。(命令行基本上用TCPdump,後面咱們還會用這張圖說明問題):面試

 

tcp_session_create
如今我看只要看前3行,這就是TCP三次握手的完整創建過程,第一個報文SYN從發起方發出,第二個報文SYN,ACK是從被鏈接方發出,第三個報文ACK確認對方的SYN,ACK已經收到,以下圖:後端

tcp_syn_synack_ack

可是數據實際上並無傳輸,請求是有數據的,第四個報文才是數據傳輸開始的過程,細心的讀者應該可以發現wireshark把第四個報文解析成HTTP協議,HTTP協議的GET方法和URI也解析出來,因此說TCP層是沒有請求的概念,HTTP協議是事務性協議纔有請求的概念,TCP報文承載HTTP協議的請求(Request)和響應(Response)。瀏覽器

 

如今纔是開始說明爲何要有KeepAlive。連接創建以後,若是應用程序或者上層協議一直不發送數據,或者隔很長時間才發送一次數據,當連接好久沒有數據報文傳輸時如何去肯定對方還在線,究竟是掉線了仍是確實沒有數據傳輸,連接還需不須要保持,這種狀況在TCP協議設計中是須要考慮到的。TCP協議經過一種巧妙的方式去解決這個問題,當超過一段時間以後,TCP自動發送一個數據爲空的報文給對方,若是對方迴應了這個報文,說明對方還在線,連接能夠繼續保持,若是對方沒有報文返回,而且重試了屢次以後則認爲連接丟失,沒有必要保持連接。服務器

 

如何開啓KeepAlive

KeepAlive並非默認開啓的,在Linux系統上沒有一個全局的選項去開啓TCP的KeepAlive。須要開啓KeepAlive的應用必須在TCP的socket中單獨開啓。Linux Kernel有三個選項影響到KeepAlive的行爲:
1.net.ipv4.tcpkeepaliveintvl = 75
2.net.ipv4.tcpkeepaliveprobes = 9
3.net.ipv4.tcpkeepalivetime = 7200
tcpkeepalivetime的單位是秒,表示TCP連接在多少秒以後沒有數據報文傳輸啓動探測報文; tcpkeepaliveintvl單位是也秒,表示前一個探測報文和後一個探測報文之間的時間間隔,tcpkeepaliveprobes表示探測的次數。網絡

 

TCP socket也有三個選項和內核對應,經過setsockopt系統調用針對單獨的socket進行設置:
TCPKEEPCNT: 覆蓋 tcpkeepaliveprobes
TCP
KEEPIDLE: 覆蓋 tcpkeepalivetime
TCPKEEPINTVL: 覆蓋 tcpkeepalive_intvlsession

 

舉個例子,以個人系統默認設置爲例,kernel默認設置的tcpkeepalivetime是7200s, 若是我在應用程序中針對socket開啓了KeepAlive,而後設置的TCP_KEEPIDLE爲60,那麼TCP協議棧在發現TCP連接空閒了60s沒有數據傳輸的時候就會發送第一個探測報文。

 

TCP KeepAlive和HTTP的Keep-Alive是同樣的嗎?

估計不少人乍看下這個問題才發現其實常常說的KeepAlive不是這麼回事,實際上在沒有特指是TCP仍是HTTP層的KeepAlive,不能混爲一談。TCP的KeepAlive和HTTP的Keep-Alive是徹底不一樣的概念。TCP層的KeepAlive上面已經解釋過了。 HTTP層的Keep-Alive是什麼概念呢? 在講述TCP連接創建的時候,我畫了一張三次握手的示意圖,TCP在創建連接以後, HTTP協議使用TCP傳輸HTTP協議的請求(Request)和響應(Response)數據,一次完整的HTTP事務以下圖:

http_session

各位看官請注意,這張圖我簡化了HTTP(Req)和HTTP(Resp),實際上的請求和響應須要多個TCP報文。從圖中能夠發現一個完整的HTTP事務,有連接的創建,請求的發送,響應接收,斷開連接這四個過程,早期經過HTTP協議傳輸的數據以文本爲主,一個請求可能就把全部要返回的數據取到,可是,如今要展示一張完整的頁面須要不少個請求才能完成,如圖片,JS,CSS等,若是每個HTTP請求都須要新建並斷開一個TCP,這個開銷是徹底沒有必要的,開啓HTTP Keep-Alive以後,能複用已有的TCP連接,當前一個請求已經響應完畢,服務器端沒有當即關閉TCP連接,而是等待一段時間接收瀏覽器端可能發送過來的第二個請求,一般瀏覽器在第一個請求返回以後會當即發送第二個請求,若是某一時刻只能有一個連接,同一個TCP連接處理的請求越多,開啓KeepAlive能節省的TCP創建和關閉的消耗就越多。固然一般會啓用多個連接去從服務器器上請求資源,可是開啓了Keep-Alive以後,仍然能加快資源的加載速度。HTTP/1.1以後默認開啓Keep-Alive, 在HTTP的頭域中增長Connection選項。當設置爲Connection:keep-alive表示開啓,設置爲Connection:close表示關閉。實際上HTTP的KeepAlive寫法是Keep-Alive,跟TCP的KeepAlive寫法上也有不一樣。因此TCP KeepAlive和HTTP的Keep-Alive不是同一回事情。

 

Nginx的TCP KeepAlive如何設置

開篇提到我最近遇到的問題,Client發送一個請求到Nginx服務端,服務端須要通過一段時間的計算纔會返回, 時間超過了LVS Session保持的90s,在服務端使用Tcpdump抓包,本地經過wireshark分析顯示的結果如第二副圖所示,第5條報文和最後一條報文之間的時間戳大概差了90s。在肯定是LVS的Session保持時間到期的問題以後,我開始在尋找Nginx的TCP KeepAlive如何設置,最早找到的選項是keepalivetimeout,從同事那裏得知keepalivetimeout的用法是當keepalivetimeout的值爲0時表示關閉keepalive,當keepalivetimeout的值爲一個正整數值時表示連接保持多少秒,因而把keepalivetimeout設置成75s,可是實際的測試結果代表並不生效。顯然keepalivetimeout不能解決TCP層面的KeepAlive問題,實際上Nginx涉及到keepalive的選項還很多,Nginx一般的使用方式以下:

nginx

從TCP層面Nginx不只要和Client關心KeepAlive,並且還要和Upstream關心KeepAlive, 同時從HTTP協議層面,Nginx須要和Client關心Keep-Alive,若是Upstream使用的HTTP協議,還要關心和Upstream的Keep-Alive,總而言之,還比較複雜。因此搞清楚TCP層的KeepAlive和HTTP的Keep-Alive以後,就不會對於Nginx的KeepAlive設置錯。我當時解決這個問題時候不肯定Nginx有配置TCP keepAlive的選項,因而我打開Ngnix的源代碼,在源代碼裏面搜索TCP_KEEPIDLE,相關的代碼以下:

又見KeepAlive


從代碼的上下文我發現TCP KeepAlive能夠配置,因此我接着查找經過哪一個選項配置,最後發現listen指令的so_keepalive選項能對TCP socket進行KeepAlive的配置。

又見KeepAlive

以上三個參數只能使用一個,不能同時使用, 好比sokeepalive=on, sokeepalive=off或者sokeepalive=30s::(表示等待30s沒有數據報文發送探測報文)。經過設置listen 80,sokeepalive=60s::以後成功解決Nginx在LVS保持長連接的問題,避免了使用其餘高成本的方案。在商用負載設備上若是遇到相似的問題一樣也能夠經過這種方式解決。

 

參考資料

《TCP/IP協議詳解VOL1》--強烈建議對於網絡基本知識不清楚同窗有空去看下。

http://tldp.org/HOWTO/html_single/TCP-Keepalive-HOWTO/#overview

http://nginx.org/en/docs/http/ngx_http_core_module.html

Nginx Source code: https://github.com/alibaba/tengine

相關文章
相關標籤/搜索