注册 登录
  • 欢迎访问"运维那点事",推荐使用Google浏览器访问,可以扫码关注本站的"微信公众号"。
  • 如果您觉得本站对你有帮助,那么可以扫码捐助以帮助本站更好地发展。

MySQL 5.7无损复制技术

MySQL 5.7 彭东稳 10572次浏览 已收录 2个评论

一、复制架构衍生史

在谈这个特性之前,我们先来看看MySQL的复制架构衍生史。

在2000年,MySQL 3.23.15版本引入了Replication。Replication作为一种准实时同步方式,得到广泛应用。这个时候的Replicaton的实现涉及到两个线程,一个在Master,一个在Slave。Slave的I/O和SQL功能是作为一个线程,从Master获取到event后直接apply,没有relay log。这种方式使得读取event的速度会被Slave replay速度拖慢,当主备存在较大延迟时候,会导致大量binary log没有备份到Slave端。

在2002年,MySQL 4.0.2版本将Slave端event读取和执行独立成两个线程(IO线程和SQL线程),同时引入了relay log。IO线程读取event后写入relay log,SQL线程从relay log中读取event然后执行。这样即使SQL线程执行慢,Master的binary log也会尽可能的同步到Slave。当Master宕机,切换到Slave,不会出现大量数据丢失。

在2010年MySQL 5.5版本之前,一直采用的是这种异步复制的方式。主库的事务执行不会管备库的同步进度,如果备库落后,主库不幸crash,那么就会导致数据丢失。于是在MySQL在5.5中就顺其自然地引入了半同步复制,主库在应答客户端提交的事务前需要保证至少一个从库接收并写到relay log中。那么半同步复制是否可以做到不丢失数据呢?下面分析。

在2016年,MySQL在5.7.17中引入了一个全新的技术,称之为InnoDB Group Replication。目前官方MySQL 5.7.17基于Group replication的全同步技术已经问世,全同步技术带来了更多的数据一致性保障。相信是未来同步技术一个重要方向,值得期待。MySQL 5.7 Group Replication

根据上面提到的这几种复制协议,分别对应MySQL几种复制类型,分别是异步、半同步、全同步。

MySQL 5.7无损复制技术

  • 对于异步复制,主库将事务Binlog事件写入到Binlog文件中,此时主库只会通知一下Dump线程发送这些新的Binlog,然后主库就会继续处理提交操作,而此时不会保证这些Binlog传到任何一个从库节点上。
  • 对于全同步复制,当主库提交事务之后,所有的从库节点必须收到,APPLY并且提交这些事务,然后主库线程才能继续做后续操作。这里面有一个很明显的缺点就是,主库完成一个事务的时间被拉长,性能降低。
  • 对于半同步复制,是介于全同步复制和异步复制之间的一种,主库只需要等待至少一个从库节点收到并且Flush Binlog到Relay Log文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全执行并且提交的反馈,这样就节省了很多时间。

二、半同步复制原理

我们知道MySQL的复制是依靠主库二进制日志传输到从库进行回放来工作。先来看一看MySQL默认的异步复制模式下关于数据丢失的问题?这里假设sync_binlog=1,如果不等于1,binlog在主库的传输时间点不同,具体看后面的分析。

1)正常的复制为:主库事务t1在生出event并写入binlog cache之后,然后flush binlog & sync binlog & update binlog position。此时主库dump线程发送t1事务binlog到slave,slave的io线程接收到t1并写入到自己的的relay log,之后sql线程回放binlog。这时,master和slave都能看到最新事务数据,即使master挂了,将slave提升为新的master也是可以的。

2)异常的复制为:主库事务t1在生出event并写入binlog cache之后,然后flush binlog & sync binlog & update binlog position。此时主库dump线程发送t1事务binlog到slave,slave因为网络不稳定,一直没有收到t1事务binlog。这时,由于master并不知道从库是不是已经收到或处理了这些Binlog。在这种情况下,如果master挂掉了,将slave提升为新的master,t1事务丢失。

3)很大的问题是:主库和从库事务更新的不同步,就算是没有网络或者其他系统的异常,当业务并发上来时,slave因为要顺序执行master批量事务,导致很大的延迟。

为了弥补异步复制数据丢失问题,MySQL从5.5版本开始推出了半同步复制插件。相比异步复制,半同步复制在一定程度上提高了数据一致性,但并不能保证数据完全不丢失。下面就是来说说半同步复制的工作原理,以及为什么不能保证数据完全不丢失。

先看一下半同步复制原理图,如下:

MySQL 5.7无损复制技术

