結合案例深入解析orphan socket產生與消亡(一)
本文看點:結合服務器運行案例和TCP代碼分析orphan socket產生與消亡以及對係統的影響。精彩的部分在(二)細節分析章節。
問題背景
tengine服務器發生過多次orphan socket數量很多的情況,例如有一次使用ss -s命令查看:
$ss -s
Total: 36569 (kernel 36823)
TCP: 142787 (estab 28586, closed 4842, orphaned 92575, synrecv 9198, timewait 4684/5073), ports 9868
總共142787條TCP連接,處於orphaned狀態的連接達到92575。某天orphan socket數量監控占總量百分比曲線圖如下:
10:30-10:49 約持續19分鍾較高,然後恢複。
dmesg命令可以查看到有大量的提示:
[791.931967] Out of socket memory
orphan socket 做關鍵詞google一把有很多,但沒有查找到能詳細說明來龍去脈的,
先來看一下在網上關於orphan sockets一段較好的介紹。
Do you have "too many" orphan sockets?
First of all: what's an orphan socket? It's simply a socket that isn't associated to a file descriptor. For instance, after you close() a socket, you no longer hold a file descriptor to reference it, but it still exists because the kernel has to keep it around for a bit more until TCP is done with it. Because orphan sockets aren't very useful to applications (since applications can't interact with them), the kernel is trying to limit the amount of memory consumed by orphans, and it does so by limiting the number of orphans that stick around. If you're running a frontend web server (or an HTTP load balancer), then you'll most likely have a sizeable number of orphans, and that's perfectly normal.
簡要理解即orphan sockets是沒有與任何文件描述符關聯的socket,應用程序已經不能與此socket進行交互了,但是由於內核中TCP未完成,仍然占用TCP的內存暫時不能釋放。讀完這段還是丈二和尚摸不著頭腦――摸不清底細。
對於維護線上穩定性需要搞清楚如下問題:
什麼原因或條件下會導致出現這麼多orphan socket ?
orphan socket的連接處於TCP狀態的那一個階段?
orphan socket過多會給線上帶來什麼風險?
原因定位
以某台機器為例看一下TCP各狀態的連接數量。
$ss -s
Total: 37964 (kernel 38570)
TCP: 146848 (estab 29556, closed 5114, orphaned 94904, synrecv 9402, timewait 4823/5226), ports 10946
orphaned數量達到 94904;
查看close調用後TCP連接各狀態socket數量:(為什麼直接查close調用後的狀態請參細節分析部分,另外因以下命令執行時間上的偏差可能會對不上ss -s的數值)
$ss -nat | grep LAST-ACK | wc -l
67128
$ss -nat | grep FIN-WAIT-2 | wc -l
11712
$ss -nat | grep FIN-WAIT-1 | wc -l
12317
$ss -nat | grep CLOSE-WAIT | wc -l
25
$ss -nat | grep TIME-WAIT | wc -l
1410
top10輸出分析ip:port的連接占用: 以下命令輸出中第一列代表IP:PORT ,第二列代表TCP連接數量。
LAST_ACK狀態的top10的IP與PORT輸出
$ss -ant | grep LAST-ACK | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10x.xxx.xxx.x38:51892 62190
10x.xxx.xxx.x38:51814 381
10x.xxx.xxx.x38:59206 97
10x.xxx.xxx.x38:56107 94
10x.xxx.xxx.x38:58271 92
10x.xxx.xxx.x38:59800 85
10x.xxx.xxx.x38:50851 55
10x.xxx.xxx.x38:52226 45
10x.xxx.xxx.x38:52199 27
10x.xxx.xxx.x38:57017 25
FIN-WAIT-1狀態的top10的IP與PORT輸出
$ss -ant | grep FIN-WAIT-1 | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10x.xxx.xxx.x38:51892 5581
10x.xxx.xxx.x38:51814 1208
10x.xxx.xxx.x38:59206 782
10x.xxx.xxx.x38:58271 749
10x.xxx.xxx.x38:56107 724
10x.xxx.xxx.x38:59800 611
10x.xxx.xxx.x38:50851 285
10x.xxx.xxx.x38:52199 142
10x.xxx.xxx.x38:50127 97
10x.xxx.xxx.x38:52435 65
FIN-WAIT-2狀態的top10的IP與PORT輸出
$ss -ant | grep FIN-WAIT-2 | awk "{++S[\$4]} END {for(a in S) print a, S[a]}" |sort -t " " -k 2 -n -r | head -n 10
10.xxx.xxx.38:51814 1825
10.xxx.xxx.38:59800 1684
10.xxx.xxx.38:58271 1661
10.xxx.xxx.38:59206 1563
10.xxx.xxx.38:56107 1558
10.xxx.xxx.38:50851 329
10.xxx.xxx.38:52199 261
10.xxx.xxx.38:50127 203
10.xxx.xxx.38:52631 144
10.xxx.xxx.38:55488 114
可以明確看出這台主機上10x.xxx.xxx.x38:51892 占用了 62190 條socket處於LAST_ACK狀態。
為什麼這麼多LAST_ACK狀態的連接?看一下TCP狀態遷移圖,可知LAST_ACK狀態代表被動關閉的一端在發送FIN報文後,最後等待另一端的ACK報文。
正常流程客戶端TCP狀態遷移:
CLOSED->SYN_SENT->ESTABLISHED->FIN_WAIT_1->FIN_WAIT_2->TIME_WAIT->CLOSED
正常流程服務器TCP狀態遷移:
CLOSED->LISTEN->SYN_RCVD->ESTABLISHED->CLOSE_WAIT->LAST_ACK->CLOSED
此機器是做為反向代理服務器符合上述正常流程服務器TCP狀態遷移,第一個現象:通過監控查看10x.xxx.xxx.x38:51892對應的http流量,發現此用戶每秒的新建TCP連接數量是1.5K~2K,且設置的流量帶寬限製在20Mbps,並在報文出口方向出現大量的丟包;第二個現象:http訪問日誌中記錄大量的http 499狀態碼的訪問連接。
根據以上兩個現象推斷:10x.xxx.xxx.x38:51892收到client端的關閉FIN後會發送ACK,進入到CLOSE_WAIT 狀態,應用程序中如果沒有數據待發送會調用close()使TCP連接發送FIN,進入LAST_ACK狀態,因為出口流量設置的限製大量的ACK和FIN報文被丟棄,導致出現上述62190 條socket處於LAST_ACK狀態。
後來與用戶溝通得知client端是移動端APP程序,其TCP連接讀寫超過(限流量沒有收到數據等)會主動關閉連接,可以佐證日誌中大量出現的499狀態碼。
到此原因分析完成,可以使用解決方案中調整服務器的內核參數解決,如果需要更詳細了解orphaned socket可以詳細閱讀下一篇的細節分析。
解決方案
如上細節分析中某些場景下會對當前sock做懲罰,將當前orphan 的數量 2x 甚至 4x與係統限製值做比較。這樣內核日誌會打印出Out of socket memory,但實際的數量並沒有超過係統的限製值,屬於誤報的一種情形,可以根據具體的情況,將 tcp_max_orphans 內核參數值適當調大。
係統中net.ipv4.tcp_orphan_retries 的默認值是 0,內核重置為8次, 需要將tcp_orphan_retries改為較小的數值,可大大降低orphans的數量,降低tcp內存的占用量。注意:設置較小的數值,可以有效降低orphans的數量(net.ipv4.tcp_orphan_retries = 0並不是想像中的不重試)
案例小結
內核日誌dmesg命令看到 Out of socket memory,出現內存不足可能會有兩種情況:
有太多的 orphan sockets,通常對於一些負載較重的服務器經常會出現這種情況。
分配給 TCP 的內存確實較少,從而導致內存不足。
第一種情況,可以用ss -s命令或cat /proc/net/sockstat查看是否孤兒套接字過多。
第二種情況,可以調整TCP的內存,因網上有大量的此類分析解決,這裏不再詳述。
最後來回答一下對於線上穩定性來說需要搞清楚的問題:
- orphan socket的連接處於TCP狀態的那一個階段?
TCP_FIN_WAIT1、TCP_LAST_ACK、TCP_CLOSING狀態都可歸類計數於orphan socket,但當通過TCP_LINGER2或sysctl_tcp_fin_timeout設置的超時時間大於60秒時的TCP_FIN_WAIT2的連接也歸類計數於orphan socket;小於60秒的TCP_FIN_WAIT2狀態的連接則歸類計數於TIME_WAIT,從代碼可以看出TCP_TIME_WAIT狀態是不計入orphan socket;TCP_CLOSE_WAIT 狀態的連接既不計入orphan socket 也不計入TIME_WAIT。
- 什麼原因或條件下會導致出現這麼多orphan socket ?
- orphan socket過多會給線上帶來什麼風險?
TCP_FIN_WAIT1 和 TCP_LAST_ACK 狀態的連接都在等待對方回複ACK,例如client端可以對產生的連接故意發送FIN半關閉,而不回複最後的ACK使服務器端產生大量LAST_ACK,消耗TCP資源,這種情況下調整tcp_max_orphans參數和tcp_orphan_retries可以限製簡單的DDOS攻擊。
最後更新:2017-05-31 15:01:39