本文記錄生產環境半同步複制無法啟用的完整排查過程:從現象觀察、參數調整、日誌追蹤、系統調用分析,到 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?
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 封包雖到達核心卻永不被讀取。
ps -aux | grep mysqld | grep port 取得 PIDgdb -p 掛載info threads 找到 ack_receiver 執行緒,thread 切換p m_slaves 印出 Socket fd → 2344
fd=2344 > 1023,select() 監聽範圍外,根因確鑿。
FD_SETSIZE 並重編 glibc/內核:但 select() 掃描全陣列,fd 越大效能越差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,觸發機率低。
MySQL 三處 I/O 多路複用:
select()epoll()(EPOLLONESHOT 模式)ack_receiver select()Binlog Dump 執行緒屬 Worker,其 Socket 理應同時被 epoll(執行緒池)與 select(半同步)監聽。實測發現 epoll 採 EPOLLONESHOT:每次讀取後自動解綁,需重新 epoll_ctl(MOD) 註冊。故同一時刻 Socket 僅被單一機制監聽,不會衝突。
ack_receiver 監聽的 Socket fd 是否 > 1023m_slaves fd 值是最直接驗證手段STOP/START SLAVE 重建連線規避ack_receiver 遷移至 epoll/kqueue,徹底解決擴展性瓶頸select() 的高併發網路程序均需警惕 FD_SETSIZE 限制