在MySQL高可用架构的演进中,半同步复制(Semi-Synchronous Replication)并不是一个新技术,但却是很多团队在数据一致性与写入性能之间反复权衡后,最终落地的折中方案。它直接解决了异步复制下主库崩溃可能导致的已提交事务丢失问题,又避免了全同步复制对性能的毁灭性打击。然而,在实际生产环境中启用半同步复制,远不止执行一条INSTALL PLUGIN那么简单。很多看似正常的故障切换,背后都藏着半同步复制降级为异步复制的隐患,导致你以为数据是安全的,实际上已经出现了主备不一致的裂痕。
真正把半同步复制用好的关键,在于理解它的两种工作模式:AFTER_SYNC和AFTER_COMMIT。MySQL 5.7之前的版本只支持AFTER_COMMIT,这种模式存在一个致命缺陷:主库在引擎层提交事务后,等待从库确认ACK信号。如果主库在等待ACK期间崩溃,事务在主库上已经提交,但从库可能还没收到该事务的Binlog。当故障切换后,新主库缺少该事务,而旧主库恢复后却包含该事务,这就造成了数据冲突。MySQL 5.7引入的AFTER_SYNC模式彻底改变了这一逻辑,它将等待ACK的时机提前到引擎层提交之前。这意味着主库在存储引擎提交前,必须先等待至少一台从库确认已接收Binlog。如果主库此时崩溃,由于引擎层尚未提交,该事务在主库上也不可见,故障切换后数据天然一致。这就是为什么任何严肃的生产环境部署,都应该强制使用AFTER_SYNC模式,并将rpl_semi_sync_master_wait_point参数设置为AFTER_SYNC。
安装与配置的核心步骤半同步复制依赖插件机制,主库和从库都需要安装对应的插件。主库安装semisync_master.so,从库安装semisync_slave.so。很多人在这一步会忽略一个细节:插件必须同时在主库和从库上安装,缺一不可。如果只在主库安装,从库无法发送ACK,主库会一直等待直到超时,然后降级为异步复制。
在MySQL 8.0中,你可以通过以下方式安装插件,无需重启数据库:
-- 主库执行 INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so'; -- 从库执行 INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
安装完成后,需要开启插件并设置超时参数。这里有一个极易被误解的地方:rpl_semi_sync_master_timeout的默认值是10000毫秒,也就是10秒。很多人认为设置得越短越好,可以快速发现从库延迟问题。但实际上,如果这个值设置得过小,在业务高峰期或网络抖动时,主库会频繁因为等待ACK超时而降级为异步复制,反而失去了半同步的意义。建议根据你的网络环境和从库IO线程的处理能力,将这个值设置在1000毫秒到5000毫秒之间,并在监控中重点观察Rpl_semi_sync_master_status变量的值是否为ON。
-- 主库开启半同步并设置超时 SET GLOBAL rpl_semi_sync_master_enabled = 1; SET GLOBAL rpl_semi_sync_master_timeout = 3000; -- 从库开启半同步 SET GLOBAL rpl_semi_sync_slave_enabled = 1;
别忘了将这些参数写入my.cnf配置文件,否则数据库重启后配置会丢失。这里有一个生产环境的最佳实践:在从库上,建议同时设置rpl_semi_sync_slave_enabled = 1,但不要在主库启动时就强制要求半同步。你可以在主库启动后,通过监控确认从库已经连接并启用了半同步,再动态开启主库的半同步功能,避免启动顺序问题导致主库一开始就降级。
ACK确认机制与性能瓶颈半同步复制的核心交互在于从库的ACK确认。当主库提交事务时,会等待从库的IO线程将Binlog写入中继日志(Relay Log)并刷新到磁盘后,返回ACK信号。注意,这里并不要求从库的SQL线程已经执行完该事务,只要求Binlog已经落盘。这个设计在保证数据不丢失的前提下,最大程度减少了对主库写入性能的影响。
然而,这个机制也引入了性能瓶颈。如果你的从库IO线程写入中继日志的速度跟不上主库的写入速度,或者网络延迟较高,主库的每个事务提交都会增加一个网络往返时间(RTT)。在跨机房部署或云上跨可用区场景下,这个RTT可能达到几毫秒甚至更高。对于写入密集型的业务,这会导致主库的TPS急剧下降。解决这个问题的思路不是简单地调大超时时间,而是从架构层面优化。一种常见的做法是采用一主多从的半同步配置,只要求其中任意一台从库返回ACK即可。通过rpl_semi_sync_master_wait_for_slave_count参数,你可以设置主库需要等待的从库数量。默认值为1,意味着只要有一台从库确认收到Binlog,主库就可以继续提交。这在保证至少有一台从库拥有完整数据的前提下,分散了单台从库的压力。
-- 主库设置需要等待的从库ACK数量 SET GLOBAL rpl_semi_sync_master_wait_for_slave_count = 1;
另一个容易被忽视的优化点是rpl_semi_sync_master_wait_point的AFTER_SYNC模式本身。在AFTER_SYNC模式下,主库在引擎层提交之前等待ACK,这意味着事务的锁资源在等待期间一直持有。如果等待时间过长,会导致大量的行锁和间隙锁堆积,引发连锁反应。因此,监控主库的Innodb_row_lock_waits和Innodb_row_lock_time指标,与半同步复制的超时情况做关联分析,是DBA日常巡检的必要动作。
故障切换与降级策略的深度思考半同步复制最大的挑战不在于正常运行时的配置,而在于故障场景下的行为。当从库宕机或网络中断导致ACK超时,主库会根据rpl_semi_sync_master_timeout的设置,在超时后自动降级为异步复制。这个降级过程是静默的,应用层完全无感知,但数据安全性已经回退到了异步复制的水平。更危险的是,当从库恢复后,半同步复制会自动重新建立,但中间这段时间产生的数据差异可能已经埋下了隐患。
很多团队会配置一个监控告警,当检测到Rpl_semi_sync_master_status变为OFF时触发报警。但这还不够,因为你必须回答一个问题:降级期间如果主库真的崩溃了,你是否有预案?一种更激进的策略是设置rpl_semi_sync_master_timeout为一个非常大的值,比如86400000毫秒(24小时),这样主库在等待ACK时几乎不会超时。但代价是,如果所有从库都不可用,主库的写入操作会被无限期阻塞,相当于牺牲可用性来换取一致性。这种策略适用于对数据一致性要求极高的核心交易系统,但不适用于大多数互联网业务。
更务实的做法是结合MHA、Orchestrator或MySQL InnoDB Cluster等高可用工具,在故障切换逻辑中加入半同步状态的判断。例如,在Orchestrator的钩子脚本中,你可以检查候选新主库的半同步状态,以及它是否拥有最新的Binlog位点。如果候选新主库之前是半同步从库,且没有发生过降级,那么它的数据可以认为是与旧主库完全一致的。这就是为什么在高可用架构中,半同步从库应该被优先选举为新主库的原因。
还有一个容易被忽略的细节是,当主库降级为异步复制后,从库的IO线程可能因为重连而重新注册为半同步从库,但主库此时已经不再等待ACK。你需要通过监控主库的Rpl_semi_sync_master_clients变量来确认当前有多少台从库处于半同步状态。如果这个值从1变为0,说明所有从库都已经断开半同步连接,这是最高优先级的告警。
与GTID和并行复制的协同工作半同步复制与GTID(全局事务标识符)的结合使用,可以进一步提升数据一致性的保障。在启用GTID的情况下,即使主库降级为异步复制,你也可以通过gtid_executed和gtid_purged的对比,精确判断从库是否缺少事务。更重要的是,在AFTER_SYNC模式下,主库在等待ACK时,事务的GTID已经写入Binlog但尚未在引擎层提交。如果主库崩溃,从库上可能存在一个未提交的GTID,但这不会影响数据一致性,因为从库的SQL线程在应用该事务时会自动跳过已经在Binlog中但未提交的部分。
并行复制是另一个影响半同步性能的因素。从库的并行复制主要影响SQL线程的应用速度,而半同步复制只关心IO线程的Binlog接收和刷盘。理论上,两者互不影响。但在实际场景中,如果从库的SQL线程应用速度过慢,会导致中继日志堆积,进而影响IO线程的磁盘写入性能,间接拖慢ACK的返回速度。因此,合理配置slave_parallel_workers和slave_parallel_type参数,保持从库的SQL线程不出现明显延迟,也是保障半同步复制稳定性的重要一环。
-- 从库开启基于LOGICAL_CLOCK的并行复制 SET GLOBAL slave_parallel_type = 'LOGICAL_CLOCK'; SET GLOBAL slave_parallel_workers = 4;监控指标体系与实战经验
要真正掌控半同步复制的运行状态,你需要建立一套完整的监控指标体系。除了前面提到的Rpl_semi_sync_master_status和Rpl_semi_sync_master_clients,以下指标同样关键:Rpl_semi_sync_master_yes_tx表示成功通过半同步复制的事务数,Rpl_semi_sync_master_no_tx表示降级为异步复制的事务数。如果no_tx的值在持续增长,说明你的半同步复制正在频繁降级,需要立即排查从库的IO性能和网络状况。
在从库端,Rpl_semi_sync_slave_status表示从库是否启用了半同步,这个值应该始终为ON。如果为OFF,检查从库的IO线程是否正常运行,以及主从之间的网络连通性。还有一个容易被忽视的指标是Rpl_semi_sync_master_net_avg_wait_time,它表示主库等待从库ACK的平均等待时间,单位是微秒。这个值如果突然增大,通常意味着网络延迟增加或从库IO线程出现瓶颈。
在实际生产环境中,我们遇到过这样一个案例:业务高峰期主库的TPS突然下降30%,排查发现Rpl_semi_sync_master_net_avg_wait_time从正常的200微秒飙升到15000微秒。最终定位到原因是从库的磁盘IO性能因为备份任务而饱和,导致中继日志写入变慢。解决方法是调整备份时间窗口,避免与业务高峰期重叠,并将从库的中继日志和数据文件分离到不同的物理磁盘上。这个案例说明,半同步复制的性能瓶颈往往不在MySQL本身,而在于底层的基础设施。
最后,强烈建议在每次主从切换后,立即检查新主库的半同步配置是否生效。很多故障切换工具会自动调整复制关系,但不会自动配置半同步插件。如果新主库没有加载semisync_master插件,或者没有设置rpl_semi_sync_master_enabled = 1,那么整个拓扑就退化成了异步复制,你之前所有的努力都白费了。将半同步配置的检查作为切换流程的强制步骤,写入SOP文档,并自动化执行,这是避免人为失误的最后一道防线。
