在MySQL中,可以使用多种存储引擎。其中最常用的InnoDB引擎支持事务,Redo Log和Undo Log就是InnoDB里面的工具,用于实现事务。而Binlog是MySQL层面的东西,用于实现主从复制,与使用的存储引擎无关。
通过监听并解析Mater的Binlog,也可以实现将MySQL中的数据同步到其他应用组件中(比如更新缓存)的效果。
在不发生宕机的情况下,未提交的事务和已回滚的事务是不写入Binlog日志中的,只有提交成功的事务才写入Binlog日志。这一点和Redo Log不一样,Redo Log中会记录未提交、已回滚的事务内容。
Binlog是一种逻辑日志——例如Binlog的statement格式记录原始SQL语句、RAW格式记录某一行修改前后的值——且一个事务的日志在Binlog中是连续排列的,因此要求每个事务都要串行地写入,这意味着每个事务在写Binlog之前都要排他地锁住Binlog,这会导致写的效率很低。MySQL5.6之后,通过pipline技术异步地批量化将已提交的事务内容写入Binlog。
一个事务的提交既要写Binlog日志又要写Redo Log日志,如何保证双写的原子性?一个写成功,写另外一个时发生宕机,重启后如何处理?在讨论这个问题之前,先说下Binlog自身写入的原子性问题:Binlog刷盘到一半,出现宕机,这个问题和Redo Log的写入原子性是同样的问题,通过类似于checksum的办法或者Binlog中的结束标记来判断出某个事务的Binlog这是不是不完整的Binlog,从而把不完整的部分截掉。对于客户端来说,此时宕机,事务肯定是没有提交成功的,所以截掉也没问题。下面来讲如何保证双写Binlog和Redo Log的原子性。由于双写Binlog和Redo Log发生在同一台机器上,这其实是一个内部分布式事务,可以使用两阶段提交法来实现双写的原子性。简单来说就是:
1)第一阶段(准备阶段):MySQL Server要求innoDB完成将事务内容写入Redo Log中的工作,只等事务提交;以及,MySQL Server完成Binlog内容写入内存的工作,只等刷盘。两个都准备好之后,会向MySQL Server发送OK反馈,MySQL Server紧接着执行第二阶段。
2)第二阶段(提交阶段):收到客户端的Commit指令,MySQL Server先将内存中的Binlog刷盘,然后让innoDB执行事务的提交。两个都完成之后,会向MySQL Server发送OK反馈,两阶段提交结束。
若双写Binlog和Redo Log的过程中发生宕机,处理思路为:
1)若宕机发生在第一阶段,此时Binlog还在内存中,宕机导致全部消失。而Redo Log记录了未提交的日志,MySQL Server重启后感知到Binlog中不存在Redo Log中记录的未提交事务,会自行回滚未提交事务的Redo Log日志;
2)若宕机发生在第二阶段,Binlog写了一半,innoDB还未执行提交,MySQL Server重启后会对Binlog做截断,对Redo Log中记录的未提交事务做回滚;
3)若宕机发生在第二阶段,Binlog写入成功,innoDB还未执行提交,MySQL Server重启后会通过checksum的办法或者Binlog中的结束标记感知到Binlog写入成功,紧接着对Binlog中存在的、但Redo Log未提交的事务发起提交。
在MySQL的Master / Slave集群模式中,有三种主从复制模式:
1)同步复制:所有的Slave都收到Master发送的Binlog,并且接收完,Master才认为事务提交成功,再对客户端返回成功。这种方式最安全,但是性能很差;
2)异步复制:只要Master事务提交成功,就对客户端返回成功。后台线程异步地将Binlog发送给Slave,然后Slave回放Binlog。这种方式性能最好,但是可能会导致数据丢失;
3)半同步复制:Master事务提交后,同时把Binlog同步给Slave,只要有部分(数量可以配置)Slave收到了Binlog,就认为事务提交成功,对客户端返回。
对于半异步复制,如果Slave超时后还未返回,也会退化为异步复制。所以无论是异步复制还是半异步复制,都无法严格保证主从中的数据完全一致,主从复制的延迟会导致主节点宕机后部分数据未来得及同步到从节点,从而丢失数据。但是主节点宕机后,还是要立即切换到从节点,保证服务的可用(牺牲一致性保证可用性),数据的丢失可以通过后续的人工干预来补偿。
要保证MySQL数据不丢失,replication是一个很好的解决方案,而MySQL也提供了一套强大的replication机制。只是我们需要知道,为了性能考量,replication是采用的asynchronous模式,也就是写入的数据并不会同步更新到slave上面,如果这时候master当机,我们仍然可能会面临数据丢失的风险。
为了解决这个问题,我们可以使用semi-synchronous replication,semi-synchronous replication的原理很简单,当master处理完一个事务,它会等待至少一个支持semi-synchronous的slave确认收到了该事件并将其写入relay-log之后,才会返回。这样即使master当机,最少也有一个slave获取到了完整的数据。
但是,semi-synchronous并不是100%的保证数据不会丢失,如果master在完成事务并将其发送给slave的时候崩溃,仍然可能造成数据丢失。只是相比于传统的异步复制,semi-synchronous replication能极大地提升数据安全。更为重要的是,它并不慢,MHA的作者都说他们在facebook的生产环境中使用了semi-synchronous(这里),所以我觉得真心没必要担心它的性能问题,除非你的业务量级已经完全超越了facebook或者google。在这篇文章里面已经提到,MySQL 5.7之后已经使用了Loss-Less Semi-Synchronous replication,所以丢数据的概率已经很小了。
如果真的想完全保证数据不会丢失,现阶段一个比较好的办法就是使用gelera,一个MySQL集群解决方案,它通过同时写三份的策略来保证数据不会丢失。笔者没有任何使用gelera的经验,只是知道业界已经有公司将其用于生产环境中,性能应该也不是问题。但gelera对MySQL代码侵入性较强,可能对某些有代码洁癖的同学来说不合适了。
我们还可以使用drbd来实现MySQL数据复制,MySQL官方文档有一篇文档有详细介绍,但笔者并未采用这套方案,MHA的作者写了一些采用drdb的问题,在这里,仅供参考。
网友评论