MySQL半同步复制的问题

MySQL是一个RDBMS(关系型数据库管理系统),由瑞典MySQL AB 公司开发,目前属于 Oracle 旗下产品。由于其体积小、速度快、拥有成本低,尤其是开放源码这一特点,广受各大企业欢迎,包括腾讯,阿里,百度,网易,Google,FaceBook等互联网巨头企业。

 

随着互联网的高速发展,互联网服务可用性变得越发重要,数据容灾也随之成为各企业的关键任务。在数据容灾中,数据库集群如何处理数据一致性也成为了各企业需要解决的问题。特别在一些新兴的金融服务中,MySQL也逐渐成为其核心数据库,如何保证金钱的准确性则尤为重要。MySQL也从一开始的异步复制,到Google开发的半同步复制,到MySQL 5.7更新的lossless半同步复制,一直在优化集群的数据一致性问题。

 

虽然MySQL一直在优化数据的一致性问题,但问题依然存在,使得各大企业纷纷各自设计一套MySQL补丁来保证数据一致。腾讯数平的TDSQL,腾讯微信的PhxSQL,阿里的AliSQL,网易的InnoSQL等设计都是为了保证数据一致性。MySQL5.7发布的lossless半同步,虽然宣称zero loss,解决了5.6版本中有可能出现的data lost问题,但其数据一致性仍未完全解决。

 

关于MySQL的具体介绍可以参考MySQL官方PPThttp://www.slideshare.net/andrewjamesmorgan/mysql-high-availability-solutions-feb-2015-webinar

 

MySQL半同步复制的问题

图1 MySql半同步流程

 

图1描述了MySQL的Binlog半同步过程。Wait ACK是半同步的关键步骤,Master把Binlog发给Slave之后,需要等待Slave的ACK。Master直到成功收到ACK之后,才执行Engine Commit把数据持久化到Storage。具体细节可参考:http://my-replication-life.blogspot.com/2013/09/loss-less-semi-synchronous-replication.html

 

MySQL启动时,Wait ACK过程会被跳过,导致Engine Commit会被直接执行。具体细节请参考:https://jira.mariadb.org/browse/MDEV-162 

 

下面对MySQL的数据在Master和Slave之间是否能保证一致进行简单分析。讨论均基于各机器数据最终是否一致来展开。下面的分析只针对半同步复制,且假设半同步失败后不会退化成异步复制。

 

场景1:Master正常工作

Master的数据复制到Slave,Slave与Master保持数据一致。

 

场景2:Master Crash且不切换Master

 

场景2.1

Master已经收到ACK,并执行Engine Commit。Slave与Master保持数据一致。

 

场景2.2 

Master处于Wait ACK阶段,存在PendingBinlog(未执行Engine Commit的Binlog)。

图2 Master重启时执行EngineCommit,并把Binlog重新复制给Slave

 

Master重启时执行EngineCommit。Slave重新连接Master,Binlog重新开始复制,随后Slave数据和Master一致。如图2。

 

因此,在MySql5.7的情况下,场景2.2能保证Master和Slave之间的数据一致性。但是在MySQL5.6及之前的版本,场景2.2是不能保证数据一致性的,具体请参考:http://my-replication-life.blogspot.com/2013/09/loss-less-semi-synchronous-replication.html

 

场景3:Master Crash且切换Master

 

场景3.1

 

旧Master Crash时,已经收到至少一台Slave的ACK并执行Engine Commit。

 

数据已复制到至少一台Slave,该Slave与旧Master的数据保持一致。

 

场景3.2

旧Master处于Wait ACK阶段时Crash,新Master被切换到了一台拥有最新Binlog的Slave。

 

场景3.2中,旧Master中的PendingBinlog存在两种场景。

 

场景3.2.1

旧Master Crash时Binlog发送失败,未复制给任何Slave。

图3 机器A重启Commit Transaction X。机器A/B数据不一致。

 

 

    
图4 机器B接收到事务X的重试请求(事务X’)且复制到机器A。

机器A/B数据可能不一致。

 

假设机器A为旧Master,执行事务X时,复制失败并Crash。随后机器B成为新Master。机器A重启时执行Engine Commit,事务X被Commit。此时机器A和机器B的数据一致性被破坏。两台机器上数据可能不一致。如图3,图4。

 