从图中可以看到,Binlog在写入到二进制日志文件(事务提交的Flush阶段)后就进行了推送操作,将所有events都通过dump线程发送到从库。这里sync_binlog<>1,如果等于1,那么推送events将发生在事务提交的Sync阶段,就是Binlog落盘后。半同步就在于dump线程不光是发送events,还发送了一个ACK确认请求标志,用来确认Slave是否安全收到相关events。同时,主库在InnoDB引擎层提交事务,一旦提交完成后,最新数据对其他事务可见,其他会话会读取到最新数据。然后,主库在提交完成后等待Slave ACK确认消息,但是需要注意是此时还并没有给当前线程返回提交成功的信息,也就是说当前线程还在等待数据库返回消息中。关于这点是可以测试的,有兴趣可以测试看看。

测试步骤如下:

1. 主从开启半同步复制,并保证复制正常。

2. 关闭从库半同步复制,此时在主库执行事务;由于从库半同步关闭了,主库执行第一个事务时会等待一个超时时间,然后转为异步复制。

3. 测试关键,在主库等待事务期间(此时并没有给当前线程返回执行成功信息),再开启一个会话是可以查询到最新的数据的。

当Binlog传递到某个slave IO线程并记录relay log后,是否刷盘取决于sync_relay_log=1,然后slave反馈ACK消息给master。也就是dump线程除了发送events到slave,还承担了接收slave ACK的工作。如果出现异常,master没有收到slave ACK,那么将自动降级为异步复制,直到异常修复后又会自动变为半同步复制。

半同步复制具体特性:

  • 从库会在连接到主库时告诉主库,它是不是配置了半同步。
  • 如果半同步复制在主库端是开启了的,并且至少有一个半同步复制的从库节点,那么此时主库的事务线程在提交后会被阻塞并等待。此时,等待的结果有两种可能,要么至少一个从库节点通知它已经收到了所有这个事务的Binlog事件,要么一直等待直到超过配置的某一个时间点为止。而此时,半同步复制将自动关闭,转换为异步复制。
  • 从库节点只有在接收到某一个事务的所有Binlog,将其写入并Flush到Relay Log文件之后,才会通知对应主库上面的等待线程。
  • 如果在等待过程中,等待时间已经超过了配置的超时时间,没有任何一个从节点通知当前事务,那么此时主库会自动转换为异步复制,当至少一个半同步从节点赶上来时,主库便会自动转换为半同步方式的复制。
  • 半同步复制必须是在主库和从库两端都开启时才行,如果在主库上没打开,或者在主库上开启了而在从库上没有开启,主库都会使用异步方式复制。

通过上面几点特征,可以知道半同步的实质是,在主库被阻塞的过程中(等待从库反馈确认消息),主库处理线程不会返回去处理当前事务。当阻塞被激活之后,系统才会把控制权交给当前线程,然后继续处理当前事务余下的事情。处理完成之后,此时主库的事务已经提交,同时至少会有一个从库也已经收到了这个事务的Binlog,这样就尽可能地保证了主库和从库的数据一致性。

在MySQL 5.5~5.6使用after_commit的模式下,客户端事务在存储引擎层提交后,在得到从库确认的过程中,主库宕机了。此时,即主库在等待Slave ACK的时候,虽然没有返回当前客户端,但事务已经提交,其他客户端会读取到已提交事务。如果Slave端还没有读到该事务的events,同时主库发生了crash,然后切换到备库。那么之前读到的事务就不见了,出现了幻读。如下图所示,图片引自Loss-less Semi-Synchronous Replication on MySQL 5.7.2

MySQL 5.7无损复制技术

如果主库永远启动不了,那么实际上在主库已经成功提交的事务,在从库上是找不到的,也就是数据丢失了,这是MySQL不愿意看到的。所以在MySQL 5.7版本中增加了after_sync(无损复制)参数,并将其设置为默认半同步方式,解决了数据丢失的问题。

关于半同步复制的具体配置和状态参数说明,参见:MySQL主从复制配置

三、无损复制原理

现在我们已经知道,在半同步环境下,主库是在事务提交之后等待Slave ACK,所以才会有数据不一致问题。所以这个Slave ACK在什么时间去等待,也是一个很关键的问题了。因此MySQL针对半同步复制的问题,在5.7.2版本引入了Loss-less Semi-Synchronous,在调用binlog sync之后,engine层commit之前等待Slave ACK。这样只有在确认Slave收到事务Binlog后,事务才会提交。另外,在commit之前等待Slave ACK,同时可以堆积事务,利于Group Commit,有利于提升性能。

