[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

济南小老虎發表於2024-06-16
https://cloud.tencent.com/developer/article/1558493

前言

在對一個擋板系統進行測試時,遇到一個由於TCP全連線佇列被佔滿而影響系統效能的問題,這裡記錄下如何進行分析及解決的。

理解下TCP建立連線過程與佇列

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

從圖中明顯可以看出建立 TCP 連線的時候,有兩個佇列:syns queue(半連線佇列)和accept queue(全連線佇列),分別在第一次握手和第三次握手。 半連線佇列: 儲存 SYN_RECV 狀態的連線。 控制引數:

  • 半連線佇列的大小:min(backlog, 核心引數 net.core.somaxconn,核心引數tcp_max_syn_backlog).
  • net.ipv4.tcp_max_syn_backlog:能接受 SYN 同步包的最大客戶端數量,即半連線上限;
  • tcp_syncookies:當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉;

accept佇列-全連線佇列:儲存 ESTABLISHED 狀態的連線。 控制引數:

  • 全連線佇列的大小:min(backlog, /proc/sys/net/core/somaxconn),意思是取backlog 與 somaxconn 兩值的最小值,net.core.somaxconn 定義了系統級別的全連線佇列最大長度,而 backlog 只是應用層傳入的引數,所以 backlog 值儘量小於net.core.somaxconn;
  • net.core.somaxconn(核心態引數,系統中每一個埠最大的監聽佇列的長度);
  • net.core.netdev_max_backlog(每個網路介面接收資料包的速率比核心處理這些包的速率快時,允許送到佇列的資料包的最大數目);
  • ServerSocket(int port, int backlog) 程式碼中的backlog引數;
  • 檔案控制代碼;
  • net.ipv4.tcp_abort_on_overflow = 0,此值為 0 表示握手到第三步時全連線佇列滿時則扔掉 client 發過來的 ACK,此值為 1 則說明握手到第三步時全連線佇列滿時則返回 reset 給客戶端。

系統概況

系統的整體架構比較簡單,只有一個擋板服務,業務功能主要是接受業務資料寫入日誌檔案,並加了 35 ms的延時等待,沒有複雜的運算等業務邏輯。

開始第一次壓測

以 6000 執行緒併發,加 1 秒的等待,對擋板服務發起壓力,壓測結果如下: PS:應客戶要求,為了模擬真實業務場景,用較大併發進行測試。

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

透過上圖可以看出,當系統吞吐量也就是 TPS 達到 3800 左右的時候,系統開始出現部分請求失敗,繼續壓一段時間後,報錯沒有減少,且有增多的趨勢。這是什麼原因導致的呢,接著我也觀察了下,系統的資源使用情況,發現CPU也不是很高,那可以先排除系統CPU資源的問題。

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

這時候,我們一定要記住,當出現請求事務大量失敗的時候,一定要先看以下具體的錯誤資訊,在繼續往下面分析,而不是進行盲目的猜測,這裡要提一下高樓老師經常強調的證據鏈,一定要根據詳細的錯誤資訊指向進行下一步分析,不能根據猜測進而透過修改一些引數,或者增加系統資源來解決問題。 以下是具體的報錯資訊:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

看到報錯資訊後,發現有大量的 “Connection reset” 錯誤,導致這種錯誤的原因就是服務端因為某種原因關閉了 Connection,而客戶端仍然在讀寫資料,此時伺服器會返回復位標誌 “RST”,也就是剛才提到的 `“java.net.SocketException: Connection reset”。參考 Oracle 的相關文件,看到這麼一段話,原文如下:

By contrast, an abortive close uses the RST (Reset) message. If either side issues an RST, this means the entire connection is aborted and the TCP stack can throw away any queued data which has not been sent or received by either application.

翻譯過來也就是說:

如果任何一方發出RST,這意味著整個連線被中止,TCP棧可以丟棄任何沒有被任何應用程式傳送或接收的佇列資料。

這樣的話,問題就很明顯了,接下來看下 TCP 連線佇列的溢位資料統計情況,命令為:“netstat -s

程式碼語言:javascript
複製
# 檢視TCP半連線佇列溢位:
netstat -s | grep LISTEN

# 檢視TCPaccept佇列溢位:
netstat -s | grep overflow
[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

透過反覆敲命令,可以看出這個 overflow 的值一直在增加,那麼這個現象說明 server 的TCP 全連線佇列的確是滿了。這時候應該想到的是,全連線佇列已經溢位了,下一步就應該看一下,全連線佇列的佔用情況,命令為:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

引數說明:

  • Recv-Q:全連線當前長度
  • Send-Q:如果連線不是在建立狀態,則是當前全連線最大佇列長度

從上圖第三列的 Send-Q 可以看出,5000 埠服務的全連線佇列最大為 50,而 Recv-Q 為當前使用了多少。在壓測過程中,檢視指定埠的 TCP 全連線佇列使用情況,如下:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

上圖可以看出,全連線佇列幾乎已經被佔滿,那麼最終可以確定問題所在了。找到原因後,現在只要增大全連線佇列的長度就可以了。 透過上面介紹的全連線佇列中,我們知道全連線佇列的大小為 backlog 和 somaxconn 的最小值,那麼來看下 somaxconn 的取值。

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

可以看出 somaxconn 的值是很大的,那就只有通知開發,增加應用程式碼中的 backlog 的值來加大全連線佇列的長度。

調大backlog值為5000後,再次進行壓測

調整後的全連線佇列如下圖所示:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

繼續以 6000 併發對系統發起壓力,測試結果如下:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

從上面的測試結果資料看出,已經沒有錯誤請求了,再次檢視TCP全連線佇列的使用情況,Recv-Q的值也變得很大,但是仍小於 5000,這也說明之前的 50 的確太小,導致全連線佇列被佔滿,最終影響系統效能,出現大量請求失敗,到此,由 TCP 連線佇列滿導致的問題解決。

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

但是仔細看上面的 JMeter 的測試結果資料,發現當系統併發達到 4600 多後,再繼續加大執行緒,系統的響應時間開始大幅度的增加,TPS增加趨勢變緩,可以看出來此時系統仍存在瓶頸。 發現仍問題後,接著往下分析。系統沒有報錯,響應時間變長,導致系統吞吐量增長速度變慢。這時應該清楚的是,接下來該看什麼。首先檢視系統CPU使用情況,發現並不是很高,說明不是系統資源不夠用而引起的問題。因為擋板服務本身沒有什麼業務邏輯,只是加了 35 ms的延時,那麼如果響應時間變慢了,那麼多半是由於網路傳輸出現阻塞導致。 所以使用命令:

程式碼語言:javascript
複製
netstat -ano | grep 10.231.44.249:5000 | grep ESTABLISHED | more

看下網路佇列情況: 

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

引數說明:

  • Send-Q:傳送佇列中沒有被遠端主機確認的 bytes 數;
  • Recv-Q:指收到的資料還在快取中,還沒被程序讀取,這個值就是還沒被程序讀取的
  • bytes;一般是CPU處理不過來導致的。

可以看出圖中標紅列的資料不為 0,透過上面的解釋可以判斷出是系統 CPU 處理不過來了,但是CPU也沒有被充分使用,那為什麼會出現這種情況呢。接下我們就該看一下,CPU 在做什麼。 這裡使用阿里的開源工具 arthas(arthas的安裝及使用這裡不過多介紹了),看下擋板服務是否存線上程的資源爭用或者阻塞等,發現結果如下,存在大量的執行緒狀態為 BLOCKED 命令:thread | grep BLOCKED

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

命令:thread -b

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

看到具體的執行緒棧資訊後,問題就比較明顯了,是一個寫日誌的鎖導致出現執行緒阻塞,嚴重影響系統的處理能力。 為了快速的驗證是寫日誌導致的,調整日誌級別為 ERROR,再次發起壓力,看問題是否解決,測試結果如下:

[轉帖]效能分析之TCP全連線佇列佔滿問題分析及最佳化過程

調整日誌級別後,系統的響應時間保持在 37 ms左右,吞吐量有了大幅度的提升,問題解決。

小結

透過上面的分析案例,需注意以下幾點:

  1. 壓測時,如果出現請求大量失敗時,記住一定要先解決報錯,在進行下一步的分析;
  2. 進行效能分析時,一定要找到相應的證據鏈一步一步的往下分析,而不是盲目的猜測,透過修改引數及加大資源配置來解決問題;
  3. 響應時間長,TPS上不去這種問題,一定要對時間進行拆分拆解,找到時間具體慢在哪裡,再進行進一步的分析最佳化。

相關文章