1.TCP keepalive
1.1.概念
A keepalive (KA) is a message sent by one device to another to check that the link between the two is operating, or to prevent the link from being broken.
——From wiki
TCP keepalive是TCP的保活定時器。通俗地說,就是TCP有一個定時任務做倒計時,超時后會觸發任務,內容是發送一個探測報文給對端,用來判斷對端是否存活。(想到一個橋段:“如果2小時后沒等到我的消息,你們就快跑”)
1.2.作用
正如概念中說的,用于探測對端是否存活,從而防止連接處于“半打開”狀態。
所謂半打開,就是網絡連接的雙端中,有一端已經斷開,而另一端仍然處于連接狀態。
1.3.機制
(圖一)TCP keepalive 流程圖
建立連接的雙端在通信的同時,存在一個定時任務A,每當傳輸完一個報文,都會重置定時任務A。如果在定時任務的時限tcp_keepalive_time內不再有新的報文傳輸,便會觸發定時任務A,向對端發送存活探測報文。根據響應報文的不同情況,有不同的操作分支,如上圖所示。
定時任務B會被循環執行,具體邏輯是:定時任務A的探測報文沒有得到響應報文,開始執行定時任務B。任務B的內容同樣是發送探測報文,但不同的是,B會被執行tcp_keepalive_probes次,時間間隔為tcp_keepalive_intvl。B的探測報文同樣也是在收到響應報文后,重置定時任務A,維持連接狀態。
上文提到的三個參數存在于系統文件中,具體路徑如下:
/proc/sys.NET/ipv4/tcp_keepalive_time
/proc/sys/net/ipv4/tcp_keepalive_intvl
/proc/sys/net/ipv4/tcp_keepalive_probes
通信雙端都存在一個文件作為數據緩沖區,對端發送給本地當前端口的數據都會緩沖在這個文件中。上文中講的“斷開連接”就是關閉這個文件,關閉后所有發送到當前端口的數據將無法存儲到緩沖區,即數據被丟棄了。
通過指令lsof -i :8080,8080改成你的端口號,便能看到這個緩沖區文件。
2.HTTP keepalive
2.1.概念
HTTP persistent connection, also called HTTP keep-alive, or HTTP connection reuse, is the idea of using a single TCP connection to send and receive multiple HTTP requests/responses, as opposed to opening a new connection for every single request/response pair. The newer HTTP/2 protocol uses the same idea and takes it further to allow multiple concurrent requests/responses to be multiplexed over a single connection.
——From wiki
HTTP keepalive指的是持久連接,強調復用TCP連接。(類似場景:掛電話之前總會問句,沒啥事就先掛了,延長通話時長來確認沒有新話題)
2.2.作用
延長TCP連接的時長,一次TCP連接從創建到關閉期間能傳輸更多的數據。
2.3.機制
(圖二)HTTP keepalive 流程圖
通信連接的雙端在通信的同時,存在一個HTTP層面的keepalive定時任務。當客戶端發起Request,并且接收到Response之后,觸發定時任務。定時任務會開始計時,達到keepalive的時間距離后,關閉連接。如果在計時期間,客戶端再次發起Request,并且接收到Response,定時任務會被重置,從頭計時。
圖二用Python/ target=_blank class=infotextkey>Python的socket庫為示例進行說明,在HTTP的“請求-響應”過程中,HTTP keepalive(或者稱為HTTP持久連接)在底層是如何作用于連接釋放流程,從而延長連接時長的。
為什么不用Python的requests庫來舉例說明?requests底層也是socket連接管理,不同的是requests支持HTTP協議,可以解析出HTTP各部分信息;socket僅僅是從文件緩沖區讀取二進制流。同樣地,各種Web框架中的Request和Response對象的內部仍然是socket連接管理,只提socket可以排除很多干擾信息。
服務端HTTP keepalive超時后的數據丟棄的說明。剛入門的同學可能也會像我一樣感到疑惑:服務端keepalive超時后再收到數據就會丟棄,那么服務端后續還怎么接收端口的數據?
這就不得不提到服務端的fork模型了:服務端主進程監聽端口,當數據到來時便交給子進程來處理,主進程繼續循環監聽端口。
具體地說,當數據到來時,主進程先創建新的socket連接句柄(本質就是生成了socket文件描述符落在磁盤上,端口數據會存儲在該文件中緩沖),隨后fork出子進程;主進程關閉新的socket句柄,子進程則維持socket句柄的連接(當一個socket句柄在所有進程中都被close之后才會開始TCP四次揮手);此后,子進程接管了與客戶端的通信。
正如(圖三)的例子,主進程會fork出很多子進程,A和B分別對接的是不同客戶端發來的請求,socket文件描述符a不會影響b的數據讀寫。
(圖三)fork模型下傳遞socket句柄的過程
結論是,服務端與外界建立的每一個socket連接,都有獨立的文件描述符和獨立的子進程與客戶端通信。服務端斷開連接是指關閉了某個文件描述符的讀寫,并非關閉了整個端口的數據往來,不影響其他的socket連接之間通信。至于丟棄,就是說外界如果還有發往這個socket文件描述符的數據被丟棄,因為這個文件描述符已經禁止寫入,自然地數據便無法落地。
3.兩者之間的關系
TCP keepalive更像是保障系統有序工作的兜底機制,確保系統最終能收回半打開的socket連接,否則長期運行后無法再接收更多的請求(系統的socket最大連接數限制)。
HTTP keepalive則是應用層的騷操作,使得服務端的應用程序能自主決定socket的釋放,因為TCP keepalive的倒計時默認值很長,web服務的某次連接通常不需要等待那么久。說直白點,就是TCP有一個計時器,HTTP也可以自己搞個計時器,如果HTTP的計時器先超時,同樣有權利讓TCP進入四次揮手流程。
在某個數據包傳輸后,兩個keepalive的定時任務同時存在且一起進入倒計時狀態,一個是系統內核TCP相關代碼的程序,另一個是高級編程語言(Python/JAVA/Go等)Web框架代碼的程序,他們一起運行并不沖突。
4.念經
HTTP keepalive是應用層的東西,在上生產時對外提供服務的應用程序都會有keepalive參數,例如Gunicorn的keepalive、Nginx的keepalive_timeout。通過這個參數,我們能在更高級的層面控制等待下一個數據的時長。
還有,如果同一臺服務器有N個Web服務,TCP keepalive參數是全局生效,眾口難調。
如果你的網絡結構是類似client-nginx-web server,那么你就要同時考慮nginx和web server的keepalive參數大小搭配的問題,此處引用Gunicorn對keepalive參數的使用建議:
Generally set in the 1-5 seconds range for servers with direct connection to the client (e.g. when you don’t have separate load balancer). When Gunicorn is deployed behind a load balancer, it often makes sense to set this to a higher value.
假設web等待時間比nginx短很多,client-nginx的連接還在,nginx-web就已經斷開了,web就會錯過一些數據,對于客戶來說好端端的我拿不到結果是無法容忍的。因此最好是和nginx的等待時間協調好,不要相差太多(不要太短,也不要長很多)。
關于不要太長,多說一句。如果等待很久,web服務會累積維持非常多的連接,這樣子新的請求無法打進來,正在維持的連接不見得利用率很高(可能客戶端的代碼在打斷點、可能客戶端早就close)。結果就是服務端netstat顯示一堆連接,新的請求全都被掛起甚至丟棄。
原文鏈接:
https://mp.weixin.qq.com/s/duDNJGbQtr05Y4S9_abveQ