
1. 前言
本文排查的問題是經典的TCP隊列溢出問題,因TCP隊列問題在作業系統層面沒有明顯的名額異常,容易被忽略,故把排查過程分享給大家。
2. 問題描述
A服務調用B服務接口逾時,B服務主機IOWAIT高,具體逾時情況分為兩種:
- A服務的請求在B服務日志中可查到,但B服務的響應時間超過了A服務的等待逾時時間3S。
- A服務的請求在B服務日志中無法查到。
3. 問題分析
此種逾時請求集中在很短的一段時間(通常在2分鐘之内),過後便恢複正常,是以很難抓到問題現場分析原因,隻能搭建測試環境,A服務持續請求B服務,在B服務主機上通過DD指令寫入大量資料造成主機IOWAIT高,同時通過TCPDUMP在兩端抓包分析。
部分服務逾時日志:
- 服務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請求的資料包如下:
圖1:服務A發起GET請求
09:51:46.966653000 服務A發起 GET請求3s(即服務A設定的等待逾時時長)後,因未收到服務B響應,服務A向服務B發起FIN主動斷開連接配接。
圖2:服務A等待逾時主動斷開連接配接
09:51:59.958195000 服務A發起http請求16s後收到服務B的http響應封包,因服務A已主動關閉該連接配接,故直接回複RST。
圖3: 服務B16s後響應
服務B端資料包分析:
09:51:44.062095000 服務B收到服務A發送的http請求包。
圖4:服務B收到服務A的請求
09:51:59.936169000 服務B響應服務A,服務B從接收到http請求封包至響應http請求總用時約為15s多,但服務B列印的日志響應時長約為0.165s。
圖5:服務B15S後響應
圖6:服務B日志顯示響應時間0.165s
情景2:服務A有連接配接日志,服務B無連接配接日志。
09:51:43.973791000 服務A向服務B發送一個http請求資料包,随後收到服務B重傳的第二次握手的syn+ack包,超過3s未收到服務B的http響應後斷開連接配接。
圖7:服務B重傳syn+ack
服務B重傳了第二次握手的syn+ack包,收到服務A的http請求,服務B忽略,未響應,服務A等待逾時後斷開了連接配接。
圖8: 服務B忽略服務A請求
5. 根因分析
TCP在三次握手過程中核心會維護兩個隊列:
- 半連接配接隊列,即SYN隊列
- 全連接配接隊列,即ACCEPT隊列
圖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: TCP全隊列觀察方法
當連接配接處于listen狀态時:
- Recv-Q:目前全連接配接隊列的大小
- Send-Q:目前全連接配接最大隊列長度
當Recv-Q > Send-Q時表示全隊列溢出,可通過執行netstat -s | grep "overflowed"指令觀察溢出情況,檢視累計溢出次數,如果需觀察一段時間内的全隊列溢出情況,建議使用監控系統采集資料,比如prometheus。
圖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隊列溢出問題也随之解決。
我們是阿裡雲智能全球技術服務-SRE團隊,我們緻力成為一個以技術為基礎、面向服務、保障業務系統高可用的工程師團隊;提供專業、體系化的SRE服務,幫助廣大客戶更好地使用雲、基于雲建構更加穩定可靠的業務系統,提升業務穩定性。我們期望能夠分享更多幫助企業客戶上雲、用好雲,讓客戶雲上業務運作更加穩定可靠的技術,您可用釘釘掃描下方二維碼,加入阿裡雲SRE技術學院釘釘圈子,和更多雲上人交流關于雲平台的那些事。