在部署基於 MySQL 原生復制的高可用系統時,發現一個反直覺的現象:當半同步復制降級為異步復制的超時時間 rpl_semi_sync_master_timeout 設置極大值 (如 300 天) 時,系統 QPS 會嚴重下降,與超時時間呈正比關係。這在高併發場景下會造成災難性後果。
| rpl_semi_sync_master_enabled | rpl_semi_sync_master_timeout (ms) | QPS | 換算天數 |
|---|---|---|---|
| ON | 2,147,483,648 | 13.99 | 約 248 天 |
| ON | 2,147,483,648 | 196.3 | 約 24.8 天 |
| ON | 2,147,483,641 | 1251.67 | 約 2.5 天 |
| ON | 86,400,000 | 2146.96 | 1 天 |
| ON | 43,200,000 | 3211.17 | 12 小時 |
| ON | 21,600,000 | 3583.02 | 6 小時 |
| ON | 10,000 (默認) | 3637.16 | 10 秒 |
| OFF | - | 8926.76 | 異步模式 |
數據清晰顯示:超時值越大,QPS 越低,呈線性負相關。
查閱 MySQL 源碼 plugin/semisync/semisync_master.cc 中的 ReplSemiSyncMaster::commitTrx() 方法:
unsigned long long diff_nsecs = start_ts.tv_nsec + (unsigned long long)wait_timeout_ * TIME_MILLION;
abstime.tv_sec = start_ts.tv_sec;
while (diff_nsecs >= TIME_BILLION) { // 罪魁禍首!
abstime.tv_sec++;
diff_nsecs -= TIME_BILLION;
}
abstime.tv_nsec = diff_nsecs;
問題本質:代碼通過 while 循環將毫秒級超時轉換為秒和納秒。循環次數等於 wait_timeout_ 對應的秒數。設置 300 天約 25,920,000 秒,意味著每次事務提交都要執行 2500 萬次循環迭代,直接阻塞主線程導致性能崩潰。
將循環替換為等價的除法與取模運算,時間複雜度從 O(n) 降為 O(1):
// 修改前
start_ts.tv_nsec + (unsigned long long)wait_timeout_ * TIME_MILLION;
abstime.tv_sec = start_ts.tv_sec;
while (diff_nsecs >= TIME_BILLION) { ... }
// 修改後
start_ts.tv_nsec + ((unsigned long long)wait_timeout_ % TIME_THOUSAND) * TIME_MILLION;
abstime.tv_sec = start_ts.tv_sec + (unsigned long long)wait_timeout_ / TIME_THOUSAND;
plugin/semisync/semisync_master.cc 替換上述邏輯cmake -DCMAKE_INSTALL_PREFIX=/usr/local/mysql ... 其他選項
make
cp ./plugin/semisync/semisync_master.so /usr/local/mysql/lib/plugin/
chown mysql:mysql /usr/local/mysql/lib/plugin/semisync_master.so
修復後 QPS 穩定在 3700 左右,與 rpl_semi_sync_master_timeout 設置值無關,性能恢復正常。
該 Bug 已提交 MySQL 官方:Bug #80651
核心教訓:看似無害的參數配置可能觸發算法級性能陷阱。任何涉及時間轉換、大數運算的邏輯都應警惕 O(n) 循環,優先考慮 O(1) 數學運算替代。