半同步复制与无损复制的主要区别在于半同步复制在InnoDB commit后等待Slave ACK(需要收到至少一个Slave节点回复的ACK),无损复制在binlog sync后与Slave确认。虽然都同样避免不了数据丢失的风险,但是由于ack确认的位置不同,这样就有一个大的区别在于其他事务是否看得见这个事务的修改操作,半同步复制由于在InnoDB commit后ack,此时事务已经提交,对其实事务可见,如果此时主库宕机并发生主从切换,那么用户在新主库找不到刚刚那个事务修改后的数据,就可以称得上数据丢失了,因为用户已经看见过。而无损复制由于在binlog sync后进行binlog发送和ack确认,此时由于事务并没有提交,对于其他事务来说不可见,所以就算发生主从切换,新主库虽然也没有刚刚那个事务修改后的数据,但用户并没有看见新数据,所以也就称不上数据丢失了。

无损复制工作模式如下图:

MySQL 5.7无损复制技术

虽然无损复制从某种意义上来说不会丢失数据。但是也同样存在一个问题,如果主库数据传输到Slave后,Slave进行ack确认时主库宕机,那么当主库重新启动后由于redo和binlog都有事务信息,所以这个或这组事务必然会提交成功,那么主从数据一致,皆大欢喜。那么如果说主库数据在传输到从库的过程中宕机,那么从库必然没有最新binlog,当主库重启后同样会提交最后这个或这组事务,此时就意味着主从数据不一致。如果你想让老的主库做新主库的从库,就需要人工干预处理了。这也是基于无损复制做高可用需要考虑的。

MySQL 5.7安装半同步模块,命令如下:

看一下相关状态信息

无损复制其实就是对semi sync增加了rpl_semi_sync_master_wait_point参数,来控制半同步模式下主库在返回给会话事务成功之前提交事务的方式。rpl_semi_sync_master_wait_point该参数有两个值:AFTER_COMMIT和AFTER_SYNC,其中AFTER_COMMIT表示使用半同步复制,而AFTER_SYNC表示使用无损复制,MySQL 5.7默认就是AFTER_SYNC。另外,5.7还增加了rpl_semi_sync_master_wait_for_slave_count参数,用来控制主库至少收到几个Slave ACK后才提交事务,增加了灵活性。

半同步复制与无损复制的对比总结:

1. ACK的时间点不同

  • 半同步复制在InnoDB层的Commit Log后等待ACK,主从切换会有数据丢失风险。
  • 无损复制在MySQL Server层的Write binlog后等待ACK,主从切换不会有数据丢失风险,但是主从有不一致的风险。

2. 主从数据一致性

  • 半同步复制意味着在Master节点上,这个刚刚提交的事务对数据库的修改,对其他事务是可见的。因此,如果在等待Slave ACK的时候crash了,那么会对其他事务出现幻读,数据丢失。
  • 无损复制在write binlog完成后,就传输binlog,但还没有去写commit log,意味着当前这个事务对数据库的修改,其他事务也是不可见的。因此,不会出现幻读,数据丢失风险。

因此5.7引入了无损复制(after_sync)模式,带来的主要收益是解决after_commit导致的Master crash后数据丢失问题,因此在引入after_sync模式后,所有提交的数据已经都被复制,故障切换时数据一致性将得到提升。

四、MySQL 5.7半同步优化

  • 性能提升,支持发送binlog和接受ack的异步化

旧版本的semi sync受限于dump thread ,原因是dump thread承担了两份不同且又十分频繁的任务:传送binlog给slave ,还需要等待slave反馈信息,而且这两个任务是串行的,dump thread必须等待slave返回之后才会传送下一个events事务。dump thread已然成为整个半同步提高性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库整体的TPS 。

MySQL 5.7无损复制技术

为了解决上述问题,在5.7版本的semi sync框架中,独立出一个Ack Receiver线程 ,专门用于接收slave返回的ack请求,这将之前dump线程的发送和接受工作分为了两个线程来处理。这样master上有两个线程独立工作,可以同时发送binlog到slave,和接收slave的ack信息。因此半同步复制得到了极大的性能提升。这也是MySQL 5.7发布时号称的Faster semi-sync replication。

MySQL 5.7无损复制技术

但是在MySQL 5.7.17之前,这个Ack Receiver线程采用了select机制来监听slave返回的结果,然而select机制监控的文件句柄只能是0-1024,当超过1024时,用户在MySQL的错误日志中或许会收到类似如下的报错,更有甚者会导致MySQL发生宕机。

semi-sync master failed on net_flush() before waiting for slave reply.

