生產環境 Nginx 后端服務大量 TIME-WAIT , 該怎么辦?
遇到這樣的生產環境難題,小伙伴們非常頭疼。
更為頭疼的是,這個也是一道場景的面試題。之前有小伙伴反應過,他面試科大訊飛的時候,遇到了這道題目:
生產環境 Nginx 后端服務大量 TIME-WAIT 的解決步驟
這里小編給大家做一下系統化、體系化的梳理,使得大家可以充分展示一下大家雄厚的 “技術肌肉”,讓面試官愛到 “不能自已、口水直流”。
基礎知識
TIME_WAIT是什么?
在構建TCP客戶端服務器系統時,很容易犯簡單的錯誤,這些錯誤會嚴重限制可伸縮性。
其中一個錯誤是沒有考慮到狀態。
為什么TIME_WAIT存在,它可能導致的問題,如何解決它,以及什么時候不應該。
TIME_WAIT是 TCP 狀態轉換圖中經常被誤解的狀態。
這是某些套接字可以進入并保持相對較長時間的狀態,如果您有足夠的套接字,那么您創建新套接字連接的能力可能會受到影響,這可能會影響客戶端服務器系統的可伸縮性。
關于套接字如何以及為什么首先進入TIME_WAIT,經常存在一些誤解,不應該有,這并不神奇。從下面的TCP狀態轉換圖中可以看出TIME_WAIT,是TCP客戶端通常最終處于的最終狀態。
雖然狀態轉換圖顯示TIME_WAIT為客戶端的最終狀態,但它不一定是客戶端TIME_WAIT。事實上,最終狀態是啟動“主動關閉”的對等端最終進入,這可以是客戶端或服務器。那么,發布“主動關閉”是什么意思?
如果TCP對等方是第一個呼叫Close()連接的對等方,則TCP對等方會啟動“主動關閉” 。
在許多協議和客戶端/服務器設計中,這是客戶端。
在HTTP和FTP服務器中,這通常是服務器。
導致同伴結束的事件的實際順序TIME_WAIT如下。
現在我們知道套接字是如何結束TIME_WAIT的,理解為什么這個狀態存在以及它為什么會成為一個潛在的問題是有用的。
TIME_WAIT通常也被稱為2MSL等待狀態。
這是因為轉換到的套接字在此期間的持續時間TIME_WAIT為2 x最大段壽命。
MSL是任何段的最大時間量,對于構成TCP協議一部分的數據報的所有意圖和目的而言,在丟棄之前,網絡可以在網絡上保持有效。這個時間限制最終以用于傳輸TCP段的IP數據報中的TTL字段為界。
不同的實現為MSL選擇不同的值,常用值為30秒,1分鐘或2分鐘。
RFC 793指定MSL為2分鐘,Windows系統默認為此值,但可以使用TcpTimedWaitDelay注冊表設置進行調整。
TIME_WAIT可能影響系統可伸縮性 的原因是,TCP連接中一個完全關閉的套接字將保持TIME_WAIT約4分鐘的狀態。
如果許多連接正在快速打開和關閉,那么套接字TIME_WAIT可能會開始累積在系統上; 您可以TIME_WAIT使用netstat查看套接字。一次可以建立有限數量的套接字連接,限制此數量的其中一個因素是可用本地端口的數量。
如果TIME_WAIT插入太多的套接字,您會發現很難建立新的出站連接,因為缺少可用于新連接的本地端口。
但為什么TIME_WAIT存在呢?有兩個原因需要TIME_WAIT。
首先是防止一個連接的延遲段被誤解為后續連接的一部分。丟棄在連接處于2MSL等待狀態時到達的任何段。
在上圖中,我們有兩個從終點1到終點2的連接。每個終點的地址和端口在每個連接中都是相同的。第一個連接終止于由端點2啟動的主動關閉。如果端點2沒有保持TIME_WAIT足夠長的時間以確保來自先前連接的所有分段已經失效,則延遲的分段(具有適當的序列號)可以是誤認為是第二個連接的一部分...
請注意,延遲片段很可能會導致這樣的問題。首先,每個端點的地址和端口需要相同; 這通常不太可能,因為操作系統通常從短暫端口范圍為您選擇客戶端端口,從而在連接之間進行更改。其次,延遲段的序列號需要在新連接中有效,這也是不太可能的。但是,如果出現這兩種情況,TIME_WAIT則會阻止新連接的數據被破壞。
需要TIME_WAIT第二個原因是可靠地實施TCP的全雙工連接終端。
如果ACK從終點2開始的終點被丟棄,則終點1將重新發送終點FIN。如果連接已經過渡到CLOSED終點2,那么唯一可能的應答是發送一個,RST因為重發FIN是意外的。即使所有數據傳輸正確,這也會導致終點1接收到錯誤。
不幸的是,一些操作系統實現的方式TIME_WAIT似乎有點幼稚。
只有TIME_WAIT通過阻塞才能提供保護的連接與需要的socket完全匹配TIME_WAIT。這意味著由客戶端地址,客戶端端口,服務器地址和服務器端口標識的連接。但是,某些操作系統會施加更嚴格的限制,并且阻止本地端口號被重新使用,而該端口號包含在連接中TIME_WAIT。如果有足夠的套接字結束,TIME_WAIT則不能建立新的出站連接,因為沒有剩余本地端口分配給新連接。
Windows不會執行此操作,只會阻止與其中的連接完全匹配的出站連接TIME_WAIT。
入站連接受影響較小TIME_WAIT。
雖然由服務器主動關閉的TIME_WAIT連接與客戶端連接完全一樣,但是服務器正在偵聽的本地端口不會阻止其成為新的入站連接的一部分。在Windows上,服務器正在監聽的眾所周知的端口可以構成隨后接受的連接的一部分,并且如果從遠程地址和端口建立了新的連接,該連接當前構成TIME_WAIT該本地地址和端口的連接的一部分,則只要新序列號大于當前連接的最終序列號,就允許連接TIME_WAIT。然而,TIME_WAIT在服務器上累積可能會影響性能和資源使用,因為TIME_WAIT最終需要超時的連接需要進行一些工作,直到TIME_WAIT狀態結束,連接仍占用(少量)服務器資源。
鑒于TIME_WAIT由于本地端口號耗盡而影響出站連接的建立,并且這些連接通常使用由臨時端口范圍由操作系統自動分配的本地端口,因此您可以做的第一件事情是確保你正在使用一個體面的短暫端口范圍。在Windows上,您可以通過調整MaxUserPort注冊表設置來完成此操作。請注意,默認情況下,許多Windows系統的臨時端口范圍大約為4000,這對于許多客戶端服務器系統來說可能太低。
雖然有可能縮短套接字在TIME_WAIT這方面的花費時間通常不會有幫助。鑒于TIME_WAIT當許多連接建立并且主動關閉時,這只是一個問題,調整2MSL等待周期通常會導致在給定時間內建立和關閉更多連接的情況,因此必須不斷調整2MSL直到由于延遲片段似乎是后來連接的一部分,因此它可能會開始出現問題; 如果您連接到相同的遠程地址和端口,并且非常快速地使用所有本地端口范圍,或者如果連接到相同的遠程地址和端口并將本地端口綁定到固定值,這種情況才會變得可能。
更改2MSL延遲通常是機器范圍內的配置更改。您可以嘗試TIME_WAIT使用SO_REUSEADDR套接字選項解決套接字級別問題。這允許在具有相同地址和端口的現有套接字已經存在的同時創建套接字。新的套接字基本上劫持了舊的套接字。您可以使用它SO_REUSEADDR來允許創建套接字,同時具有相同端口的套接字已經在其中,TIME_WAIT但這也會導致諸如拒絕服務攻擊或數據竊取等問題。在Windows平臺上另一套接字選項,SO_EXCLUSIVEADDRUSE可以幫助防止某些缺點的SO_REUSEADDR,,但在我看來,最好避免這些嘗試在各地工作TIME_WAIT,而是設計系統,使TIME_WAIT 不是問題。
上面的TCP狀態轉換圖都顯示有序的連接終止。還有另一種方法來終止TCP連接,這是通過中止連接并發送一個RST而不是一個FIN。這通常通過將SO_LINGER套接字選項設置為0 來實現。這會導致掛起的數據被丟棄,并且連接將被中止,RST而不是等待傳輸的數據,并且使用a清除干凈的連接FIN。重要的是要認識到,當連接被中止時,可能在對等體之間流動的任何數據將被丟棄,RST直接送達; 通常作為表示“連接已被同級重置”的事實的錯誤。遠程節點知道連接被中止,并且兩個節點都沒有進入TIME_WAIT。
當然,已經中止使用的連接的新化身RST可能成為TIME_WAIT阻礙的延遲段問題的受害者,但是成為問題所需的條件是不太可能的,無論如何,參見上面的更多細節。為了防止已經中止的連接導致延遲段問題,兩個對等端都必須轉換到TIME_WAIT連接關閉可能由諸如路由器之類的中介引起。但是,這不會發生,連接的兩端都會關閉。
有幾件事你可以做,以避免TIME_WAIT成為你的問題。其中一些假定您有能力更改您的客戶端和服務器之間所說的協議,但是對于自定義服務器設計,您通常會這樣做。
對于永遠不會建立自己的出站連接的服務器,除了維護連接的資源和性能意義之外TIME_WAIT,您不必過分擔心。
對于確實建立出站連接并接受入站連接的服務器,黃金法則是始終確保如果TIME_WAIT需要發生這種情況,則它會在另一個對等而不是服務器上結束。做到這一點的最佳方式是永遠不要從服務器發起主動關閉,無論原因是什么。如果您的對等方超時,則以中止連接RST而不是關閉連接。如果你的對方發送無效數據,中止連接等等。
這個想法是,如果你的服務器永遠不會啟動一個主動關閉,它永遠不會累積TIME_WAIT套接字,因此永遠不會受到它們導致的可伸縮性問題的困擾。雖然在出現錯誤情況時很容易看到如何中止連接,但正常連接終止的情況如何?理想情況下,您應該為協議設計一種方式,讓服務器告訴客戶端它應該斷開連接,而不是簡單地讓服務器發起主動關閉。因此,如果服務器需要終止連接,服務器會發送一個應用程序級別“我們已經完成”的消息,客戶將此消息作為關閉連接的原因。如果客戶端未能在合理的時間內關閉連接,則服務器會中止連接。
在客戶端上,事情稍微復雜一些,畢竟,有人必須發起一個主動關閉來干凈地終止TCP連接,并且如果它是客戶端,那么這就是TIME_WAIT最終會結束的地方。然而,TIME_WAIT最終在客戶端有幾個優點。
首先,如果由于某種原因,客戶端由于套接字在TIME_WAIT一個客戶端中的積累而最終導致連接問題。其他客戶不會受到影響。
其次,快速打開和關閉TCP連接到同一臺服務器是沒有效率的,因此超出了問題的意義TIME_WAIT嘗試維持更長時間的連接而不是更短的時間。不要設計一個協議,客戶端每分鐘連接一次,并打開一個新的連接。而是使用持久連接設計,并且只有在連接失敗時才重新連接,如果中間路由器拒絕在沒有數據流的情況下保持連接處于打開狀態,則可以實現應用程序級別ping,使用TCP保持活動狀態或僅接受路由器重置連接; 好處是你沒有積累TIME_WAIT插座。如果你在連接上做的工作自然是短暫的,那么考慮某種形式的“連接池”設計,從而保持連接的開放性和重用性。
最后,如果您絕對必須快速地從客戶端打開和關閉連接到同一臺服務器,那么也許您可以設計一個可以使用的應用程序級別關閉順序,然后按照此順序關閉。您的客戶可能會發送“我完成了”消息,您的服務器可能會發送“再見”消息,然后客戶端可能會中止連接。
TIME_WAIT存在原因并通過縮短2MSL周期或允許地址重用來解決這個問題SO_REUSEADDR并不總是一個好主意。
如果你能夠設計你的協議TIME_WAIT避免在腦海中,那么你通常可以完全避免這個問題。
TIME_WAIT的4種查詢方式
1、netstat -n | awk '/^tcp/ {++S[$NF]} END {for(a in S) print a, S[a]}'
2、ss -s
3、netstat -nat |awk '{print $6}'|sort|uniq -c|sort -rn
4、 統計 TIME_WAIT 連接的本地地址
netstat -an | grep TIME_WAIT | awk '{print $4}' | sort | uniq -c | sort -n -k1
嘗試抓取 tcp 包
系統參數優化
處理方法:需要修改內核參數開啟重啟:
net.ipv4.tcp_tw_recycle = 1 (在NAT網絡中不建議開啟,要設置為0),并且開啟net.ipv4.tcp_timestamps = 1以上兩個參數才生產。
具體操作方法如下:
echo "net.ipv4.tcp_tw_recycle = 1" >> /etc/sysctl.conf;
echo "net.ipv4.tcp_timestamps = 1" >> /etc/sysctl.conf;
sysctl -p;
nginx 配置優化
當使用nginx作為反向代理時,為了支持長連接,需要做到兩點:
- 從client到nginx的連接是長連接
- 從nginx到server的連接是長連接
保持和client的長連接:
keepalive_timeout 120s 120s;
keepalive_requests 1000;
}
keepalive_timeout設置
語法
第一個參數:設置keep-alive客戶端(瀏覽器)連接在服務器端(nginx端)保持開啟的超時值(默認75s);值為0會禁用keep-alive客戶端連接;
第二個參數:可選、在響應的header域中設置一個值“Keep-Alive: timeout=time”;通常可以不用設置;
這個keepalive_timeout針對的是瀏覽器和nginx建立的一個tcp通道,沒有數據傳輸時最長等待該時候后就關閉.
nginx和upstream中的keepalive_timeout則受到tomcat連接器的控制,tomcat中也有一個類似的keepalive_timeout參數
keepalive_requests理解設置
keepalive_requests指令用于設置一個keep-alive連接上可以服務的請求的最大數量,當最大請求數量達到時,連接被關閉。默認是100。
這個參數的真實含義,是指一個keep alive建立之后,nginx就會為這個連接設置一個計數器,記錄這個keep alive的長連接上已經接收并處理的客戶端請求的數量。如果達到這個參數設置的最大值時,則nginx會強行關閉這個長連接,逼迫客戶端不得不重新建立新的長連接。
保持和server的長連接
為了讓nginx和后端server(nginx稱為upstream)之間保持長連接,典型設置如下:(默認nginx訪問后端都是用的短連接(HTTP1.0),一個請求來了,Nginx 新開一個端口和后端建立連接,后端執行完畢后主動關閉該鏈接)
proxy_http_version 1.1;
proxy_set_header Connection keep-alive;
proxy_pass http://httpurl;
}
HTTP協議中對長連接的支持是從1.1版本之后才有的,因此最好通過proxy_http_version指令設置為”1.1”;
從client過來的http header,因為即使是client和nginx之間是短連接,nginx和upstream之間也是可以開啟長連接的。
keepalive理解設置
此處keepalive的含義不是開啟、關閉長連接的開關;也不是用來設置超時的timeout;更不是設置長連接池最大連接數。官方解釋:
- The connections parameter sets the maximum number of idle keepalive connections to upstream servers connections(設置到upstream服務器的空閑keepalive連接的最大數量)
- When this number is exceeded, the least recently used connections are closed. (當這個數量被突破時,最近使用最少的連接將被關閉)
- It should be particularly noted that the keepalive directive does not limit the total number of connections to upstream servers that an nginx worker process can open.(特別提醒:keepalive指令不會限制一個nginx worker進程到upstream服務器連接的總數量)
總結:
keepalive 這個參數一定要小心設置,尤其對于QPS比較高的場景,推薦先做一下估算,根據QPS和平均響應時間大體能計算出需要的長連接的數量。
比如前面10000 QPS和100毫秒響應時間就可以推算出需要的長連接數量大概是1000. 然后將keepalive設置為這個長連接數量的10%到30%。比較懶的同學,可以直接設置為keepalive=1000之類的,一般都OK的了。
綜上,出現大量TIME_WAIT的情況
1)導致 nginx端出現大量TIME_WAIT的情況有兩種:
keepalive_requests設置比較小,高并發下超過此值后nginx會強制關閉和客戶端保持的keepalive長連接;(主動關閉連接后導致nginx出現TIME_WAIT)
keepalive設置的比較小(空閑數太小),導致高并發下nginx會頻繁出現連接數震蕩(超過該值會關閉連接),不停的關閉、開啟和后端server保持的keepalive長連接;
2)導致后端server端出現大量TIME_WAIT的情況:
nginx沒有打開和后端的長連接,即:沒有設置proxy_http_version 1.1;和proxy_set_header Connection “”;從而導致后端server每次關閉連接,高并發下就會出現server端出現大量TIME_WAIT
-
服務器
+關注
關注
12文章
9149瀏覽量
85398 -
TCP
+關注
關注
8文章
1353瀏覽量
79067 -
TIME
+關注
關注
0文章
13瀏覽量
14324 -
nginx
+關注
關注
0文章
149瀏覽量
12173
發布評論請先 登錄
相關推薦
評論