解決Tengine健康檢查引發的TIME_WAIT堆積問題

簡介: 解決Tengine健康檢查引發的TIME_WAIT堆積問題nginx

 

一. 問題背景

「服務上雲後,咱們的TCP端口基本上都處於TIME_WAIT的狀態」、「這個問題在線下機房不曾發生過」 這是客戶提交問題的描述。後端


客戶環境是自建Tengine做爲7層反向代理,後端接約1.8萬臺NGINX。Tengine上雲以後,在服務器上發現大量的TIME_WAIT狀態的TCP socket;因爲後端較多,潛在可能影響業務可用性。用戶對比以前的經驗比較擔憂是否多是接入阿里雲以後致使,因此但願咱們對此進行詳細的分析。服務器


注:TIME_WAIT狀態的監聽帶來的問題在於主機沒法爲往外部的鏈接請求分配動態端口。此時,能夠配置net.ipv4.ip_local_port_range,增長其端口選擇範圍(能夠考慮 5000 - 65535),但依然存在 2 MSL 時間內被用完的可能。網絡

二. TIME_WAIT緣由分析

首先,若是咱們從新回顧下TCP狀態機就能知道,TIME_WAIT狀態的端口僅出如今主動關閉鏈接的一方(跟這一方是客戶端或者是服務器端無關)。當TCP協議棧進行鏈接關閉請求時,只有【主動關閉鏈接方】會進入TIME_WAIT狀態。而客戶的顧慮也在這裏。socket


一方面,健康檢查使用 HTTP1.0 是短鏈接,邏輯上應該由後端NGINX服務器主動關閉鏈接,多數TIME_WAIT應該出如今NGINX側。tcp


另外一方面,咱們也經過抓包確認了多數鏈接關閉的第一個FIN請求均由後端NGINX服務器發起,理論上,Tengine服務器的socket 應該直接進入CLOSED狀態而不會有這麼多的TIME_WAIT 。函數


抓包狀況以下,咱們根據Tengine上是TIME_WAIT的socket端口號,進行了過濾。性能

 

圖1:一次HTTP請求交互過程


雖然上面的抓包結果顯示當前 Tengine 行爲看起來確實很奇怪,但實際上經過分析,此類情形在邏輯上仍是存在的。爲了解釋這個行爲,咱們首先應該瞭解:經過tcpdump抓到的網絡數據包,是該數據包在該主機上收發的「結果」。儘管在抓包上看,Tengine側看起來是【被動接收方】角色,但在操做系統中,這個socket是否屬於主動關閉的決定因素在於操做系統內TCP協議棧如何處理這個socket。阿里雲


針對這個抓包分析,咱們的結論就是:可能這裏存在一種競爭條件(Race Condition)。若是操做系統關閉socket和收到對方發過來的FIN同時發生,那麼決定這個socket進入TIME_WAIT仍是CLOSED狀態決定於 主動關閉請求(Tengine 程序針對 socket 調用 close 操做系統函數)和 被動關閉請求(操做系統內核線程收到 FIN 後調用的 tcp_v4_do_rcv 處理函數)哪一個先發生 。spa


不少狀況下,網絡時延,CPU處理能力等各類環境因素不一樣,可能帶來不一樣的結果。例如,而因爲線下環境時延低,被動關閉可能最早發生;自從服務上雲以後,Tengine跟後端Nginx的時延由於距離的緣由被拉長了,所以Tengine主動關閉的狀況更早進行,等等,致使了雲上雲下不一致的狀況。


但是,若是目前的行爲看起來都是符合協議標準的狀況,那麼如何正面解決這個問題就變得比較棘手了。咱們沒法經過下降Tengine所在的主機性能來延緩主動鏈接關閉請求,也沒法下降由於物理距離而存在的時延消耗加快 FIN 請求的收取。這種狀況下,咱們會建議經過調整系統配置來緩解問題。


注:如今的Linux系統有不少方法均可以快速緩解該問題,例如,
a) 在timestamps啓用的狀況下,配置tw_reuse。
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_timestamps = 1
b) 配置 max_tw_buckets
net.ipv4.tcp_max_tw_buckets = 5000
缺點就是會往syslog裏寫: time wait bucket table overflow.

因爲用戶使用自建 Tengine ,且用戶不肯意進行 TIME_WAIT 的強制清理,所以咱們考慮經過Tengine的代碼分析看看是否有機會在不改動 Tengine 源碼的狀況下,改變 Tengine 行爲來避免socket被Tengine主動關閉。
Tengine version: Tengine/2.3.1
NGINX version: nginx/1.16.0

一、 Tengine code analysis

從以前的抓包,咱們能夠看出來多數的TIME_WAIT socket是爲了後端健康檢查而建立的,所以咱們主要關注 Tengine的健康檢查行爲,如下是從ngx_http_upstream_check_module 的開源代碼中摘抄出來的關於socket清理的函數。

 

圖2:Tengine 健康檢查完成後清理socket過程


從這段邏輯中,咱們能夠看到,若是知足如下任一條件時,Tengine會在收到數據包以後直接關閉鏈接。

  • c->error != 0
  • cf->need_keepalive = false
  • c->requests > ucscf->check_keepalive_requ

 

圖3: Tengine 中真正完成socket關閉的函數


這裏,若是咱們讓以上的條件變成不知足,那麼就有可能讓Tengine所在的操做系統先處理被動關閉請求,進行socket清理,進入CLOSED狀態,由於從HTTP1.0的協議上來講,NGINX服務器這一方必定會主動關閉鏈接。

二、解決方法

通常狀況下,咱們對於TIME_WAIT的鏈接無需太過關心,通常2MSL(默認60s) 以後,系統自動釋放。若是須要減小,能夠考慮長連接模式,或者調整參數。
該case中,客戶對協議比較瞭解,但對於強制釋放TIME_WAIT 仍有擔憂;同時因爲後端存在1.8萬臺主機,長鏈接模式帶來的開銷更是沒法承受。
所以,咱們根據以前的代碼分析,經過梳理代碼裏面的邏輯,推薦客戶如下健康檢查配置,
check interval=5000 rise=2 fall=2 timeout=3000 type=http default_down=false;
check_http_send "HEAD / HTTP/1.0\r\n\r\n";
check_keepalive_requests 2
check_http_expect_alive http_2xx http_3xx;
理由很簡單,咱們須要讓以前提到的三個條件不知足。在代碼中,咱們不考慮 error 狀況,而need_keepalive 在代碼中默認 enable (若是不是,能夠經過配置調整),所以需確保check_keepalive_requests大於1便可進入Tengine的KEEPALIVE邏輯,避免Tengine主動關閉鏈接。

 

圖4:Tengine健康檢查參考配置

由於使用HTTP1.0的HEAD方法,後端服務器收到後會主動關閉鏈接,所以Tengine建立的socket進入CLOSED狀態,避免進入TIME_WAIT而佔用動態端口資源。

做者:SRE團隊技術小編-小凌

原文連接

本文爲阿里雲原創內容,未經容許不得轉載

相關文章
相關標籤/搜索