一則 TCP 緩存超負荷導致的 MySQL 連接中斷的案例分析

除了 MySQL 本身之外,如何分析定位其他因素的可能性?

作者:龔唐傑,愛可生 DBA 團隊成員,主要負責 MySQL 技術支持,擅長 MySQL、PG、國產數據庫。

愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

本文約 1200 字,預計閱讀需要 3 分鐘。

背景

在執行跑批任務的過程中,應用程序遇到了一個問題:部分任務的數據庫連接會突然丟失,導致任務無法完成。從數據庫的錯誤日誌中,發現了 Aborted connection 的信息,這說明客戶端和服務器之間的通信被異常中斷了。

分析

爲了找出問題的原因,我們首先根據經驗,分析了可能導致連接被 Aborted 的幾種常見情況:

  1. 客戶端沒有正確地關閉連接,沒有調用 mysql_close() 函數。
  2. 客戶端空閒時間超過了 wait_timeoutinteractive_timeout 參數的秒數,服務器自動斷開了連接。
  3. 客戶端發送或接收的數據包大小超過了 max_allowed_packet 參數的值,導致連接中斷。
  4. 客戶端試圖訪問數據庫,但沒有權限,或者使用了錯誤的密碼,或者連接包不包含正確的信息。

然而,經過排查,發現以上情況都不適用於當前的問題。因爲任務在之前都是正常運行的,而且程序也沒有變動,所以可以排除第一種情況。查看了 MySQL 的超時參數 wait_timeoutinteractive_timeout ,發現它們都是 28800,也就是 8 個小時,遠遠超過了任務執行時間,所以可以排除第二種情況。也檢查了客戶端和服務器的 max_allowed_packet 參數,發現它們都是 64M,也不太可能超過這個限制,所以可以排除第三種情況。我們也確認了客戶端的數據庫訪問權限,密碼,連接包等信息,都是正確的,所以可以排除第四種情況。

到此,我們初步感覺 MySQL 層面應該沒有問題,問題可能出在其他地方。

爲了進一步定位問題,我們嘗試了修改服務器的一些相關內核參數,如下:

net.ipv4.tcp_keepalive_intvl = 30
net.ipv4.tcp_keepalive_probes = 3
net.ipv4.tcp_keepalive_time = 120
net.core.rmem_default = 2097152
net.core.wmem_default = 2097152
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_max_syn_backlog = 16384

這些參數主要是爲了優化網絡連接的性能和穩定性,避免連接被意外關閉或超時。但是,修改後的結果並沒有改善,連接還是會異常中斷。

最後,我們嘗試了進行抓包分析,通過 Wireshark 工具,我們發現了一個異常的現象:服務器會給客戶端發送大量的 ACK 包。如下圖所示:

這些 ACK 包是 TCP 協議中的確認包,表示服務器已經收到了客戶端的數據包,請求客戶端繼續發送數據。但是,爲什麼服務器會發送這麼多的 ACK 包呢?我們猜測可能是網絡有異常,導致客戶端接收不到服務器返回的 ACK 包,所以服務器會反覆發送 ACK 包,直到超時或收到客戶端的響應。但是,經過網絡人員的排查,未發現有明顯的問題。

繼續分析抓包,我們又發現了另一個異常的現象:客戶端會給發送服務器一些窗口警告。如下圖所示:

這些窗口警告是 TCP 協議中的流量控制機制,表示服務器或客戶端的接收窗口已經滿了,不能再接收更多的數據。

[TCP Window Full] 是發送端向接收端發送的一種窗口警告,表示已經到數據接收端的極限了

[TCP ZeroWindow] 是接收端向發送端發送的一種窗口警告,告訴發送者,接收端接收窗口已滿,暫時停止發送。

根據以上信息,我們推測出了問題的原因:由於 MySQL 需要發送的數據太大,客戶端的 TCP 緩存已經滿了,所以需要等待客戶端把 TCP 緩存裏面的數據消化掉,才能繼續接收數據。但是,在這段時間內,MySQL 會一直向客戶端請求繼續發送數據,如果客戶端在一定時間內(默認是 60 秒)沒有響應,MySQL 就會認爲發送數據超時,中斷了連接。

爲了驗證推測,查看 MySQL 的慢日誌,發現了很多 Last_errno: 1161 的記錄。

這些記錄表示 MySQL 在發送數據時遇到了超時錯誤,而且發現出現的次數和應用程序失敗的任務數很接近。根據 MySQL 官網的說明,這個錯誤的含義是:

Error number: 1161; Symbol: ER_NET_WRITE_INTERRUPTED; SQLSTATE: 08S01

Message: Got timeout writing communication packets

可知這個表示的意思是網絡寫入中斷,而MySQL層面有個參數就是控制這個的,所以嘗試更改net_write_timeout參數爲600,跑批任務正常運行。

所以 MySQL 連接被異常中斷的原因在於客戶端獲取的數據庫太大,超過了客戶端 TCP 緩存,客戶端需要先處理緩存中的數據,在這段時間內,MySQL 會一直向客戶端請求繼續發送數據,但是客戶端 60 秒內一直未能響應,導致 MySQL 發送數據超時,中斷了連接。

結論

通過上述的分析和嘗試,我們得出了以下的結論:

  • 抓包信息中,有很多 ACK 信息是因爲客戶端的緩存滿了不能及時給服務端反饋,所以服務器會反覆發送 ACK 信息,直到超過 60秒(net_write_timeout 默認值是 60),導致 MySQL 把連接中斷了。
  • 慢日誌中,有很多 Last_errno: 1161 的記錄,是因爲該 SQL 實際已經在 MySQL 中執行完畢了,但是在發送數據到客戶端時,由於數據量太大超過了客戶端的 TCP 緩存,然後客戶端上的應用在 60 秒內未把緩存中的數據處理掉,導致 MySQL 往客戶端發送數據超時。
  • MySQL 層面調整 net_write_timeout 參數只能緩解這個現象,根因在於單個 SQL 獲取的數據量太大,超過了客戶端的緩存大小,應用程序不能短時間內處理完緩存中的數據,進而導致後續的數據發送超時。

優化建議

  • 業務層面進行分批處理數據,避免單個 SQL 從服務器獲取大量的數據,導致客戶端的 TCP 緩存不足。
  • 提高 MySQL 中的 net_write_timeout 參數或者增加客戶端的 TCP 緩存,可緩解此情況的發生,但不能徹底解決該問題,因爲數據量太大仍然會影響性能和穩定性。
  • 優化 SQL 語句,減少不必要的數據返回,比如使用 LIMIT、WHERE 等條件,或者使用聚合函數,分組函數等,以減少數據量和提高查詢效率。

更多技術文章,請訪問:https://opensource.actionsky.com/

關於 SQLE

SQLE 是一款全方位的 SQL 質量管理平臺,覆蓋開發至生產環境的 SQL 審覈和管理。支持主流的開源、商業、國產數據庫,爲開發和運維提供流程自動化能力,提升上線效率,提高數據質量。

SQLE 獲取

類型 地址
版本庫 https://github.com/actiontech/sqle
文檔 https://actiontech.github.io/sqle-docs/
發佈信息 https://github.com/actiontech/sqle/releases
數據審覈插件開發文檔 https://actiontech.github.io/sqle-docs/docs/dev-manual/plugins/howtouse
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章