首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> MySQL 資料庫 >> MySQL 綜合教學

MySQL 半同步複制故障排查實戰:FD_SETSIZE 1024 限制導致 ACK 丟失的根因分析與解決方案

     編輯:MySQL 綜合教學

MySQL 半同步複制故障排查實戰:FD_SETSIZE 1024 限制導致 ACK 丟失的根因分析與解決方案

本文記錄生產環境半同步複制無法啟用的完整排查過程:從現象觀察、參數調整、日誌追蹤、系統調用分析,到 GDB 驗證 Socket fd=2344 超過 FD_SETSIZE=1024 導致 select() 監聽失效,最終給出臨時規避與根治建議。

一、問題現象

  • 大多數實例半同步正常,少數實例 Rpl_semi_sync_master_status=OFF 持續降級
  • 同一物理主機上兩個實例,一個正常一個異常
  • 調大 rpl_semi_sync_master_timeout 至 10s 無效

二、原理回顧

Master 執行事務 → Binlog 落盤 → 等待 Slave ACK → 收到 ACK → 提交回應。ack_receiver 執行緒用 select() 監聽 Slave Socket,超時未收到 ACK 則降級異步。

三、日誌追蹤定位斷點

設定 rpl_semi_sync_master_trace_level=80、rpl_semi_sync_slave_trace_level=80:

# Master
2016-01-04 18:00:30 [Note] ReplSemiSyncMaster::updateSyncHeader: server(-1721062019), (mysql-bin.000006, 500717950) sync(1), repl(1)
2016-01-04 18:00:40 [Warning] Timeout waiting for reply of binlog (file: mysql-bin.000006, pos: 500717950), semi-sync up to file , position 0.
2016-01-04 18:00:40 [Note] Semi-sync replication switched OFF.

# Slave
2016-01-04 18:00:30 [Note] ---> ReplSemiSyncSlave::slaveReply enter
2016-01-04 18:00:30 [Note] ReplSemiSyncSlave::slaveReply: reply (mysql-bin.000006, 500717950)
2016-01-04 18:00:30 [Note] <--- ReplSemiSyncSlave::slaveReply exit (0)

結論:Slave 已在 18:00:30 發送 ACK,Master 等待 10 秒未收到 → 降級。問題轉化為:Master 為何收不到 ACK?

四、select() 與 FD_SETSIZE 硬傷分析

ack_receiver 使用 select() 監聽多路 Socket。select() 依賴 fd_set 位陣列:

# 核心定義(Linux glibc)
#define __FD_SETSIZE 1024
typedef long int __fd_mask;           // 8 bytes = 64 bits
#define __NFDBITS 64
// fd_set 內部:__fd_mask __fds_bits[1024/64=16]
// 支援 fd 0~1023,每個 fd 佔 1 bit
// FD_SET(fd, &set) 若 fd>=1024 會陣列越界,無邊界檢查
// select() 掃描 16 個 long int,fd>1023 永不被檢查

若 MySQL 進程打開檔案描述符(長連線、Binlog Dump 等)超過 1023,Slave 連線 Socket fd 可能落在 1024 之後,導致 select() 根本不輪詢該 fd,ACK 封包雖到達核心卻永不被讀取。

五、GDB 實測鐵證

  1. ps -aux | grep mysqld | grep port 取得 PID
  2. gdb -p 掛載
  3. info threads 找到 ack_receiver 執行緒,thread 切換
  4. p m_slaves 印出 Socket fd → 2344

GDB 查看 fd=2344

fd=2344 > 1023,select() 監聽範圍外,根因確鑿。

六、解決方案

根治(需重編/改碼)

  • 擴大 FD_SETSIZE 並重編 glibc/內核:但 select() 掃描全陣列,fd 越大效能越差
  • 改用 poll()/epoll():無 fd 數量與最大值限制,現代高併發標準。需修改 MySQL 源碼(rpl_semi_sync_master.cc)重編

臨時規避(零停機)

從庫執行 STOP SLAVE; START SLAVE; 重建連線。核心重新分配 Socket fd,若 0~1023 有空閒位,新 fd 將落在可監聽範圍,半同步即刻生效。若 0~1023 全被長連線佔用則無效。

官方版本現狀

MySQL 5.7/8.0 源碼中 ack_receiver 仍用 select(),問題普遍存在。但典型部署連線數較少時 fd 不易超 1023,觸發機率低。

七、延伸疑問:同一 Socket 被 select 與 epoll 雙重監聽?

MySQL 三處 I/O 多路複用:

  1. 主監聽埠 select()
  2. 執行緒池 epoll()(EPOLLONESHOT 模式)
  3. 半同步 ack_receiver select()

Binlog Dump 執行緒屬 Worker,其 Socket 理應同時被 epoll(執行緒池)與 select(半同步)監聽。實測發現 epoll 採 EPOLLONESHOT:每次讀取後自動解綁,需重新 epoll_ctl(MOD) 註冊。故同一時刻 Socket 僅被單一機制監聽,不會衝突。

八、總結與建議

  • 半同步失效且 Slave 明確回 ACK 時,優先檢查 ack_receiver 監聽的 Socket fd 是否 > 1023
  • GDB 掛載查看 m_slaves fd 值是最直接驗證手段
  • 臨時用 STOP/START SLAVE 重建連線規避
  • 長期推動社區將 ack_receiver 遷移至 epoll/kqueue,徹底解決擴展性瓶頸
  • 所有使用 select() 的高併發網路程序均需警惕 FD_SETSIZE 限制
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved