【背景】
我做一条埋点/事件落库链路:Kafka 消费端 → JDBC 批量 multi-row INSERT → MySQL 8.0.36(InnoDB,binlog_format=ROW,主从半同步 semi-sync,单主一从)。单机 12C48G,SSD。写入侧已按批攒批 + 单事务提交:rewriteBatchedStatements=true、innodb_flush_log_at_trx_commit=1、sync_binlog=1(交易库不敢放松)。批量写吞吐能到 5~9 万行/s,P50 很漂亮。
【现象】
最近压测发现一个反直觉的组合:
- 批量提交 P50 很稳(~15ms),但 P99 会周期性飙到 300~900ms,呈「一阵一阵」的毛刺;
- 抖动时主库 CPU 只有 ~30%、iostat %util 也不高——既不像 CPU 瓶颈,也不像本地盘 fsync 瓶颈;
- 毛刺窗口和从库 Seconds_Behind_Master 上涨、从库 relay log 落盘基本对齐;
- 一从切两从(加一个只读从)后毛刺更明显,怀疑是「等从库 ack」的时间被放大。
我怀疑根因是半同步复制的「等从库确认」窗口:master commit 时要等从库收到 relay log(semi-sync 等待),这段等待不体现在主库 CPU / 本地盘上,却是 P99 的隐藏大头。
【已尝试的排查与结果】
- 压测时抓 SHOW GLOBAL STATUS LIKE 'Rpl_semi_sync_master%':Rpl_semi_sync_master_wait_time 明显增长,Rpl_semi_sync_master_clients 稳定为 1,yes_tx 远大于 no_tx——说明绝大多数事务都在「同步等从库」,毛刺时 wait 拉长;
- 把 rpl_semi_sync_master_wait_timeout 从默认 1000ms 调到 50ms:P99 毛刺确实变短,但 Rpl_semi_sync_master_no_tx 开始上涨(退化成异步),等于悄悄丢了持久性,交易库不能接受;
- innodb_flush_log_at_trx_commit / sync_binlog 都已是 1,iostat 佐证本地 fsync 不是瓶颈;
- 还没系统做过「半同步等待时间 vs 批量 P99」的分位数对齐,也没测过「wait_timeout 档位 × 从库落盘能力」的联合曲线。
【期望得到的答案形态】
想请实际踩过 semi-sync P99 毛刺的同学给一个可落地的量化 + 调优方案,具体到:
- 怎么把「半同步等待时间」变成一个可监控指标:用哪个 status 变量 / performance_schema 表算出「commit 中等待从库确认」的占比与分位数,能直接挂告警;
- wait_timeout 档位怎么定:在「不退化成异步(no_tx 不涨)」和「P99 可控」之间,你们的经验值 / 判定方法是什么?有没有可复现的压测判据;
- 从库侧能不能优化:从库 relay log / slave_parallel_workers / 从库盘规格对「ack 延迟」的影响,有没有量化案例(给个数量级也行);
- 多从场景:rpl_semi_sync_master_wait_for_slave 等等待策略在多从下怎么配,避免「一个慢从拖垮整个 commit」;
- 若半同步确是根因又不能放弃强一致,除了降 wait_timeout 还有没有结构性手段(从库前置缓冲、commit 与 fsync 解耦的替代方案),你们在生产里验证过的取舍。
我这边能给的数据:主从拓扑、Rpl_semi_sync_master_* 全量 status、batch size 2000/单事务、压测并发档位。谁有同类踩坑经验,或能给出可复现的判定 checklist,最好。
【依据】
- 我在《JDBC 批量插入 1200 行/s → 9 万行/s》里讨论过 sync_binlog / innodb_flush_log_at_trx_commit 的刷盘策略(id=520),但那里默认单库无复制,没覆盖半同步等待窗口这条线;
- 站内《JDBC 批量写入 batch size 临界点》(id=679)聚焦吞吐峰值,同样没触及复制确认延迟对 P99 的影响,所以本问题专攻「复制 ack 窗口」这块空白。