記一次TCP全隊列溢出問題排查過程

7EB92BAE-561C-42da-A363-591774FB234B.png

1. 前言

本文排查的問題是經典的TCP隊列溢出問題,因TCP隊列問題在操做系統層面沒有明顯的指標異常,容易被忽略,故把排查過程分享給你們。算法

2. 問題描述

A服務調用B服務接口超時,B服務主機IOWAIT高,具體超時狀況分爲兩種:網絡

  • A服務的請求在B服務日誌中可查到,但B服務的響應時間超過了A服務的等待超時時間3S。
  • A服務的請求在B服務日誌中沒法查到。

3. 問題分析

此種超時請求集中在很短的一段時間(一般在2分鐘以內),事後便恢復正常,因此很難抓到問題現場分析緣由,只能搭建測試環境,A服務持續請求B服務,在B服務主機上經過DD命令寫入大量數據形成主機IOWAIT高,同時經過TCPDUMP在兩端抓包分析。
部分服務超時日誌:tcp

  • 服務A:Get http://xxx&id=593930: net/http: request canceled (Client.Timeout exceeded while awaiting headers)
  • 服務B: "GET xxx&id=593930 HTTP/1.1" 200 64 "-" "Go-http-client/1.1" "-" "-" 165000(單位微秒)

服務A發起請求3S後沒有收到服務B響應,斷開鏈接,服務B日誌顯示處理時長爲0.165S,遠低於3S,服務A側看服務B的響應時間爲網絡傳輸時間、TCP隊列排隊時間及服務B應用程序處理時間之和,由於是內網測試,網絡傳輸時間能夠忽略,主要排查方向應爲TCP隊列排隊時間。函數

4. 抓包數據分析

情景1:服務A及服務B均有鏈接日誌打印。
服務A端數據包分析:
09:51:43.966553000 服務A發起 GET請求的數據包以下:oop

1.png
圖1:服務A發起GET請求性能

09:51:46.966653000 服務A發起 GET請求3s(即服務A設置的等待超時時長)後,因未收到服務B響應,服務A向服務B發起FIN主動斷開鏈接。測試

2.png
圖2:服務A等待超時主動斷開鏈接阿里雲

09:51:59.958195000 服務A發起http請求16s後收到服務B的http響應報文,因服務A已主動關閉該鏈接,故直接回復RST。spa

3.png
圖3: 服務B16s後響應操作系統

服務B端數據包分析:
09:51:44.062095000 服務B收到服務A發送的http請求包。

4.png
圖4:服務B收到服務A的請求

09:51:59.936169000 服務B響應服務A,服務B從接收到http請求報文至響應http請求總用時約爲15s多,但服務B打印的日誌響應時長約爲0.165s。

5.png
圖5:服務B15S後響應

6.png
圖6:服務B日誌顯示響應時間0.165s

情景2:服務A有鏈接日誌,服務B無鏈接日誌。
服務A端數據包分析:
09:51:43.973791000 服務A向服務B發送一個http請求數據包,隨後收到服務B重傳的第二次握手的syn+ack包,超過3s未收到服務B的http響應後斷開鏈接。

7.png
圖7:服務B重傳syn+ack

服務B端數據包分析:
服務B重傳了第二次握手的syn+ack包,收到服務A的http請求,服務B忽略,未響應,服務A等待超時後斷開了鏈接。

8.png
圖8: 服務B忽略服務A請求

5. 根因分析

TCP在三次握手過程當中內核會維護兩個隊列:

  • 半鏈接隊列,即SYN隊列
  • 全鏈接隊列,即ACCEPT隊列

9.png
圖9:TCP隊列

TCP三次握手過程當中,第一次握手server收到client的syn後,內核會把該鏈接存儲到半鏈接隊列中,同時回覆syn+ack給client(第二次握手),第三次握手時server收到client的ack,若是此時全鏈接隊列未滿,內核會把鏈接從半鏈接隊列移除,並將其添加到 accept 隊列,等待應用進程調用 accept 函數取出鏈接,若是全鏈接隊列已滿,內核的行爲取決於內核參數tcp_abort_on_overflow:

  • tcp_abort_on_overflow=0,server會丟棄client的ack。
  • tcp_abort_on_overflow=1,server 會發送 reset 包給 client。

默認值是0。
情景1的抓包數據顯示鏈接已經進入全鏈接隊列,可是服務B日誌顯示的鏈接時間晚了15S多,說明鏈接在隊列裏等待了15S後才被應用處理。
情景2的抓包數據顯示全鏈接隊列已溢出,內核根據tcp_abort_on_overflow的值爲0丟棄了服務A的ack,超過了服務A的超時等待時間。
結論:服務B主機在IO達到瓶頸的狀況下,系統CPU時間主要消耗在等待IO響應及處理軟中斷上,服務B應用程序獲取的CPU時間有限,沒法及時調用 accept 函數把鏈接取出並處理,致使TCP全隊列溢出或隊列等待時間過長,超過了服務A的超時時間。

6. 如何觀察和調整tcp全隊列

10.png
圖10: TCP全隊列觀察方法

當鏈接處於listen狀態時:

  • Recv-Q:目前全鏈接隊列的大小
  • Send-Q:目前全鏈接最大隊列長度

當Recv-Q > Send-Q時表示全隊列溢出,可經過執行netstat -s | grep "overflowed"命令觀察溢出狀況,查看累計溢出次數,若是需觀察一段時間內的全隊列溢出狀況,建議使用監控系統採集數據,好比prometheus。

11.png
圖11: TCP隊列溢出監控

TCP 全鏈接隊列最大值取決於min(somaxconn, backlog),其中:

  • somaxconn可經過內核參數/proc/sys/net/core/somaxconn設置,默認值是128。
  • backlog是 listen(int sockfd, int backlog) 函數中的 backlog 大小,Nginx 默認值是 511,能夠經過修改配置文件設置其長度。

7. 結語

本次問題,由於服務對成功率要求很高,因此先經過調大服務B主機/proc/sys/net/core/somaxconn參數值及服務A的超時時間來緩解超時問題,暫時保證了接口成功率。但要從根本上解決問題,仍需解決誘因io瓶頸,由於服務B主機掛載的共享sas存儲集羣上有其餘客戶的主機偶爾io很大,影響了整個集羣的性能。爲解決此問題,更換爲獨享的ssd盤,並經過blktrace+fio分析,將io調度算法修改成noop,io性能明顯提高,TCP隊列溢出問題也隨之解決。

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

相關文章
相關標籤/搜索