[TOC]
一.半同步和无损复制
主从复制,基本上都是 异步复制
, Master并不关Slave节点有没有获取到数据 ,所以复制效率很高,但是数据有可能会丢失。
从 MySQL5.5 开始,MySQL推出了semi-sync replication (半同步复制)
- 至少有一个Slave节点收到binlog后再返回( IO线程接收到即可 )
- 减少数据丢失风险
- 不能完全避免数据丢失
- 超时后,切换回异步复制
从 MySQL5.7.2 开始,MySQL推出了lossless semi-sync replication (无损复制)
- 二进制日志(binlog)先写远程( IO线程接收到即可 )
- 可保证数据完全不丢失
1.1. loss less / semi-sync replication插件安装
- 手工安装
mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
- 方式二 – 写入配置文件
[mysqld]
plugin_dir=/usr/local/mysql/lib/plugin
plugin_load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
上述操作 仅仅是加载了插件 ,还 未启动
对应的功能,需要配置额外的参数:
[mysqld]
# 等同于 rpl_semi_sync_master_enabled = 1
loose_rpl_semi_sync_master_enabled = 1
# 等同于 rpl_semi_sync_slave_enabled = 1
loose_rpl_semi_sync_slave_enabled = 1
# 超时5秒后,则切换回异步方式
loose_rpl_semi_sync_master_timeout = 5000
使用 loose_
前缀表示如果没有加载 semi_sync
的插件,则 忽略该参数
当Slave在Timeout
后,又追上了Master了( IO线程 ),则会 自动切换回半同步复制
注意:
半同步复
制 /无损复制
在主从
上都要安装插件和开启功能
。
注意:要保证
任意时刻发生一台机器宕机都不丢失数据
的前提是 mastersync_binlog
设置为1,slavesync_relay_log
设置为1。
1.2. semi-sync replication
semi-sync replication 称为 半同步复制
,在一个事务 提交(commit)
的过程时,在 InnoDB 层的 commit log
步骤后,Master节点需要收到 至少一个
Slave节点回复的 ACK
(表示 收到了binlog )后,才能继续下一个事务;
如果在一定时间内(Timeout)内 没有收到ACK
,则 切换为异步模式
,具体流程如下:
对应的配置参数如下:
[mysqld]
# 开启主的半同步复制
rpl_semi_sync_master_enabled=1
# 开启从的半同步复制
rpl_semi_sync_slave_enabled=1
# 超时1秒,切回异步
rpl_semi_sync_master_timeout=1000
# 至少收到 1 个 slave发回的ack
rpl_semi_sync_master_wait_for_slave_count=1
1.3. loss less semi-sync replication
loss less semi-sync replication 称为 无损复制
,在一个事务提交(commit
) 的过程时,在 MySQL
层的write binlog
步骤后,Master节点需要收到 至少一个
Slave节点回复的 ACK
(表示 收到了binlog )后,才能继续下一个事务;
如果在一定时间内(Timeout)内 没有收到ACK
,则 切换为异步模式
,具体流程如下:
对应的配置参数如下:
[mysqld]
# 开启主的半同步复制
rpl_semi_sync_master_enabled=1
# 开启从的半同步复制
rpl_semi_sync_slave_enabled=1
# 超时1秒,切回异步
rpl_semi_sync_master_timeout=1000
[mysqld57]
# 控制 半同步复制 还是 无损复制 的参数
# - AFTER_SYNC 表示的是无损复制;(5.7 默认)
# - AFTER_COMMIT 表示的是半同步复制;
rpl_semi_sync_master_wait_point=AFTER_SYNC
# 至少收到 1 个 slave发回的ack
rpl_semi_sync_master_wait_for_slave_count=1
1.4. 半同步复制与无损复制的对比
ACK的时间点不同
半同步复制
在InnoDB层
的Commit Log后
等待ACK,主从切换会有数据丢失
风险。无损复制
在MySQL Server层
的Write binlog后
等待ACK,主从切换会有数据变多
风险。
主从数据一致性
半同步复制
意味着在Master节点上,这个刚刚提交的事务对数据库的修改,对其他事务是可见的。因此,如果在等待Slave ACK的时候crash了,那么会对其他事务出现幻读
,数据丢失
。无损复制
在write binlog完成后,就传输binlog,但还没有去写commit log,意味着当前这个事务对数据库的修改,其他事务也是不可见的
。因此,不会出现幻读,数据丢失风险。- 因此5.7.2引入了
无损复制(after_sync)模式
,带来的主要收益是解决after_commit
导致的master crash
后数据丢失问题
,因此在引入after_sync模式
后,所有提交的数据已被复制
,故障切换时数据一致性将得到提升。
1.5. 演示无损/半同步复制
- master server
mysql root@localhost:employees> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
Query OK, 0 rows affected
Time: 0.063s
mysql root@localhost:employees> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
Query OK, 0 rows affected
Time: 0.005s
mysql root@localhost:employees> set global rpl_semi_sync_master_enabled = 1;
Query OK, 0 rows affected
Time: 0.020s
mysql root@localhost:employees> set global rpl_semi_sync_master_timeout = 5000;
Query OK, 0 rows affected
Time: 0.001s
mysql root@localhost:employees> show global status like "%rpl%";
+--------------------------------------------+-------+
| 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 | 0 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | ON | -- status ok
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 0 |
| Rpl_semi_sync_master_tx_wait_time | 0 |
| Rpl_semi_sync_master_tx_waits | 0 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 0 |
| Rpl_semi_sync_slave_status | OFF |
+--------------------------------------------+-------+
15 rows in set
Time: 0.013s
mysql root@localhost:employees>
- slave server
(root@localhost) 09:59:14 [employees]> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
Query OK, 0 rows affected (0.05 sec)
(root@localhost) 18:07:15 [employees]> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
Query OK, 0 rows affected (0.00 sec)
(root@localhost) 18:07:29 [employees]> set global rpl_semi_sync_slave_enabled = 1;
Query OK, 0 rows affected (0.00 sec)
(root@localhost) 18:14:59 [employees]> 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 |
| rpl_semi_sync_slave_enabled | ON |
| rpl_semi_sync_slave_trace_level | 32 |
+-------------------------------------------+------------+
8 rows in set (0.02 sec)
半复制切换异步同步过程的状态
- master server
(root@localhost) 10:11:10 [tablespace]> show global status like "%rpl%";
+--------------------------------------------+-------+
| Variable_name | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients | 1 | -- 半同步复制的client数量
| Rpl_semi_sync_master_net_avg_wait_time | 0 |
| Rpl_semi_sync_master_net_wait_time | 0 |
| Rpl_semi_sync_master_net_waits | 1 | -- master总的等待slave的次数
| Rpl_semi_sync_master_no_times | 0 | -- 切成异步的次数(no = number of)
| Rpl_semi_sync_master_no_tx | 0 | -- 切成异步后提交的事物数
| Rpl_semi_sync_master_status | ON | -- 半同步复制的状态
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 2209 | -- master等待事物的平均时间
| Rpl_semi_sync_master_tx_wait_time | 2209 | -- master等待事物的总的时间
| Rpl_semi_sync_master_tx_waits | 1 | -- master等待事物的次数
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 1 |
| Rpl_semi_sync_slave_status | OFF |
+--------------------------------------------+-------+
15 rows in set (0.03 sec)
- slave server
(root@localhost) 13:47:48 [tablespace]> stop slave io_thread; --停掉IO线程
Query OK, 0 rows affected (0.05 sec)
- master server
(root@localhost) 14:48:11 [tablespace]> show global status like "%rpl%";
+--------------------------------------------+-------+
| 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 | 1 |
| Rpl_semi_sync_master_no_times | 0 |
| Rpl_semi_sync_master_no_tx | 0 |
| Rpl_semi_sync_master_status | ON |
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 2209 |
| Rpl_semi_sync_master_tx_wait_time | 2209 |
| Rpl_semi_sync_master_tx_waits | 1 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 1 |
| Rpl_semi_sync_slave_status | OFF |
+--------------------------------------------+-------+
15 rows in set (0.00 sec)
(root@localhost) 14:48:14 [tablespace]> insert into qqq values(99); --插入sql
Query OK, 1 row affected (5.04 sec) -- 等待5秒后,切成异步
(root@localhost) 14:49:28 [tablespace]> show global status like "%rpl%";
+--------------------------------------------+-------+
| 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 | 1 |
| Rpl_semi_sync_master_no_times | 1 | --切成异步的次数
| Rpl_semi_sync_master_no_tx | 1 | --切成异步后的事物数
| Rpl_semi_sync_master_status | OFF | --status 为off
| Rpl_semi_sync_master_timefunc_failures | 0 |
| Rpl_semi_sync_master_tx_avg_wait_time | 2209 |
| Rpl_semi_sync_master_tx_wait_time | 2209 |
| Rpl_semi_sync_master_tx_waits | 1 |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0 |
| Rpl_semi_sync_master_wait_sessions | 0 |
| Rpl_semi_sync_master_yes_tx | 1 |
| Rpl_semi_sync_slave_status | OFF |
+--------------------------------------------+-------+
15 rows in set (0.00 sec)
(root@localhost) 14:49:57 [tablespace]>
1.6. 两种复制方式的性能
备注:上图是Facebook的测试性能图;其中Y轴是QPS,X轴是并发数
- 蓝色的
Normal Slave
是异步复制
- 性能很好,但是随着并发数的增长,性能有所下降
- 绿色的
Enhanced mysqlbinlog
是无损复制
- 随着并发数的增长,性能几乎是线性增长的,在高并发下,性能会优于异步复制
- 紫色的
Normal Semi Slave
是 半同步复制- 性能较低
无损复制性能优于半同步复制的原因
等待ACK回包
问题上,其实两种复制的开销是一样的,没有区别,都是网络的等待开销。无损复制
由于在write binlog
(commit 的第二步)后,需要等待ACK,后续的事务无法提交
,这样就堆积
了N多个需要落盘的事务
(半同步复制由于已经提交了事务,没有堆积事务的效果),通过组提交
机制一次 fsync的多个事务
(半同步复制也有组提交,只是一次fsync
的事务数没那么多), 相当于提高了I/O性能
;所以线程(事务)越多,效果越明显
,以至于有上图中超过异步复制的效果。(无损复制的组提交比例比原版的高3~4倍)
产生上述测试效果的前提:测试用例是
IO Bound
的(比如数据量有 100G,而 buffer pool 只有 10G),且并发数足够多。
下面这两个参数不要去设置,设置了反而性能差
(root@localhost) 10:10:47 [tablespace]> show variables like "%binlog_group%";
+-----------------------------------------+-------+
| Variable_name | Value |
+-----------------------------------------+-------+
| binlog_group_commit_sync_delay | 0 |
| binlog_group_commit_sync_no_delay_count | 0 | -- 等待一组里面有多少事务我才提交
+-----------------------------------------+-------+
2 rows in set (0.01 sec)
(root@localhost) 10:10:50 [tablespace]> show variables like "%binlog_max%";
+-----------------------------+-------+
| Variable_name | Value |
+-----------------------------+-------+
| binlog_max_flush_queue_time | 0 | -- 等待多少时间后才进行组提交
+-----------------------------+-------+
1 row in set (0.00 sec)
(root@localhost) 10:11:10 [tablespace]>
1.7. rpl_semi_sync_master_wait_for_slave_count
该参数控制Master在收到 多少个 Slave的ACK
后,才可以继续commit。配置多个ACK和配置一个ACK的效果是类似的,因为他们是 并行执行
的(理论上来说不会有两倍的等待时间), 取决于最慢的那个 。
二. 并行复制(Multi-Threaded Slave)
2.1. MTS介绍
在官方文档中,并行复制的叫法为 Multi-Threaded Slave (MTS)
- MySQL的并行复制基于组提交:
一个组提交中的事务都是可以并行执行的 ,因为既然处于组提交中,这意味着事务之间没有冲突(不会去更新同一行数据),否则不可能在同一个组里面。
Slave上开启并行复制,需要在配置文件中增加以下参数:
[mysqld]
slave-parallel-type=LOGICAL_CLOCK
slave_preserve_commit_order=1
slave-parallel-workers=4
slave-parallel-type 参数
DATABASE
基于库级别的并行复制
,如果只有一个库,就还是串行(为了兼容5.6)。LOGICAL_CLOCK
基于逻辑时钟
,主上怎么并行执行,从上也是怎么并行回放的。
slave-parallel-workers
并行复制的线程数
,一般设置为一个组内提交的事务数,线上设置为32足够了slave_preserve_commit_order
Slave上commit
的顺序保持一致
,必须为1,否则可能会有GAP锁产生
2.2. 动态调整复制线程数
配置并行复制后,Slave节点可以看到4个 Coordinator
线程
mysql root@localhost:(none)> show processlist;
+----+-------------+-----------+--------+---------+--------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------------+-----------+--------+---------+--------+--------------------------------------------------------+------------------+
| 1 | system user | | <null> | Connect | 148155 | Waiting for master to send event | <null> |
| 2 | system user | | <null> | Connect | 57792 | Slave has read all relay log; waiting for more updates | <null> |
| 3 | system user | | <null> | Connect | 57791 | Waiting for an event from Coordinator | <null> |
| 4 | system user | | <null> | Connect | 148155 | Waiting for an event from Coordinator | <null> |
| 7 | system user | | <null> | Connect | 148155 | Waiting for an event from Coordinator | <null> |
| 8 | system user | | <null> | Connect | 148155 | Waiting for an event from Coordinator | <null> |
| 12 | root | localhost | <null> | Query | 0 | starting | show processlist |
+----+-------------+-----------+--------+---------+--------+--------------------------------------------------------+------------------+
7 rows in set
Time: 0.019s
mysql root@localhost:(none)>
-- 动态调整方式如下:
mysql root@localhost:(none)> set global slave_parallel_workers=8;
Query OK, 0 rows affected
Time: 0.003s
mysql root@localhost:(none)> stop slave; 一定要重启一下slave才能有效
Query OK, 0 rows affected
Time: 0.038s
mysql root@localhost:(none)> start slave;
Query OK, 0 rows affected
Time: 0.080s
mysql root@localhost:(none)> show processlist;
+----+-------------+-----------+--------+---------+------+--------------------------------------------------------+------------------+
| Id | User | Host | db | Command | Time | State | Info |
+----+-------------+-----------+--------+---------+------+--------------------------------------------------------+------------------+
| 12 | root | localhost | <null> | Query | 0 | starting | show processlist |
| 13 | system user | | <null> | Connect | 3 | Waiting for master to send event | <null> |
| 14 | system user | | <null> | Connect | 3 | Slave has read all relay log; waiting for more updates | <null> |
| 15 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 16 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 17 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 18 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 19 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 20 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 21 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
| 22 | system user | | <null> | Connect | 3 | Waiting for an event from Coordinator | <null> |
+----+-------------+-----------+--------+---------+------+--------------------------------------------------------+------------------+
11 rows in set
Time: 0.019s
mysql root@localhost:(none)>
特别注意:
这里的
并行复制
指的是SQL Thread (回放线程)
,而非IO Thread (IO线程)
Waiting for master to send event
这个State
在show processlist
中只有一个,即只有一个IO Thread
线上环境可以配置成两台Slave做无损复制(保证数据不丢),其他的Slave做异步复制(配置为只读,用于负载均衡),都指向同一台Master。
三. GTID
3.1. GTID的介绍
1.Global Transaction Id entifier -- 全局事物ID
2.GTID = Server_UUID + Transaction_ID
- Server_UUID 是全局唯一的
- Transaction_ID 是自增的
3.GTID 的作用是替代 Filename + Position
(root@localhost) 14:54:25 [tablespace]> show variables like "server_uuid";
+---------------+--------------------------------------+
| Variable_name | Value |
+---------------+--------------------------------------+
| server_uuid | 9dc847d8-bf72-11e7-9ec4-000c2998e4f1 |
+---------------+--------------------------------------+
1 row in set (0.01 sec)
在MySQL中看到的 UUID
,实际是保存在 $DATADIR/auto.cnf
中的,且该文件是服务器初始化
的时候自动生成
的。
[root@node1 mysqldata]# cat /r2/mysqldata/auto.cnf
[auto]
server-uuid=9dc847d8-bf72-11e7-9ec4-000c2998e4f1
[root@node1 mysqldata]#
通过
冷备
做备份,拷贝$DATADIR
时,记得要把备份中的auto.cnf
给删除、
3.2. GTID的意义
- 未使用GTID
- 当
Master宕机
后,一个Slave
被选举提升
为New Master
,如果需要重建复制关系
,就需要把另外两个Slave
的CHANGE MASTER
指向New Master
; - 那问题来了,原来Slave是指向
Master
的Filename_M + Position_M
的位置,现在要指向New Master
上新的Filename_N + Position_N
的位置,这 两个位置是比较难对应起来的; 此时两个Slave要继续重建复制关系(CHANGE MASTER)
会比较麻烦。
- 当
使用GTID
- 和上面一样的场景,
选举机制
提升为New Master GTIDZ执行到最新,两个Slave
需要重新指向New Master
,由于使用了GTID
,目前Slave-A
获取到的日志对应的GTID为GTID_A
,Slave-B
获取到的日志对应的 GTID为GTID_B
; - 此时
New Master
上是存在GTID_A 和 GTID_B
(通过选举出来的,获取的日志应该是最多的),那两个Slave就可以直接使用GTID_A 和 GTID_B
这两个GTID,通过指向New Master
接着继续复制;
- 和上面一样的场景,
3.3. GTID的配置
[mysqld]
log_bin = bin.log
log_slave_updates = 1
gtid_mode = ON
enforce_gtid_consistency = 1
- 注意:
1.MySQL5.6 必须开启参数 log_slave_updates
(5.6版本的限制)
2.MySQL5.6 升级到gtid模式需要停机重启
3.MySQL5.7 版本开始可以不开启 log_slave_updates
4.MySQL5.7.6 版本开始可以在线升级
成gtid模式