在谈这个特性以前,咱们先来看看MySQL的复制架构衍生史。html
在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端。mysql
在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,不会出现大量数据丢失。sql
在2010年MySQL 5.5版本以前,一直采用的是这种异步复制的方式。主库的事务执行不会管备库的同步进度,若是备库落后,主库不幸crash,那么就会致使数据丢失。因而在MySQL在5.5中就顺其天然地引入了半同步复制,主库在应答客户端提交的事务前须要保证至少一个从库接收并写到relay log中。那么半同步复制是否能够作到不丢失数据呢?下面分析。shell
在2016年,MySQL在5.7.17中引入了一个全新的技术,称之为InnoDB Group Replication。目前官方MySQL 5.7.17基于Group replication的全同步技术已经问世,全同步技术带来了更多的数据一致性保障。相信是将来同步技术一个重要方向,值得期待。MySQL 5.7 Group Replication数据库
根据上面提到的这几种复制协议,分别对应MySQL几种复制类型,分别是异步、半同步、全同步。数组
咱们今天谈论第二种架构。咱们知道,普通的replication,即MySQL的异步复制,依靠MySQL二进制日志也即binary log进行数据复制。好比两台机器,一台主机(master),另一台是从机(slave)。安全
1)正常的复制为:事务一(t1)写入binlog buffer;dumper线程通知slave有新的事务t1;binlog buffer进行checkpoint;slave的io线程接收到t1并写入到本身的的relay log;slave的sql线程写入到本地数据库。 这时,master和slave都能看到这条新的事务,即便master挂了,slave能够提高为新的master。服务器
2)异常的复制为:事务一(t1)写入binlog buffer;dumper线程通知slave有新的事务t1;binlog buffer进行checkpoint;slave由于网络不稳定,一直没有收到t1;master挂掉,slave提高为新的master,t1丢失。网络
3)很大的问题是:主机和从机事务更新的不一样步,就算是没有网络或者其余系统的异常,当业务并发上来时,slave由于要顺序执行master批量事务,致使很大的延迟。session
为了弥补以上几种场景的不足,MySQL从5.5开始推出了半同步复制。相比异步复制,半同步复制提升了数据完整性,由于很明确知道,在一个事务提交成功以后,这个事务就至少会存在于两个地方。即在master的dumper线程通知slave后,增长了一个ack(消息确认),便是否成功收到t1的标志码,也就是dumper线程除了发送t1到slave,还承担了接收slave的ack工做。若是出现异常,没有收到ack,那么将自动降级为普通的复制,直到异常修复后又会自动变为半同步复制。
半同步复制具体特性:
半同步复制潜在问题:
先看一下半同步复制原理图,以下:
master将每一个事务写入binlog(sync_binlog=1),传递到slave刷新到磁盘(sync_relay=1),同时主库提交事务(commit)。master等待slave反馈收到relay log,只有收到ACK后master才将commit OK结果反馈给客户端。
在MySQL 5.5~5.6使用after_commit的模式下,客户端事务在存储引擎层提交后,在获得从库确认的过程当中,主库宕机了。此时,即主库在等待Slave ACK的时候,虽然没有返回当前客户端,但事务已经提交,其余客户端会读取到已提交事务。若是Slave端尚未读到该事务的events,同时主库发生了crash,而后切换到备库。那么以前读到的事务就不见了,出现了幻读。以下图所示,图片引自Loss-less Semi-Synchronous Replication on MySQL 5.7.2。
若是主库永远启动不了,那么实际上在主库已经成功提交的事务,在从库上是找不到的,也就是数据丢失了,这是MySQL不肯意看到的。因此在MySQL 5.7版本中增长了after_sync(无损复制)参数,并将其设置为默认半同步方式,解决了数据丢失的问题。
具体完整配置可参考:MySQL基于日志点作主从复制(二)
Master配置
1)安装半同步模块并启动(此模块就在/usr/local/mysql/lib/plugin/semisync_master.so)
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
mysql> set global rpl_semi_sync_master_enabled = 1; mysql> set global rpl_semi_sync_master_timeout = 2000;
安装后启动和定制主从链接错误的超时时间默认是10s可改成2s,一旦有一次超时自动降级为异步。(以上内容要想永久有效须要写到配置文件中)
[root@localhost ~]# cat /etc/my.cnf [mysqld] rpl_semi_sync_master_enabled = 1; rpl_semi_sync_master_timeout = 2000;
Slave配置
1)安装半同步模块并启动
mysql> install plugin rpl_semi_sync_slave soname 'semisync_slave.so'; mysql> set global rpl_semi_sync_slave_enabled = 1; mysql> show global variables like '%semi%'; +---------------------------------+-------+ | Variable_name | Value | +---------------------------------+-------+ | rpl_semi_sync_slave_enabled | ON | | rpl_semi_sync_slave_trace_level | 32 | +---------------------------------+-------+ 2 rows in set (0.00 sec)
2)从节点须要从新链接主服务器半同步才会生效
mysql> stop slave io_thread; mysql> start slave io_thread;
PS:若是想卸载异步模块就使用uninstall便可。
Master上查看是否启用了半同步
如今半同步已经正常工做了,主要看Rpl_semi_sync_master_clients是否不为0,Rpl_semi_sync_master_status是否为ON。若是Rpl_semi_sync_master_status为OFF,说明出现了网络延迟或Slave IO线程延迟。
那么能够验证一下半同步超时,是否会自动降为异步工做。能够在Slave上停掉半同步协议,而后在Master上建立数据库看一下能不能复制到Slave上。
Slave
# 关闭半同步; mysql> set global rpl_semi_sync_slave_enabled = 0 ; mysql> stop slave io_thread; mysql> start slave io_thread;
Master
mysql> create database dbtest; Query OK, 1 row affected (2.01 sec)
mysql> create database dbtest01; Query OK, 1 row affected (0.01 sec)
建立第一个数据库花了2.01秒,而咱们前面设置的超时时间是2秒,而建立第二个数据库花了0.01秒,由此得出结论是超时转换为异步传送。能够在Master上查看半同步相关的参数值Rpl_semi_sync_master_clients和Rpl_semi_sync_master_status是否正常。
mysql> show global status like '%semi%'; +--------------------------------------------+-----------+ | Variable_name | Value | +--------------------------------------------+-----------+ | Rpl_semi_sync_master_clients | 0 | | Rpl_semi_sync_master_net_avg_wait_time | 0 | | Rpl_semi_sync_master_net_wait_time | 0 | | Rpl_semi_sync_master_net_waits | 37490 | | Rpl_semi_sync_master_no_times | 3 | | Rpl_semi_sync_master_no_tx | 197542 | | Rpl_semi_sync_master_status | OFF | | Rpl_semi_sync_master_timefunc_failures | 0 | | Rpl_semi_sync_master_tx_avg_wait_time | 51351 | | Rpl_semi_sync_master_tx_wait_time | 362437445 | | Rpl_semi_sync_master_tx_waits | 7058 | | Rpl_semi_sync_master_wait_pos_backtraverse | 0 | | Rpl_semi_sync_master_wait_sessions | 0 | | Rpl_semi_sync_master_yes_tx | 7472 | +--------------------------------------------+-----------+ 14 rows in set (0.00 sec)
能够看到都自动关闭了,须要注意一点的是,当Slave开启半同步后,或者当主从之间网络延迟恢复正常的时候,半同步复制会自动从异步复制又转为半同步复制,仍是至关智能的。
另外我的在实际使用中还碰到一种状况从库IO线程有延迟时,主库会自动把半同步复制降为异步复制;当从库IO延迟没有时,主库又会把异步复制升级为半同步复制。能够进行压测模拟,可是此时查看Master的状态跟上面直接关闭Slave半同步有些不一样,会发现Rpl_semi_sync_master_clients仍然等于1,而Rpl_semi_sync_master_status等于OFF。
随着MySQL 5.7版本的发布,半同步复制技术升级为全新的Loss-less Semi-Synchronous Replication架构,其成熟度、数据一致性与执行效率获得显著的提高。
如今咱们已经知道,在半同步环境下,主库是在事务提交以后等待Slave ACK,因此才会有数据不一致问题。因此这个Slave ACK在什么时间去等待,也是一个很关键的问题了。所以MySQL针对半同步复制的问题,在5.7.2引入了Loss-less Semi-Synchronous,在调用binlog sync以后,engine层commit以前等待Slave ACK。这样只有在确认Slave收到事务events后,事务才会提交。在commit以前等待Slave ACK,同时能够堆积事务,利于group commit,有利于提高性能。
MySQL 5.7安装半同步模块,命令以下:
mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so'; Query OK, 0 rows affected (0.00 sec)
看一下相关状态信息
mysql> show global variables like '%semi%'; +-------------------------------------------+------------+ | Variable_name | Value | +-------------------------------------------+------------+ | rpl_semi_sync_master_enabled | OFF | | rpl_semi_sync_master_timeout | 10000 | | rpl_semi_sync_master_trace_level | 32 | | rpl_semi_sync_master_wait_for_slave_count | 1 | | rpl_semi_sync_master_wait_no_slave | ON | | rpl_semi_sync_master_wait_point | AFTER_SYNC | +-------------------------------------------+------------+ 6 rows in set (0.00 sec)
在Loss-less Semi-Synchronous模式下,master在调用binlog sync以后,engine层commit以前等待Slave ACK(须要收到至少一个Slave节点回复的ACK后)。这样只有在确认Slave收到事务events后,master事务才会提交,而后把结果返回给客户端。此时此事务才对其余事务可见。在这种模式下解决了after_commit模式带来的幻读和数据丢失问题,由于主库没有提交事务。但也会有个问题,假设主库在存储引擎提交以前挂了,那么很明显这个事务是不成功的,但因为对应的Binlog已经作了Sync操做,从库已经收到了这些Binlog,而且执行成功,至关于在从库上多了数据,也算是有问题的,但多了数据,问题通常不算严重。这个问题能够这样理解,做为MySQL,在没办法解决分布式数据一致性问题的状况下,它能保证的是不丢数据,多了数据总比丢数据要好。
无损复制其实就是对semi sync增长了rpl_semi_sync_master_wait_point参数,来控制半同步模式下主库在返回给会话事务成功以前提交事务的方式。rpl_semi_sync_master_wait_point该参数有两个值:AFTER_COMMIT和AFTER_SYNC
第一个值:AFTER_COMMIT(5.6默认值)
master将每一个事务写入binlog(sync_binlog=1),传递到slave刷新到磁盘(sync_relay=1),同时主库提交事务。master等待slave反馈收到relay log,只有收到ACK后master才将commit OK结果反馈给客户端。
第二个值:AFTER_SYNC(5.7默认值,但5.6中无此模式)
master将每一个事务写入binlog , 传递到slave刷新到磁盘(relay log)。master等待slave反馈接收到relay log的ack以后,再提交事务而且返回commit OK结果给客户端。 即便主库crash,全部在主库上已经提交的事务都能保证已经同步到slave的relay log中。
半同步复制与无损复制的对比
1.1 ACK的时间点不一样
1.2 主从数据一致性
所以5.7引入了无损复制(after_sync)模式,带来的主要收益是解决after_commit致使的master crash后数据丢失问题,所以在引入after_sync模式后,全部提交的数据已经都被复制,故障切换时数据一致性将获得提高。
旧版本的semi sync受限于dump thread ,缘由是dump thread承担了两份不一样且又十分频繁的任务:传送binlog给slave ,还须要等待slave反馈信息,并且这两个任务是串行的,dump thread必须等待slave返回以后才会传送下一个events事务。dump thread已然成为整个半同步提升性能的瓶颈。在高并发业务场景下,这样的机制会影响数据库总体的TPS 。
为了解决上述问题,在5.7版本的semi sync框架中,独立出一个Ack Receiver线程 ,专门用于接收slave返回的ack请求,这将以前dump线程的发送和接受工做分为了两个线程来处理。这样master上有两个线程独立工做,能够同时发送binlog到slave,和接收slave的ack信息。所以半同步复制获得了极大的性能提高。这也是MySQL 5.7发布时号称的Faster semi-sync replication。
可是在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足矣。
MySQL 5.7新增了rpl_semi_sync_master_wait_slave_count参数,能够用来控制主库接受多少个slave写事务成功反馈,给高可用架构切换提供了灵活性。如图所示,当count值为2时,master需等待两个slave的ack。
旧版本半同步复制在主提交binlog的写会话和dump thread读binlog的操做都会对binlog添加互斥锁,致使binlog文件的读写是串行化的,存在并发度的问题。
MySQL 5.7对binlog lock进行了如下两方面优化:
1. 移除了dump thread对binlog的互斥锁。
2. 加入了安全边际保证binlog的读安全。
能够看到从replication功能引入后,官方MySQL一直在不停的完善,前进。同时咱们能够发现当前原生的MySQL主备复制实现实际上很难在知足数据一致性的前提下作到高可用、高性能。
sync_binlog的配置
其实无损复制流程中也会存在着会致使主备数据不一致的状况,使主备同步失败的情形。见下面sync_binlog配置的分析。
源码剖析
sql/binlog.cc ordered_commit 9002 update_binlog_end_pos_after_sync= (get_sync_period() == 1); ... //当sync_period(sync_binlog)为1时,在sync以后update binlog end pos 9021 if (!update_binlog_end_pos_after_sync) //更新binlog end position,dump线程会发送更新后的events 9022 update_binlog_end_pos(); ... // 9057 std::pair<bool, bool> result= sync_binlog_file(false); ... // 9061 if (update_binlog_end_pos_after_sync) 9062 { ... 9068 update_binlog_end_pos(tmp_thd->get_trans_pos()); 9069 } sql/binlog.cc sync_binlog_file 8618 std::pair<bool, bool> 8619 MYSQL_BIN_LOG::sync_binlog_file(bool force) 8620 { 8621 bool synced= false; 8622 unsigned int sync_period= get_sync_period(); //sync_binlog值 //sync_period为0不作sync操做,其余值为达到sync调用次数后sync 8623 if (force || (sync_period && ++sync_counter >= sync_period)) 8624 {
配置分析
当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多,主备同步失败。
因为上面的缘由,sync_binlog设置为1的时候,MySQL会update binlog end pos after sync。流程以下图所示。这时候,对于每个事务都须要sync binlog,同时sync binlog和网络发送events会是一个串行的过程,性能降低明显。
sync_relay_log的配置
源码剖析
sql/rpl_slave.cc handle_slave_io 5764 if (queue_event(mi, event_buf, event_len)) ... 5771 if (RUN_HOOK(binlog_relay_io, after_queue_event, 5772 (thd, mi, event_buf, event_len, synced))) after_queue_event ->plugin/semisync/semisync_slave_plugin.cc repl_semi_slave_queue_event ->plugin/semisync/semisync_slave.cc ReplSemiSyncSlave::slaveReply queue_event ->sql/binlog.cc MYSQL_BIN_LOG::append_buffer(const char* buf, uint len, Master_info *mi) ->sql/binlog.cc after_append_to_relay_log(mi); ->sql/binlog.cc flush_and_sync(0) ->sql/binlog.cc sync_binlog_file(force)
配置分析
在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。
但当Slave再次启动,若是没有来得及从Master端同步丢失的事务Events,Master就Crash。这个时候,用户访问Slave就会发现数据丢失。
经过上面这个Case,MySQL semisync若是要保证任意时刻发生一台机器宕机都不丢失数据,须要同时设置sync_relay_log为1。对relay log的sync操做是在queue_event中,对每一个event都要sync,因此sync_relay_log设置为1的时候,事务响应时间会受到影响,对于涉及数据比较多的事务延迟会增长不少。
MySQL三节点
在一主一从的主备semisync的数据一致性分析中放弃了高可用,当主备之间网络抖动或者一台宕机的状况下中止提供服务。要作到高可用,很天然咱们能够想到一主两从,这样解决某一网络抖动或一台宕机时候的可用性问题。可是,前文叙述要保证数据一致性配置要求依然存在,即正常状况下的性能不会有改善。同时须要解决Master宕机时候,如何选取新主机的问题,如何避免多主的情形。
选取新主机时必定要读取两个从机,看哪个从机有最新的日志,不然可能致使数据丢失。这样的三节点方案就相似分布式Quorum机制,写的时候须要保证写成功三节点中的法定集合,肯定新主的时候须要读取法定集合。利用分布式一致性协议Paxos/Raft能够解决数据一致性问题,选主问题和多主问题,所以近些年,国内数据库团队大多实现了基于Paxos/Raft的三节点方案。近来MySQL官方也以插件形式引入了支持多主集群的Group Replication方案。
转自:http://www.ywnds.com/?p=7023