原创:Perry.Zhang

发表:Jane.Hoo

主从复制导致数据不一致的场景

MySQL主从复制原理:MySQL主库在事务提交时写binlog,并通过sync_binlog参数来控制binlog刷新到磁盘“落地”。从库中有两个线程: IO线程负责从主库读取binlog,并记录到本地的relay log中;SQL线程再将relay log中的记录应用到从库。如下图所示:

42f0374b6255fcdba508178200adf33c.png

1.binlog刷新机制     master写binlog与innodb引擎写redo类似,由参数sync_binlog控制:

= 0 :表示MySQL不控制binlog的刷新,由文件系统控制binlog cache的刷盘操作

= N :表示每sync_binlog在N次事务提交后,MySQL调用文件系统的flush操作将binlog cache中的内容刷盘

sync_binlog=1时最安全,即表示每次事务提交,MySQL都会把binlog cache中的内容flush disk。这样在掉电等情况下,系统只有可能丢失1个事务的数据。但是sync_binlog为1时,系统的IO消耗非常大。

但是N的值也不易过大,否则在系统掉电时会丢失较多的事务。当前我们生产系统设置为100.

2.内部XA事务原理    MySQL的存储引擎与MySQL服务层之间,或者存储引擎与存储引擎之间的分布式事务,称之为MySQL内部XA事务。最为常见的内部XA事务存在与binlog与InnoDB存储引擎之间。在事务提交时,先写二进制日志,再写InnoDB存储引擎的redo log。对于这个操作要求必须是原子的,即需要保证两者同时写入。内部XA事务机制就是保证两者的同时写入。

XA事务的大致流程:

1)事务提交后,InnoDB存储引擎会先做一个PREPARE操作,将事务的XID写入到redo log中

2)写binlog

3)将该事务的commit信息写到redo log中

cc379840210ea6b3fd64c2589b9d1ead.png

如果在步骤1和步骤2失败的情况下,整个事务会回滚,如果在步骤3失败的情况下,MySQL数据库在重启后会先检查PREPARE的XID事务是否已经提交,若没有,则在存储引擎层再进行一次提交操作。这样就保证了redo与binlog的一致性,防止丢失事务。

3.主库写redo log、binlog不实时造成的数据不一致     上面我们介绍了MySQL的内部XA事务流程,但是这个流程并不是天衣无缝的,redo的ib_logfile与binlog日志如果被设置非实时flush,就有可能出现以下数据不一致的情况:

1)Redo log的trx_prepare未写入,但binlog已写入,则crash recovery后从库数据比主库多。

2)Redo log的trx_prepare与commit都写入了,但binlog未写入,则crash recovery后从库数据量比主库少。

从目前来看,只能牺牲性能去换取数据的安全性,必须要设置redo log和binlog为实时刷盘,如果对性能要求很高,则考虑使用SSD来替代机械盘。

4.从库写redo log、binlog不实时造成的数据不一致     主库正常,但是从库出现异常情况宕机,如果数据丢失,从库的SQL线程还会重新应用吗?这个我们需要先了解SQL线程的机制。

从库读取主库的binlog日志后,需要落地3个文件:

relay log:即IO Thread读取过来的主库binlog,内容格式与主库的binlog一致

relay log info:记录SQL Thread应用的relay log的位置、文件号等信息

master info:记录IO Thread读取主库的binlog的位置、文件号、延迟等信息

因此如果当这3个文件如果不及时落地,则system crash后会导致数据的不一致。

在MySQL 5.6.2之前,从库记录的主库信息以及从库应用binlog的信息存放在文件中,即master.info与relay-log.info。在5.6.2版本之后,允许记录到table中,参数设置如下:

master-info-repository  = TABLE  relay-log-info-repository = TABLE  对应的表分别为mysql.slave_master_info与mysql.slave_relay_log_info,且这两个表均为innodb引擎表。

master info与relay info还有3个参数控制刷新:

1)sync_relay_log:默认为10000,即每10000次sync_relay_log事件会刷新到磁盘。为0则表示不刷新,交由OS的cache控制。

