基於官方 MySQL 5.7 源碼(GitHub),深度剖析 Semi-sync 核心路徑:Master 三大執行緒協作、組提交四階段插入 Semi-sync 等待、Slave IO 執行緒回應邏輯。
# Master SET GLOBAL rpl_semi_sync_master_enabled=1; SET GLOBAL rpl_semi_sync_master_timeout=10000; -- 毫秒 # Slave STOP SLAVE IO_THREAD; SET GLOBAL rpl_semi_sync_slave_enabled=1; START SLAVE IO_THREAD; -- 必須重啟 IO_THREAD 使參數生效
啟用後觀察 SHOW STATUS LIKE 'Rpl_semi_sync_master_status',ON 表示半同步生效。若因延遲超時會自動降級為 OFF。
調試利器:rpl_semi_sync_master_trace_level / rpl_semi_sync_slave_trace_level(建議 80=64+16,記錄詳細流程與函數調用)。
核心函數 mysql_binlog_send():
rpl_semi_sync_slave),是則 add_slave() 加入 ACK Receiver 監聽隊列file/pos 讀取 Binlog 事件updateSyncHeader():根據實時 Semi-sync 狀態決定是否在封包頭打標my_net_write() + net_flush() 發送wait_for_update_bin_log() 等待MySQL 5.6+ 採用組提交,原有 Flush/Sync/Commit 三階段,引入 Semi-sync 後擴展為四階段:
flush_cache_to_file() 一次 write。若 sync_binlog!=1,此處通知 Dump 執行緒有新 Binlog;若 =1 則等 Sync 階段後再通知(保證主庫 Binlog 先落地)。sync_binlog_file() 強制 fsync(視 sync_binlog 值)。call_after_sync() → cond_timedwait(&COND_binlog_send) 等待 ACK。waitAfterCommit() 再次 cond_timedwait 等待 ACK。關鍵決策點:由參數 rpl_semi_sync_master_wait_point 決定在階段 3 (AFTER_SYNC) 或階段 4 (AFTER_COMMIT) 等待。AFTER_COMMIT 更安全(主庫已提交),AFTER_SYNC 效能略好。
啟動條件:rpl_semi_sync_master_enabled=1。關閉參數則執行緒退出。
select() 監聽每個 Slave Socket 可讀事件my_net_read() 讀取 ACK 封包reportReplyPacket() 處理狀態:若 Slave 已收到最新 Binlog 位點 → mysql_cond_broadcast(&COND_binlog_send) 喚醒等待的 Worker核心函數 handle_slave_io():
safe_connect() 建立與 Master 連線request_dump():
SET @rpl_semi_sync_slave=1 標記自己為 Semi-slaveCOM_BINLOG_DUMP 請求 Binlogread_event() 讀取事件(無資料阻塞等待)slaveReadSyncHeader() 解析封包頭 Semi-sync 標記queue_event() 寫入 Relay Log(過濾自生成 Server-ID 事件)slaveReply() 發送 ACK(含當前 Relay Log file/pos)ReplSemiSyncMaster:Master 端狀態機,含 m_slaves(Slave 連線陣列)、m_semi_sync_slaves 計數、cond 條件變數ReplSemiSyncSlave:Slave 端狀態,含 net 網路結構、reply ACK 封包緩衝COND_binlog_send:條件變數,Worker 與 ACK Receiver 同步核心Semi-sync 在不改變複制基礎架構前提下,透過 ACK Receiver 執行緒 + 條件變數同步 實現「至少一從庫收到 Binlog」的強一致性保證。核心複雜度在於組提交管道中插入等待點、以及 select() 的擴展性瓶頸(見排查實戰篇)。源碼閱讀建議從 rpl_semi_sync_master.cc、rpl_semi_sync_slave.cc 入手。