数据不一致的原因是机器A在重启时对PendingBinlog执行Engine Commit。在切换了Master的情况下,只能通过回滚PendingBinlog解决。

 

 

MySQL半同步插件的维护者也提到了类似的想法:http://my-replication-life.blogspot.com/2013/09/loss-less-semi-synchronous-replication.html

 

场景3.2.2

旧Master Crash时Binlog发送成功,但还未执行Engine Commit。

 

图6 机器A重启马上执行Engine Commit,数据一致

 

假设机器A为旧Master,执行事务X时在执行Commit前Crash,但机器B收到事务X。随后机器B成为新Master。

 

机器A重启时对PendingBinlog执行Engine Commit,执行成功后机器A的数据是机器B的子集。此时机器A可从机器B中拉取最新的数据。另外一台Slave机器C可以从这两台机器中任意拉取。

 

从图6可以看出,机器A在出现故障时,由于TransactionX已经复制给其中一台Slave和重启时立刻Commit Transaction X,使得该Slave和Master的数据能保证一致。


图7 两台机器出现故障,Master切换可能会丢失数据

 

上述讨论都是基于拥有最新数据的Slave和Master不能一起出现故障。当这两台机器一起出现故障时,进行Master切换则会造成数据丢失。如图7。

 

对于较小的集群(机器数目小于或者等于3),当出现两台机器一起发生故障时,可认为集群已无法提供服务(半同步复制无法工作)。

 

对于较大的集群(机器数目大于3),当出现两台机器一起发生故障,且无法得知该两台机器的数据状态时,该集群也无法提供服务(无法确认拥有最新数据的Slave是否包含在故障机器中)。因此,对于较大的集群,通常增加半同步复制等待ACK的数目,使得出现上述状况时,仍能进行Master切换(非故障机器中,存在拥有最新数据的机器)。

 

增加等待ACK的数目,解决了数据丢失的问题,但同时给数据回滚带来了难题。

 


图8

 

如图8。假设MySQL集群有5台机器,半同步复制需要等待2台Slave的ACK。机器A为旧Master,在执行Wait ACK阶段,机器B收到Binlog后,机器A和机器B同时Crash或者被隔离,导致Binlog复制失败。根据场景3.2.1的分析,当机器C成为Master后,机器A和机器B在恢复服务前需要对其进行数据回滚。但对Slave进行数据回滚较为困难。且若回滚失败,则会出现数据不一致。

 

对于较小的集群,回滚PendingBinlog比较容易实现。但对于较大的集群,回滚PendingBinlog本身就是一个未解决的难题。

 

MySQL的Master切换问题

Master如何切换同时也是MySQL容灾中的一个难题。

一个简单的Master切换步骤:

1.  Pause旧Master

2.  Start新Master

3.  更换MySQLClient的Master指向IP

 

存在以下几个问题:

1.  当Master被隔离时,如何将其变更为Slave

解决方法:可修改MySQL的代码,使用zookeeper等外部辅助服务来自动维护Master的状态,可解决Master被隔离后不能操作的问题。

 

2.  如何定位拥有最新Binlog数据的MySQL

解决方法:可以通过人工,或者使用外部工具来检测集群每台MySQL的数据。但当出现故障机器无法访问时,无法定位。

 

3.  如何进行数据回滚

解决方法:可以通过运维进行人工操作。

 

4.  如何同时更换MySQLClient的Master指向IP

同时更换所有MySQLClient的Master指向IP是一件不可能的事情,因为不可能同一时刻操作所有机器。

 

不能同时更换所有MySQLClient的Master指向IP,导致部分Client会向旧Master发送请求,即出现多个Master同时服务。在使用半同步复制的情况下,多台MySQL不能同时知道Master的去向,使得数据可能产生不一致的情况。

 

图9


图10

 

假设机器A是旧Master,机器B是新Master,机器C还没收到Master更换的通知仍然向机器A复制Binlog。User1在Master切换前已经连上机器A并持续写入数据。User2在Master切换后开始向机器B写入数据。由于机器A能把数据复制给机器C,机器B能把数据复制给机器A,因此机器A和机器B都能成功写入。如图9。

 

由于机器A和机器B同时写入数据,数据一致性无法保证。如图10。

 

总结

从上面分析来看,MySQL的半同步复制和Master切换都存在一些不足。数据复制存在回滚难题,Master切换存在多Master难题。只有解决了这两大难题,才能保证MySQL集群的数据一致性。