2)sync_master_info:若master-info-repository为FILE,当设置为0时,则每次sync_master_info事件都会刷新到磁盘,默认为10000次刷新到磁盘;若master-info-repository为TABLE,当设置为0时,则表不做任何更新,设置为1,则每次事件会更新表。默认为10000。

3)sync_relay_log_info:若relay_log_info_repository为FILE,当设置为0时,交由OS刷新磁盘,默认为10000次刷新到磁盘;若relay_log_info_repository为TABLE,则无论为任何值,每次event都会更新表。

如果参数设置如下:

sync_relay_log = 1

sync_master_info = 1  sync_relay_log_info = 1  master-info-repository  = TABLE  relay-log-info-repository = TABLE

将导致调用fsync()/fdatasync()随着master的事务的增加而增加,且若slave的binlog和redo也实时刷新的话,会带来很严重的IO性能瓶颈。

5.主库宕机后无法及时恢复造成的数据不一致     当主库出现故障后,binlog未及时拉到从库中,或者各个从库收到的binlog不一致(多数是由于网络原因)。且主库无法在第一时间恢复:

1)如果主库不切换,则应用只能读写主库。如果有读写分离的场景则会影响应用(读写分离场景中从库会从)。

2)如果将某一从库提升为新的主库(如MHA),那么原主库未来得及传到从库的binlog数据则会丢失,并且还涉及到下面2个问题:

a)各个从库之间接收到的binlog不一致,如果强制拉起一个从库做新主库,则从库之间数据会不一致。

b)原主库恢复正常后,由于新的主库日志丢弃了部分原主库的binlog日志,那么会多出来故障时期的这部分binlog。

对于上面出现的问题,业内已经有较成熟的方法来解决:

5.1确保binlog全部传到从库    方案一:使用semisync replication(半同步复制)插件。半同步复制的特点是从库中有一台提交后,主库才能提交事务。优点是保证了主、从库的数据一致性;缺点是对性能影响很大,依赖网络,适合tps压力小的场景。

方案二:双写binlog,通过DBDR OS层的文件系统复制到备机,或者使用共享盘保存binlog日志。优点和方案一类似,但此方案缺点较明显:

1)DBDR需要部署自己的服务

2)DBDR脑裂严重。在发生灾难场景时,往往不能正确切换。

3)需要建立heartbeat机制。保证被监控机的存活。

方案三:架构层面调整,引入消息队列做异步消息处理。比如保证数据库写成功后,再异步队列的方式写一份,部分业务可以借助设计和数据流解决。

5.2保证数据最小化丢失    上面的方案设计及架构比较复杂,如果能容忍数据的丢失,可以考虑使用MHA。

当master宕机后,MHA可以指定一台或者选延迟最低或者binlog pos最新的一台从库,并将其提升为主库。

MHA在切换master后,原master可以修复后以新master的slave角色重新加入集群。从而达到高可用。

总结:      通过上面的总结分析,MySQL丢数据的场景众多,主要还是涉及到引擎层数据丢失场景、主从的数据不一致场景等。

根据分布式领域的CAP理论(Consistency一致性、Availability高可用性、Partition tolerance分区耐受性),在任何的分布式系统只能同时满足2点,没办法三者兼顾。MySQL的主从环境满足Availability,且在半同步场景中数据可以做到数据强一致性,可以满足Consistency。但是不能完全满足Partition tolerance。

因此现在业内对于事务(数据)丢失的处理有很多解决方案,如事务补偿机制、半同步复制、双写机制、异步消息队列等等。甚至,还可以针对业务对CAP中哪两者更有需求来选择相应的数据产品,如需要分区耐受性和高可用兼顾时,可以使用Cassandra等列式存储。都可以达到业务相应的数据一致性需求。

来自 “ ITPUB博客 ” ,链接:http://blog.itpub.net/30109892/viewspace-1814520/,如需转载,请注明出处,否则将追究法律责任。

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