什么是HTTP Keep-Alive呢?


在通過調試工具查看網絡請求的時候,通常在response header能看到類似下面這種:Keep-Alive: timeout=10, max=94 。那么Keep-Alive到底是什么呢?

HTTP Keep-Alive

在http早期,每個http請求都要求打開一個tpc socket連接,並且使用一次之后就斷開這個tcp連接。

使用keep-alive可以改善這種狀態,即在一次TCP連接中可以持續發送多份數據而不會斷開連接。通過使用keep-alive機制,可以減少tcp連接建立次數,也意味着可以減少TIME_WAIT狀態連接,以此提高性能和提高httpd服務器的吞吐率(更少的tcp連接意味着更少的系統內核調用,socket的accept()和close()調用)。

但是,keep-alive並不是免費的午餐,長時間的tcp連接容易導致系統資源無效占用。配置不當的keep-alive,有時比重復利用連接帶來的損失還更大。所以,正確地設置keep-alive timeout時間非常重要。

keepalvie timeout

Httpd守護進程,一般都提供了keep-alive timeout時間設置參數。比如nginx的keepalive_timeout,和Apache的KeepAliveTimeout。這個keepalive_timout時間值意味着:一個http產生的tcp連接在傳送完最后一個響應后,還需要hold住keepalive_timeout秒后,才開始關閉這個連接。

當httpd守護進程發送完一個響應后,理應馬上主動關閉相應的tcp連接,設置 keepalive_timeout后,httpd守護進程會想說:”再等等吧,看看瀏覽器還有沒有請求過來”,這一等,便是keepalive_timeout時間。如果守護進程在這個等待的時間里,一直沒有收到瀏覽發過來http請求,則關閉這個http連接。

下面寫一個腳本,方便測試:

1    sleep(60);  //為了便於分析測試,會根據測試進行調整
2    echo "www.example.com";

1. 當keepalive_timeout時間為0時,即不啟用Keep-Alive時,一個tcp連接的生命周期:

01    #tcpdump -n host 218.1.57.236 and port 80
02    20:36:50.792731 IP 218.1.57.236.43052 > 222.73.211.215.http: S 1520902589:1520902589(0) win 65535
03    20:36:50.792798 IP 222.73.211.215.http > 218.1.57.236.43052: S 290378256:290378256(0) ack 1520902590 win 5840
04    20:36:50.801629 IP 218.1.57.236.43052 > 222.73.211.215.http: . ack 1 win 32768
05     
06    20:36:50.801838 IP 218.1.57.236.43052 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
07    20:36:50.801843 IP 222.73.211.215.http > 218.1.57.236.43052: . ack 797 win 59
08     
09    20:37:50.803230 IP 222.73.211.215.http > 218.1.57.236.43052: P 1:287(286) ack 797 win 59
10    20:37:50.803289 IP 222.73.211.215.http > 218.1.57.236.43052: F 287:287(0) ack 797 win 59
11    20:37:50.893396 IP 218.1.57.236.43052 > 222.73.211.215.http: . ack 288 win 32625
12    20:37:50.894249 IP 218.1.57.236.43052 > 222.73.211.215.http: F 797:797(0) ack 288 win 32625
13    20:37:50.894252 IP 222.73.211.215.http > 218.1.57.236.43052: . ack 798 win 59
  • 第1~3行建立tcp三次握手,建立連接。用時8898μs
  • 第4~5行通過建立的連接發送第一個http請求,服務端確認收到請求。用時5μs
  • 第5~6行,可以知道腳本執行用時60s1387μs,與php腳本相符。
  • 第6、8行服務端發送http響應。發送響應用時90166μs。
  • 第7行,表明由服務端守護進程主動關閉連接。結合第6、8行,說明http響應一旦發送完畢,服務端馬上關閉這個tcp連接
  • 第7、9、10說明tcp連接順序關閉,用時90963μs。需要注意,這里socket資源並沒有立即釋放,需要等待2MSL時間(60s)后才被真正釋放。

由此可見,在沒有設置 keepalive_timeout 情況下,一個socket資源從建立到真正釋放需要經過的時間是:建立tcp連接 + 傳送http請求 + php腳本執行 + 傳送http響應 + 關閉tcp連接 + 2MSL 。(注:這里的時間只能做參考,具體的時間主要由網絡帶寬,和響應大小而定)

