上一篇文章已经讲解了主从(异步)复制的搭建:https://blog.youkuaiyun.com/line_on_database/article/details/104496937
借鉴大佬的文章(前面理论部分直接摘抄大佬文章):https://www.cnblogs.com/kevingrace/p/10228694.html
这里基于上一篇文章搭建半同步复制
MySQL主从复制包括异步模式、半同步模式、GTID模式以及多源复制模式,默认是异步模式 (如之前详细介绍的mysql主从复制)。所谓异步模式指的是MySQL 主服务器上I/O thread 线程将二进制日志写入binlog文件之后就返回客户端结果,不会考虑二进制日志是否完整传输到从服务器以及是否完整存放到从服务器上的relay日志中,这种模式一旦主服务(器)宕机,数据就可能会发生丢失。
异步模式是一种基于偏移量的主从复制,实现原理是: 主库开启binlog功能并授权从库连接主库,从库通过change master得到主库的相关同步信息然后连接主库进行验证,主库IO线程根据从库slave线程的请求,从master.info开始记录的位置点向下开始取信息,同时把取到的位置点和最新的位置与binlog信息一同发给从库IO线程,从库将相关的sql语句存放在relay-log里面,最终从库的sql线程将relay-log里的sql语句应用到从库上,至此整个同步过程完成,之后将是无限重复上述过程。
mysql主从复制的步骤: 1)在主库与从库都安装mysql数据库; 2) 在主库的my.cnf配置文件中配置server-id 和log-bin; 3) 在登陆主库后创建认证用户并做授权; 4) 在从库的my.cnf配置文件中配置server-id; 5) 登陆从库后,指定master并开启同步开关。
需要注意的是server-id主从库的配置是不一样的。
server-id存在作用: mysql同步的数据中是包含server-id的,而server-id用于标识该语句最初是从哪个server写入的。因此server-id一定要有的
server-id不能相同的原因:每一个同步中的slave在master上都对应一个master线程,该线程就是通过slave的server-id来标识的;每个slave在master端最多有一个master线程,如果两个slave的server-id 相同,则后一个连接成功时,slave主动连接master之后,如果slave上面执行了slave stop;则连接断开,但是master上对应的线程并没有退出;当slave start之后,master不能再创建一个线程而保留原来的线程,那样同步就可能有问题;
在mysql做主主同步时,多个主需要构成一个环状,但是同步的时候又要保证一条数据不会陷入死循环,这里就是靠server-id来实现的;
描述msyql replication 机制的实现原理,如何在不停掉mysql主库的情况下,恢复数据不一致的slave的数据库节点?
MySQL的复制(replication)是一个异步的复制,从一个MySQL instace(称之为Master)复制到另一个MySQL instance(称之Slave)。实现整个复制操作主要由三个进程完成的,其中两个进程在Slave(Sql进程和IO进程),另外一个进程在Master(IO进程)上。简单来说,Mysql复制就是一种基于binlog的单线程异步复制过程!!
上面提到的是mysql默认的异步同步模式,接下来重点说下Mysql半同步复制,从MySQL5.5开始,MySQL以插件的形式支持半同步复制。先来区别下mysql几个同步模式概念:
异步复制(Asynchronous replication)
MySQL默认的复制即是异步的,主库在执行完客户端提交的事务后会立即将结果返给给客户端,并不关心从库是否已经接收并处理,这样就会有一个问题,主如果crash掉了,此时主上已经提交的事务可能并没有传到从上,如果此时,强行将从提升为主,可能导致新主上的数据不完整。
全同步复制(Fully synchronous replication)
指当主库执行完一个事务,所有的从库都执行了该事务才返回给客户端。因为需要等待所有从库执行完该事务才能返回,所以全同步复制的性能必然会收到严重的影响。
半同步复制(Semisynchronous replication)
介于异步复制和全同步复制之间,主库在执行完客户端提交的事务后不是立刻返回给客户端,而是等待至少一个从库接收到并写到relay log中才返回给客户端。相对于异步复制,半同步复制提高了数据的安全性,同时它也造成了一定程度的延迟,这个延迟最少是一个TCP/IP往返的时间。所以,半同步复制最好在低延时的网络中使用。
- 对于异步复制,主库将事务Binlog事件写入到Binlog文件中,此时主库只会通知一下Dump线程发送这些新的Binlog,然后主库就会继续处理提交操作,而此时不会保证这些Binlog传到任何一个从库节点上。
- 对于全同步复制,当主库提交事务之后,所有的从库节点必须收到,APPLY并且提交这些事务,然后主库线程才能继续做后续操作。这里面有一个很明显的缺点就是,主库完成一个事务的时间被拉长,性能降低。
- 对于半同步复制,是介于全同步复制和异步复制之间的一种,主库只需要等待至少一个从库节点收到并且Flush Binlog到Relay Log文件即可,主库不需要等待所有从库给主库反馈。同时,这里只是一个收到的反馈,而不是已经完全执行并且提交的反馈,这样就节省了很多时间。
Mysql半同步复制技术
一般而言,普通的replication,即MySQL的异步复制,依靠MySQL二进制日志也即binary log进行数据复制。比如两台机器,一台主机(master),另外一台是从机(slave)。
正常的复制为:事务一(t1)写入binlog buffer;dumper线程通知slave有新的事务t1;binlog buffer进行checkpoint;slave的io线程接收到t1并写入到自己的的relay log;slave的sql线程写入到本地数据库。 这时,master和slave都能看到这条新的事务,即使master挂了,slave可以提升为新的master。
异常的复制为:事务一(t1)写入binlog buffer;dumper线程通知slave有新的事务t1;binlog buffer进行checkpoint;slave因为网络不稳定,一直没有收到t1;master挂掉,slave提升为新的master,t1丢失。
很大的问题是:主机和从机事务更新的不同步,就算是没有网络或者其他系统的异常,当业务并发上来时,slave因为要顺序执行master批量事务,导致很大的延迟。
为了弥补以上几种场景的不足,MySQL从5.5开始推出了半同步复制。相比异步复制,半同步复制提高了数据完整性,因为很明确知道,在一个事务提交成功之后,这个事务就至少会存在于两个地方。即在master的dumper线程通知slave后,增加了一个ack(消息确认),即是否成功收到t1的标志码,也就是dumper线程除了发送t1到slave,还承担了接收slave的ack工作。如果出现异常,没有收到ack,那么将自动降级为普通的复制,直到异常修复后又会自动变为半同步复制。
半同步复制具体特性
- 从库会在连接到主库时告诉主库,它是不是配置了半同步。
- 如果半同步复制在主库端是开启了的,并且至少有一个半同步复制的从库节点,那么此时主库的事务线程在提交时会被阻塞并等待,结果有两种可能,要么至少一个从库节点通知它已经收到了所有这个事务的Binlog事件,要么一直等待直到超过配置的某一个时间点为止,而此时,半同步复制将自动关闭,转换为异步复制。
- 从库节点只有在接收到某一个事务的所有Binlog,将其写入并Flush到Relay Log文件之后,才会通知对应主库上面的等待线程。
- 如果在等待过程中,等待时间已经超过了配置的超时时间,没有任何一个从节点通知当前事务,那么此时主库会自动转换为异步复制,当至少一个半同步从节点赶上来时,主库便会自动转换为半同步方式的复制。
- 半同步复制必须是在主库和从库两端都开启时才行,如果在主库上没打开,或者在主库上开启了而在从库上没有开启,主库都会使用异步方式复制。
下面来看看半同步复制的原理图:
半同步复制的意思表示MASTER 只需要接收到其中一台SLAVE的返回信息,就会commit;否则需等待直至达到超时时间然后切换成异步再提交。这个做可以使主从库的数据的延迟较小,可以在损失很小的性能的前提下提高数据的安全性。
主库产生binlog到主库的binlog file,传到从库中继日志,然后从库应用;也就是说传输是异步的,应用也是异步的。半同步复制指的是传输同步,应用还是异步的!
好处:保证数据不丢失(本机和远端都有binlog)
坏处:不能保证应用的同步。
mysql半同步复制模式的流程图
即主库忽然崩了时,从库虽然说有延迟,但是延迟过后,可以把从库提升为主库继续服务,事后恢复到主库即可
mysql异步复制模式的流程图
半同步复制的潜在问题
客户端事务在存储引擎层提交后,在得到从库确认的过程中,主库宕机了,此时,可能的情况有两种
- 事务还没发送到从库上
此时,客户端会收到事务提交失败的信息,客户端会重新提交该事务到新的主上,当宕机的主库重新启动后,以从库的身份重新加入到该主从结构中,会发现,该事务在从库中被提交了两次,一次是之前作为主的时候,一次是被新主同步过来的。
- 事务已经发送到从库上
此时,从库已经收到并应用了该事务,但是客户端仍然会收到事务提交失败的信息,重新提交该事务到新的主上。
无数据丢失的半同步复制
针对上述潜在问题,MySQL 5.7引入了一种新的半同步方案:Loss-Less半同步复制。针对上面这个图,"Waiting Slave dump"被调整到"Storage Commit"之前。当然,之前的半同步方案同样支持,MySQL 5.7.2引入了一个新的参数进行控制: rpl_semi_sync_master_wait_point, 这个参数有两种取值:1) AFTER_SYNC , 这个是新的半同步方案,Waiting Slave dump在Storage Commit之前。2) AFTER_COMMIT, 这个是老的半同步方案。
来看下面半同步复制原理图,分析下半同步复制潜在问题
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,然后切换到备库。那么之前读到的事务就不见了,出现了幻读。
如果主库永远启动不了,那么实际上在主库已经成功提交的事务,在从库上是找不到的,也就是数据丢失了,这是MySQL不愿意看到的。所以在MySQL 5.7版本中增加了after_sync(无损复制)参数,并将其设置为默认半同步方式,解决了数据丢失的问题。
半同步复制的安装部署条件
要想使用半同步复制,必须满足以下几个条件:
1)MySQL 5.5及以上版本
2)变量have_dynamic_loading为YES (查看命令:show variables like "have_dynamic_loading";)
3)主从复制已经存在 (即提前部署mysql主从复制环境,主从同步要配置基于整个数据库的,不要配置基于某个库的同步,即同步时不要过滤库)(这点亲测可以配置,我是基于GTID布置的)
- 首先加载插件并查看状态
主库
install plugin rpl_semi_sync_master soname 'semisync_master.so';
select plugin_name,plugin_status from information_schema.plugins where plugin_name like '%semi%';
从库1
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
select plugin_name,plugin_status from information_schema.plugins where plugin_name like '%semi%';
从库2
install plugin rpl_semi_sync_slave soname 'semisync_slave.so';
select plugin_name,plugin_status from information_schema.plugins where plugin_name like '%semi%';
启动半同步复制
主库
set global rpl_semi_sync_master_enabled=1;
从库1
set global rpl_semi_sync_slave_enabled=1;
从库2
set global rpl_semi_sync_slave_enabled=1;
重启从库io线程
STOP SLAVE IO_THREAD;
START SLAVE IO_THREAD;
在主从库执行下面语句查看状态
show status like '%rpl_semi%';
在主库中会看到如下结果
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 2 | # 半同步复制客户端的个数
| Rpl_semi_sync_master_net_avg_wait_time | 0 | # 平均等待时间(默认毫秒)
| Rpl_semi_sync_master_net_wait_time | 0 | # 总共等待时间
| Rpl_semi_sync_master_net_waits | 12 | # 等待次数
| Rpl_semi_sync_master_no_times | 2 | # 关闭半同步复制的次数
| Rpl_semi_sync_master_no_tx | 2 | # 表示没有成功接收slave提交的次数
| Rpl_semi_sync_master_status | ON | # 表示当前是异步模式是半同步模式
| Rpl_semi_sync_master_timefunc_failures | 0 | # 调用时间函数失败的次数
| Rpl_semi_sync_master_tx_avg_wait_time | 1054 | # 事务的平均传输时间
| Rpl_semi_sync_master_tx_wait_time | 5274 | # 事务的总共传输时间
| Rpl_semi_sync_master_tx_waits | 5 | # 事务等待次数
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 | # 后来的先到了,而先来的还没有到的次数
| Rpl_semi_sync_master_wait_sessions | 0 | # 当前有多少个session因为slave的回复而造成等待
| Rpl_semi_sync_master_yes_tx | 5 | # 成功接受到slave事务回复的次数
+--------------------------------------------+-------+
而从库则会显示
+----------------------------+-------+
| Variable_name | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON |
+----------------------------+-------+
-
对上述所描述问题的实验
- 半同步复制的特性及半同步复制到异步复制的转换
通过上述的状态查看,我们可以看出当前的是处于半同步复制状态,我们有两个从库,这时候我们停止一个从库的复制,然后像主库插入数据
主库(准备操作)
mysql> use test;
Database changed
mysql> create table rpl_test (a int(10));
Query OK, 0 rows affected (0.04 sec)
从库1
stop slave;
主库
mysql> insert into rpl_test values(1),(2),(3);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
这里可以看到数据很快的同步了过去,并没有等待10s,这是因为半同步复制的意思表示MASTER 只需要接收到其中一台SLAVE的返回信息,就会commit;接下来停掉从库2的复制
从库2
stop slave;
然后然后向主库插入信息
mysql> insert into rpl_test values(4),(5),(6);
Query OK, 3 rows affected (10.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
mysql> show status like 'rpl_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 | 17 |
| Rpl_semi_sync_master_no_times | 3 |
| Rpl_semi_sync_master_no_tx | 3 |
| Rpl_semi_sync_master_status | OFF |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 1247 |
| Rpl_semi_sync_master_tx_wait_time | 9980 |
| Rpl_semi_sync_master_tx_waits | 8 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 8 |
+--------------------------------------------+-------+
14 rows in set (0.01 sec)
这时候我们可以看到等待10s数据才提交,而且状态变成了异步状态,这即证明没有SLAVE的返回信息需等待直至达到超时时间然后切换成异步再提交。这个做可以使主从库的数据的延迟较小,可以在损失很小的性能的前提下提高数据的安全性。
然后我们随意开启一台从库的复制
start slave;
可以看到半同步复制自动恢复
mysql> show status like 'rpl_semi%';
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 |
| Rpl_semi_sync_master_net_avg_wait_time | 0 |
| Rpl_semi_sync_master_net_wait_time | 0 |
| Rpl_semi_sync_master_net_waits | 18 |
| Rpl_semi_sync_master_no_times | 3 |
| Rpl_semi_sync_master_no_tx | 3 |
| Rpl_semi_sync_master_status | ON |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 1247 |
| Rpl_semi_sync_master_tx_wait_time | 9980 |
| Rpl_semi_sync_master_tx_waits | 8 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 8 |
+--------------------------------------------+-------+
14 rows in set (0.01 sec)
半同步复制是否可以指定数据库复制
在主库的配置文件里增加如下配置,即只复制test数据库
binlog-do-db=test
接下来在主库的test库插入数据
mysql> insert into rpl_test values(7),(8),(9);
Query OK, 3 rows affected (0.01 sec)
Records: 3 Duplicates: 0 Warnings: 0
检查从库情况
mysql> select * from rpl_test;
+------+
| a |
+------+
| 1 |
| 2 |
| 3 |
| 4 |
| 5 |
| 6 |
| 7 |
| 8 |
| 9 |
+------+
9 rows in set (0.00 sec)
可以看到test库正常复制,再来看主库新建的库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| repl_test |
| sys |
| test |
+--------------------+
6 rows in set (0.00 sec)
mysql> create database test2;
Query OK, 1 row affected (0.02 sec)
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| repl_test |
| sys |
| test |
| test2 |
+--------------------+
7 rows in set (0.00 sec)
从库
mysql> show databases;
+--------------------+
| Database |
+--------------------+
| information_schema |
| mysql |
| performance_schema |
| repl_test |
| sys |
| test |
+--------------------+
6 rows in set (0.00 sec)
可以看到半同步复制的指定库复制起作用
AFTER_SYNC和AFTER_COMMIT的区别
理论部分摘抄大佬文章:http://blog.itpub.net/15498/viewspace-2143986/
MySQL 5.7增强了半同步复制,rpl_semi_sync_master_wait_point增加了AFTER_SYNC的值,由该参数AFTER_SYNC/AFTER_COMMIT两个值选择是否启用增强半同步.
当半同步模式为 AFTER_COMMIT时:
过程分析如下:
1 > session 发出commit请求
2 > flush binlog and fsync binlog
3 > InnoDB 引擎层 commit
4 > 发送binlog到SLAVE,等待slave发送ack确认
5 > slave 接受binlog 写入relay log ,刷盘完成发送ack确认包给master
6 > master 返回 commit ok 信息给session
另外 (master 等待事务A 的 ACK的时候宕机,此时新事务B在宕机之前开启):
1> binlog 未发送到从库:
事务B获取到事务A提交的内容, 此时宕机故障切换到slave,事务B获取到的内容却丢失了。事务A commit没有收到反馈信息(则需要业务判断了)。
2> binlog 已经发送给从库 :
事务B获取到事务A提交的内容,故障切换到salve ,B仍然获取到A提交的内容,没毛病。事务A commit没有收到反馈信息,若重新执行该事务,则相当于执行两次A事务(则需要业务判断了)。
当半同步模式为 AFTER_SYNC(5.7版本推荐使用)时:
过程分析如下:
1 > session 发出commit请求
2 > flush binlog and fsync binlog
3 > 发送binlog到SLAVE,等待slave发送ack确认
4 > slave 接受binlog 写入relay log ,刷盘完成发送ack确认包给master
5 > InnoDB 引擎层 commit
6 > master 返回 commit ok 信息给session
另外(master 等待事务A 的 ACK的时候宕机,此时新事务B在宕机之前开启):
1> 事务B读取不到事务A的内容,因为事务A的ENGINE层还没有提交(无损复制)
dump thread过程分析:
mysql5.6版本之前:
1> master dump thread 发送binlog events 给 slave 的IO thread,等待 slave 的ack回包
2> slave 接受binlog events 写入redo log ,返回 ack 包给master dump thread
3> master dump thread 收到ack包 ,给session返回commit ok,然后继续发送写一个事务的binlog。
mysql5.7之后新增ack线程:
1> master dump thread 发送binlog events 给 slave 的IO thread,开启ack线程等待 slave 的ack回包,dump 线程继续向slaveIO thread发送下一个事务的binlog。
2> slave 接受binlog events 写入redo log ,返回 ack 包给master ack线程,然后给session返回commit ok。
过程总结:
Master在收到slave的应答后才Commit事务--after_sync(5.6上Master在commit后,才等待Slave的应答--after commit).
因此在确认事务复制到Slave上之前,并发的事务看不到当前事务的数据.
当Master出现故障时,所有已经提交的事务都复制到了Slave上.
缺省采用无数据丢失的应答等待机制after_sync。用户也可以选择使用5.6的应答等待机制after_commit
之后想到怎么验证这个问题再补充