http keepalive

http keepalive
在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連接。
我寫了一個腳本,方便測試

<?php
sleep(60); //爲了便於分析測試,會根據測試進行調整
echo "www.perfgeeks.com";
?>

1.當keepalive_timeout時間爲0時,即不啓用Keep-Alive時,一個tcp連接的生命週期
#tcpdump -n host 218.1.57.236 and port 80
20:36:50.792731 IP 218.1.57.236.43052 > 222.73.211.215.http: S 1520902589:1520902589(0) win 65535
20:36:50.792798 IP 222.73.211.215.http > 218.1.57.236.43052: S 290378256:290378256(0) ack 1520902590 win 5840
20:36:50.801629 IP 218.1.57.236.43052 > 222.73.211.215.http: . ack 1 win 32768

20:36:50.801838 IP 218.1.57.236.43052 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
20:36:50.801843 IP 222.73.211.215.http > 218.1.57.236.43052: . ack 797 win 59

20:37:50.803230 IP 222.73.211.215.http > 218.1.57.236.43052: P 1:287(286) ack 797 win 59
20:37:50.803289 IP 222.73.211.215.http > 218.1.57.236.43052: F 287:287(0) ack 797 win 59
20:37:50.893396 IP 218.1.57.236.43052 > 222.73.211.215.http: . ack 288 win 32625
20:37:50.894249 IP 218.1.57.236.43052 > 222.73.211.215.http: F 797:797(0) ack 288 win 32625
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
#tcpdump -n host 218.1.57.236 and port 80
21:38:05.471129 IP 218.1.57.236.54049 > 222.73.211.215.http: S 1669618600:1669618600(0) win 65535
21:38:05.471140 IP 222.73.211.215.http > 218.1.57.236.54049: S 4166993862:4166993862(0) ack 1669618601 win 5840
21:38:05.481731 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 1 win 32768
21:38:05.481976 IP 218.1.57.236.54049 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
21:38:05.481985 IP 222.73.211.215.http > 218.1.57.236.54049: . ack 797 win 59
21:38:07.483626 IP 222.73.211.215.http > 218.1.57.236.54049: P 1:326(325) ack 797 win 59
21:38:07.747614 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 326 win 32605
21:43:07.448454 IP 222.73.211.215.http > 218.1.57.236.54049: F 326:326(0) ack 797 win 59
21:43:07.560316 IP 218.1.57.236.54049 > 222.73.211.215.http: . ack 327 win 32605
21:43:11.759102 IP 218.1.57.236.54049 > 222.73.211.215.http: F 797:797(0) ack 327 win 32605
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個請求。
# tcpdump -n host 218.1.57.236 and port 80
22:43:57.102448 IP 218.1.57.236.49955 > 222.73.211.215.http: S 4009392741:4009392741(0) win 65535
22:43:57.102527 IP 222.73.211.215.http > 218.1.57.236.49955: S 4036426778:4036426778(0) ack 4009392742 win 5840
22:43:57.111337 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1 win 32768

22:43:57.111522 IP 218.1.57.236.49955 > 222.73.211.215.http: P 1:797(796) ack 1 win 32768
22:43:57.111530 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 797 win 59
22:43:59.114663 IP 222.73.211.215.http > 218.1.57.236.49955: P 1:326(325) ack 797 win 59
22:43:59.350143 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 326 win 32605

22:45:59.226102 IP 218.1.57.236.49955 > 222.73.211.215.http: P 1593:2389(796) ack 650 win 32443
22:45:59.226109 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 2389 win 83
22:46:01.227187 IP 222.73.211.215.http > 218.1.57.236.49955: P 650:974(324) ack 2389 win 83
22:46:01.450364 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 974 win 32281

22:47:57.377707 IP 218.1.57.236.49955 > 222.73.211.215.http: P 3185:3981(796) ack 1298 win 32119
22:47:57.377714 IP 222.73.211.215.http > 218.1.57.236.49955: . ack 3981 win 108
22:47:59.379496 IP 222.73.211.215.http > 218.1.57.236.49955: P 1298:1622(324) ack 3981 win 108
22:47:59.628964 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1622 win 32768

22:50:59.358537 IP 222.73.211.215.http > 218.1.57.236.49955: F 1622:1622(0) ack 3981 win 108
22:50:59.367911 IP 218.1.57.236.49955 > 222.73.211.215.http: . ack 1623 win 32768
22:50:59.686527 IP 218.1.57.236.49955 > 222.73.211.215.http: F 3981:3981(0) ack 1623 win 32768
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保鮮定時器,支持三個系統內核配置參數:
echo 1800 > /proc/sys/net/ipv4/tcp_keepalive_time
echo 15 > /proc/sys/net/ipv4/tcp_keepalive_intvl
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的利用。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章