MySQL 5.7.17版本开始,官方修复了这个bug,开始使用poll机制来替换原来的select机制,从而可以避免上面的问题。其实poll调用本质上和select没有区别,只是在I/O句柄数理论上没有上限了,原因是它是基于链表来存储的。但是同样有缺点:比如大量的fd的数组被整体复制于用户态和内核地址空间之间,而不管这样的复制是不是有意义。poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd。

其实在高性能软件中都是用另外一种调用机制,名为epoll,高性能的代表,比如Nginx,haproxy等都是使用epoll。可能poll的复杂性比epoll低,另外对于ack receiver线程来说可能poll足矣。

  • 性能提升,控制主库接收slave写事务成功反馈数量

MySQL 5.7新增了rpl_semi_sync_master_wait_slave_count参数,可以用来控制主库接收多少个slave写事务成功反馈,给高可用架构切换提供了灵活性。如图所示,当count值为2时,master需等待两个slave的ack。

MySQL 5.7无损复制技术

  • 性能提升,Binlog互斥锁改进

旧版本半同步复制在主提交binlog的写会话和dump thread读binlog的操作都会对binlog添加互斥锁,导致binlog文件的读写是串行化的,存在并发度的问题。

MySQL 5.7无损复制技术

MySQL 5.7对binlog lock进行了以下两方面优化:

1. 移除了dump thread对binlog的互斥锁。

2. 加入了安全边际保证binlog的读安全。

MySQL 5.7无损复制技术

可以看到从replication功能引入后,官方MySQL一直在不停的完善,前进。同时我们可以发现当前原生的MySQL主备复制实现实际上很难在满足数据一致性的前提下做到高可用、高性能。

  • 性能提升,充分利用组提交特性

MySQL 5.7无损复制技术

上图是Facebook的测试性能图,其中Y轴是QPS,X轴是并发数。

Normal Slave/异步复制:性能很好,但是随着并发数的增长,性能有所下降。

Enhanced mysqlbinlog/无损复制:随着并发数的增长,性能几乎是线性增长的,在高并发下,性能会优于异步复制。

Normal Semi Slave/半同步复制:性能较低。

从Facebook测试报告可以看出,无损复制性能比半同步复制要高一倍,甚至在高并发下比异步复制性能还要好。无损复制性能优于半同步复制的原因在于无损复制由于在sync binlog后等待,后续的事务无法提交,这样就堆积多个需要落盘的事务,通过组提交机制一次fsync的多个事务,充分发挥了组提交的作用,并发越高性能相比半同步就越好,因为一次提交的事务变多了,降低了磁盘IO。半同步由于在commit后等待,事务都已经提交了,所以无法利用组提交。而双方在等待ACK回包问题上,其实两种复制的开销是一样的,没有区别,都是网络的等待开销。

而无损复制可能会比异步复制性能只有一些前提的,首先高并发,其次属于io-bound类型,io-bound是指缓冲池10G,数据100G,需要大量与磁盘打交道;如果数据100G,缓冲池也100G,就属于cpu-bound。在测试模式为io-bound类型时,由于需要大量IO开销,所以如果能充分利用组提交就能减少磁盘开销。

五、参数sync_binlog/sync_relay与半同步复制

  • sync_binlog的配置

其实无损复制流程中也会存在着会导致主备数据不一致的情况,使主备同步失败的情形。先说一下sync_binlog不同参数产生的不同操作行为。

当sync_binlog=0时,二进制日志从不进行二进制日志FSYNC(同步)到磁盘上,而是依赖操作系统刷盘机制来刷新二进制日志到磁盘。如果存在主从复制,那么主库dump线程会在flush阶段后进行binlog传输到从库。

当sync_binlog=1时,在没有组提交特性之前,每个事务在commit时都必须要FSYNC二进制日志到磁盘;有了组提交特性后,就成了每次组提交时进行FSYNC刷盘。如果存在主从复制,主库dump线程会在sync阶段后进行binlog传输。

当sync_binlog>1时,binlog将在指定次数组提交后进行FSYNC刷盘。如果存在主从复制,主库dump线程会在flush阶段后进行binlog传输。

源码剖析sync_binlog配置

配置分析,当sync_binlog为0的时候,binlog sync磁盘由操作系统负责。当不为0的时候,其数值为定期sync磁盘的binlog commit group数。通过源码我们知道,sync_binlog值不等于1的时候事务在FLUSH阶段就传输binlog到从库了,而值为1时,binlog同步操作是在SYNC阶段后。当sync_binlog值大于1的时候,sync binlog操作可能并没有使binlog落盘。如果没有落盘,事务在提交前,Master掉电,然后恢复,那么这个时候该事务被回滚。但是Slave上可能已经收到了该事务的events并且执行,这个时候就会出现Slave事务比Master多的情况,主备同步会失败。所以如果要保持主备一致,需要设置sync_binlog为1。

