1. 程式人生 > >client,server,nginx 在使用keepAlive裏要保持一致,否則起不到效果

client,server,nginx 在使用keepAlive裏要保持一致,否則起不到效果

返回 tcp三次握手 現在 tom 應用 sock keepal 一個數 代碼

為什麽要有KeepAlive?

在談KeepAlive之前,我們先來了解下簡單TCP知識(知識很簡單,高手直接忽略)。首先要明確的是在TCP層是沒有“請求”一說的,經常聽到在TCP層發送一個請求,這種說法是錯誤的。

TCP是一種通信的方式,“請求”一詞是事務上的概念,HTTP協議是一種事務協議,如果說發送一個HTTP請求,這種說法就沒有問題。也經常聽到面試官反饋有些面試運維的同學,基本的TCP三次握手的概念不清楚,面試官問TCP是如何建立鏈接,面試者上來就說,假如我是客戶端我發送一個請求給服務端,服務端發送一個請求給我。。。


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
TCPKEEPIDLE: 覆蓋 tcpkeepalivetime
TCPKEEPINTVL: 覆蓋 tcpkeepalive_intvl

舉個例子,以我的系統默認設置為例,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(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通常的使用方式如下:
技術分享

從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,相關的代碼如下:

技術分享

從代碼的上下文我發現TCP KeepAlive可以配置,所以我接著查找通過哪個選項配置,最後發現listen指令的so_keepalive選項能對TCP socket進行KeepAlive的配置。

技術分享

http://www.bubuko.com/infodetail-260176.html




KeepAlive,這次看下Nginx加Tomcat做反向代理這種典型場景下的KeepAlive配置與否的影響。以下圖中的數據只能做個定性的參考,具體要根據實際業務測試。

技術分享

技術分享

技術分享

技術分享

技術分享

技術分享

  • 顯然keepalive需要client和sever同時支持才生效;
  • 未使用keepalive(無論是客戶端還是服務端不支持),服務端會主動關閉TCP連接,存在大量的TIME_WAI;
  • 是否使用keepalive比較復雜,並不是單純的一個http頭決定的;
  • Nginx似乎跟upstream之間維持著一個長連接池,所以很少會看到TIME_WAIT,都處於ESTABLISHED狀態。

Nginx有關KeepAlive的配置有兩處:

一處是http節點下的keepalive_timeout,這個設置的是跟client(圖中downstream)的連接超時時間;還有一處是upstream中配置的keepalive,註意這個單位是數量不是時間。

client,server,nginx 在使用keepAlive裏要保持一致,否則起不到效果