2. 當keepalive_timeout時間大於0時,即啟用Keep-Alive時,一個tcp連接的生命周期。為了便於分析,我們將keepalive_timeout設置為300s

01    #tcpdump -n host 218.1.57.236 and port 80
02    21:38:05.471129 IP 218.1.57.236.54049 > 222.73.211.215.http: S 1669618600:1669618600(0) win 65535
03    21:38:05.471140 IP 222.73.211.215.http > 218.1.57.236.54049: S 4166993862:4166993862(0) ack 1669618601 win 5840
04    21:38:05.481731 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 1 win 32768
05    21:38:05.481976 IP 218.1.57.236.54049 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
06    21:38:05.481985 IP 222.73.211.215.http > 218.1.57.236.54049: . ack 797 win 59
07     
08    21:38:07.483626 IP 222.73.211.215.http > 218.1.57.236.54049: P 1:326(325) ack 797 win 59
09    21:38:07.747614 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 326 win 32605
10    21:43:07.448454 IP 222.73.211.215.http > 218.1.57.236.54049: F 326:326(0) ack 797 win 59
11    21:43:07.560316 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 327 win 32605
12    21:43:11.759102 IP 218.1.57.236.54049 > 222.73.211.215.http: F 797:797(0) ack 327 win 32605
13    21:43:11.759111 IP 222.73.211.215.http > 218.1.57.236.54049: . ack 798 win 59

 

   
  • 我們先看一下,第6~8行,跟上次示例不一樣的是,服務端httpd守護進程發完響應后,沒有立即主動關閉tcp連接。
  • 第8行,結合第6行,我們可以看到,5分鍾(300s)后,服務端主動關閉這個tcp連接。這個時間,正是我們設置的keepalive_timeout的時間。
  • 由此可見,設置了keepalive_timout時間情況下,一個socket建立到釋放需要的時間是多了keepalive_timeout時間。

3. 當keepalive_timeout時間大於0,並且在同一個tcp連接發送多個http響應。這里為了便於分析,我們將keepalive_timeout設置為180s

通過這個測試,我們想弄清楚,keepalive_timeout是從第一個響應結束開啟計時,還是最后一個響應結束開啟計時。測試結果證實是后者,這里,我們每隔120s發一次請求,通過一個tcp連接發送了3個請求。

01    # tcpdump -n host 218.1.57.236 and port 80
02    22:43:57.102448 IP 218.1.57.236.49955 > 222.73.211.215.http: S 4009392741:4009392741(0) win 65535
03    22:43:57.102527 IP 222.73.211.215.http > 218.1.57.236.49955: S 4036426778:4036426778(0) ack 4009392742 win 5840
04    22:43:57.111337 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1 win 32768
05     
06    22:43:57.111522 IP 218.1.57.236.49955 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
07    22:43:57.111530 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 797 win 59
08    22:43:59.114663 IP 222.73.211.215.http > 218.1.57.236.49955: P 1:326(325) ack 797 win 59
09    22:43:59.350143 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 326 win 32605
10     
11    22:45:59.226102 IP 218.1.57.236.49955 > 222.73.211.215.http: P 1593:2389(796) ack 650 win 32443
12    22:45:59.226109 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 2389 win 83
13    22:46:01.227187 IP 222.73.211.215.http > 218.1.57.236.49955: P 650:974(324) ack 2389 win 83
14    22:46:01.450364 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 974 win 32281
15     
16    22:47:57.377707 IP 218.1.57.236.49955 > 222.73.211.215.http: P 3185:3981(796) ack 1298 win 32119
17    22:47:57.377714 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 3981 win 108
18    22:47:59.379496 IP 222.73.211.215.http > 218.1.57.236.49955: P 1298:1622(324) ack 3981 win 108
19    22:47:59.628964 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1622 win 32768
20     
21    22:50:59.358537 IP 222.73.211.215.http > 218.1.57.236.49955: F 1622:1622(0) ack 3981 win 108
22    22:50:59.367911 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1623 win 32768
23    22:50:59.686527 IP 218.1.57.236.49955 > 222.73.211.215.http: F 3981:3981(0) ack 1623 win 32768
24    22:50:59.686531 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 3982 win 108

 

  • 第一組,三個ip包表示tcp三次握手建立連接,由瀏覽器建立。
  • 第二組,發送第一次http請求並且得到響應,服務端守護進程輸出響應之后,並沒馬上主動關閉tcp連接。而是啟動keepalive_timout計時。
  • 第三組,2分鍾后,發送第二次http請求並且得到響應,同樣服務端守護進程也沒有馬上主動關閉tcp連接,重新啟動keepalive_timout計時。
  • 第四組,又2分鍾后,發送了第三次http請求並且得到響應。服務器守護進程依然沒有主動關地閉tcp連接(距第一次http響應有4分鍾了,大於keepalive_timeout值),而是重新啟動了keepalive_timout計時。
  • 第五組,跟最后一個響應keepalive_timeout(180s)內,守護進程再沒有收到請求。計時結束,服務端守護進程主動關閉連接。4次揮手后,服務端進入TIME_WAIT狀態。