WAIT_AFTER_SYNC和WAIT_AFTER_COMMIT两图中Send Events的位置,也可能导致主备数据不一致,出现同步失败的情形。实际在rpl_semi_sync_master_wait_point分析的图中是sync binlog大于1的情况。根据上面源码,流程如下图所示。Master依次执行flush binlog, update binlog position, sync binlog。如果Master在update binlog position后,sync binlog前掉电,Master再次启动后原事务就会被回滚。但可能出现Slave获取到Events,这也会导致Slave数据比Master多,主备同步失败。

MySQL 5.7无损复制技术

由于上面的原因,sync_binlog设置为1的时候,MySQL会update binlog end pos after sync。流程如下图所示。这时候,对于每一个事务都需要sync binlog,同时sync binlog和网络发送events会是一个串行的过程,性能下降明显。

MySQL 5.7无损复制技术

  • sync_relay_log的配置

源码剖析

配置分析,在Slave的IO线程中get_sync_period获得的是sync_relay_log的值,与sync_binlog对sync控制一样。当sync_relay_log不是1的时候,semisync返回给Master的position可能没有sync到磁盘。在gtid_mode下,在保证前面两个配置正确的情况下,sync_relay_log不是1的时候,仅发生Master或Slave的一次Crash并不会发生数据丢失或者主备同步失败情况。如果发生Slave没有sync relay log,Master端事务提交,客户端观察到事务提交,然后Slave端Crash。这样Slave端就会丢失掉已经回复Master ACK的事务events。

MySQL 5.7无损复制技术

但当Slave再次启动,如果没有来得及从Master端同步丢失的事务Events,Master就Crash。这个时候,用户访问Slave就会发现数据丢失。

MySQL 5.7无损复制技术

通过上面这个Case,MySQL semisync如果要保证任意时刻发生一台机器宕机都不丢失数据,需要同时设置sync_relay_log为1。对relay log的sync操作是在queue_event中,对每个event都要sync,所以sync_relay_log设置为1的时候,事务响应时间会受到影响,对于涉及数据比较多的事务延迟会增加很多。但关于从库Crash后数据安全问题,可以通过MySQL 5.6提供的两个参数保证,具体看“MySQL从库CrashSafe问题?”。

  • MySQL三节点

在一主一从的主备semisync的数据一致性分析中放弃了高可用,当主备之间网络抖动或者一台宕机的情况下停止提供服务。要做到高可用,很自然我们可以想到一主两从,这样解决某一网络抖动或一台宕机时候的可用性问题。但是,前文叙述要保证数据一致性配置要求依然存在,即正常情况下的性能不会有改善。同时需要解决Master宕机时候,如何选取新主机的问题,如何避免多主的情形。

MySQL 5.7无损复制技术

选取新主机时一定要读取两个从机,看哪一个从机有最新的日志,否则可能导致数据丢失。这样的三节点方案就类似分布式Quorum机制,写的时候需要保证写成功三节点中的法定集合,确定新主的时候需要读取法定集合。利用分布式一致性协议Paxos/Raft可以解决数据一致性问题,选主问题和多主问题,因此近些年,国内数据库团队大多实现了基于Paxos/Raft的三节点方案。近来MySQL官方也以插件形式引入了支持多主集群的Group Replication方案。

<参考>

滚蛋吧,MySQL主从复制延迟 #M1002#

从一个调试结果来看mysql5.7的无损复制

MySQL · 源码分析 · MySQL 半同步复制数据一致性分析

MySQL 5.7中sync_binlog参数和半同步中after_commit和after_sync的区别


如果您觉得本站对你有帮助,那么可以支付宝扫码捐助以帮助本站更好地发展,在此谢过。
喜欢 (16)or分享 (0)
关于作者:

您必须 登录 才能发表评论!

(2)个小伙伴在吐槽
  1. “这个问题可以这样理解,作为MySQL,在没办法解决分布式数据一致性问题的情况下,它能保证的是不丢数据,多了数据总比丢数据要好。” 这句话有问题,如果是个update或者delete操作,数据被更改或删除对用户而言仍然是丢失的。其实这个地方本身还未回复ACK给用户,并没有承诺结果,所以对于数据是否真正写入是对于用户个未知数(常说的分布式的三态:成功、失败、未知),这个是分布式的特点,无法避免。
    jefferent2018-03-23 01:16 Windows 10 | Chrome 64.0.3282.186
    • 彭东稳2018-03-23 16:20 未知操作系统 | Chrome 63.0.3239.132