這說明,當設定了keepalive_timeout,一個socket由建立到釋放,需要時間是:tcp建立 + (最后一個響應時間 – 第一個請求時間) + tcp關閉 + 2MSL。紅色加粗表示每一次請求發送時間、每一次請求腳本執行時間、每一次響應發送時間,還有兩兩請求相隔時間。進一步測試,正在關閉或者TIME_WAIT狀態的tcp連接,不能傳輸http請求和響應。即,當一個連接結束keepalive_timeout計時,服務端守護進程發送第一個FIN標志ip包后,該連接不能再使用了。

http keep-alive與tcp keep-alive

http keep-alive與tcp keep-alive,不是同一回事,意圖不一樣。http keep-alive是為了讓tcp活得更久一點,以便在同一個連接上傳送多個http,提高socket的效率。而tcp keep-alive是TCP的一種檢測TCP連接狀況的保鮮機制。tcp keep-alive保鮮定時器,支持三個系統內核配置參數:

1    echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
2    echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
3    echo 5 > /proc/sys/net/ipv4/tcp_keepalive_probes
 

keepalive是TCP保鮮定時器,當網絡兩端建立了TCP連接之后,閑置idle(雙方沒有任何數據流發送往來)了tcp_keepalive_time后,服務器內核就會嘗試向客戶端發送偵測包,來判斷TCP連接狀況(有可能客戶端崩潰、強制關閉了應用、主機不可達等等)。如果沒有收到對方的回答(ack包),則會在 tcp_keepalive_intvl后再次嘗試發送偵測包,直到收到對對方的ack,如果一直沒有收到對方的ack,一共會嘗試 tcp_keepalive_probes次,每次的間隔時間在這里分別是15s, 30s, 45s, 60s, 75s。如果嘗試tcp_keepalive_probes,依然沒有收到對方的ack包,則會丟棄該TCP連接。TCP連接默認閑置時間是2小時,一般設置為30分鍾足夠了。

也就是說,僅當nginx的keepalive_timeout值設置高於tcp_keepalive_time,並且距此tcp連接傳輸的最后一個http響應,經過了tcp_keepalive_time時間之后,操作系統才會發送偵測包來決定是否要丟棄這個TCP連接。一般不會出現這種情況,除非你需要這樣做。

keep-alive與TIME_WAIT

使用http keep-alvie,可以減少服務端TIME_WAIT數量(因為由服務端httpd守護進程主動關閉連接)。道理很簡單,相較而言,啟用keep-alive,建立的tcp連接更少了,自然要被關閉的tcp連接也相應更少了。

最后

我想用一張示意圖片來說明使用啟用keepalive的不同。另外,http keepalive是客戶端瀏覽器與服務端httpd守護進程協作的結果,所以,我們另外安排篇幅介紹不同瀏覽器的各種情況對keepalive的利用。

 
Keep-Alive模式,客戶端如何判斷請求所得到的響應數據已經接收完成(或者說如何知道服務器已經發生完了數據)?
1. 使用消息首部字段Conent-Length

故名思意,Conent-Length表示實體內容長度,客戶端(服務器)可以根據這個值來判斷數據是否接收完成。但是如果消息中沒有Conent-Length,那該如何來判斷呢?又在什么情況下會沒有Conent-Length呢?請繼續往下看……


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM