【Mysql】mysql 基于GTID复制

1、GTID的概述:

一、全局事物标识:global transaction identifieds。mysql

二、GTID事物是全局惟一性的,且一个事务对应一个GTID。git

三、一个GTID在一个服务器上只执行一次,避免重复执行致使数据混乱或者主从不一致。sql

四、GTID用来代替classic的复制方法,不在使用binlog+pos开启复制。而是使用master_auto_postion=1的方式自动匹配GTID断点进行复制。数据库

五、MySQL-5.6.5开始支持的,MySQL-5.6.10后开始完善。安全

六、在传统的slave端,binlog是不用开启的,可是在GTID中,slave端的binlog是必须开启的,目的是记录执行过的GTID(强制)。服务器

2、GTID的组成部分:

前面是server_uuid:后面是一个序列号session

例如:server_uuid:sequence number多线程

7800a22c-95ae-11e4-983d-080027de205a:10并发

UUID:每一个mysql实例的惟一ID,因为会传递到slave,因此也能够理解为源ID。app

Sequence number:在每台MySQL服务器上都是从1开始自增加的序列,一个数值对应一个事务。

3、GTID比传统复制的优点:

一、更简单的实现failover,不用之前那样在须要找log_file和log_Pos。

二、更简单的搭建主从复制。

三、比传统复制更加安全。

四、GTID是连续没有空洞的,所以主从库出现数据冲突时,能够用添加空事物的方式进行跳过。

4、GTID的工做原理:

一、master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
二、slave端的i/o 线程将变动的binlog,写入到本地的relay log中。
三、sql线程从relay log中获取GTID,而后对比slave端的binlog是否有记录。
四、若是有记录,说明该GTID的事务已经执行,slave会忽略。
五、若是没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
六、在解析过程当中会判断是否有主键,若是没有就用二级索引,若是没有就用所有扫描。

5、要点:

一、slave在接受master的binlog时,会校验master的GTID是否已经执行过(一个服务器只能执行一次)。

二、为了保证主从数据的一致性,多线程只能同时执行一个GTID。

6、使用GTID搭建mysql的主从复制的主要参数:

[mysqld]
#GTID:
gtid_mode=on
enforce_gtid_consistency=on
server_id=2003306    #天天实例的server_id都要不同
 
#binlog
log-bin=mysqlbin
log-slave-updates=1   #容许下端接入slave
binlog_format=row      #强烈建议,其余格式可能形成数据不一致
 
#relay log
skip_slave_start=1
注意:建议使用mysql-5.6.5以上的最新版本。

启动GTID的两种方法:

方法1、

一、若是是在已经跑的服务器,你须要重启一下mysql server。
二、启动以前,必定要先关闭master的写入,保证全部slave端都已经和master端数据保持同步。
三、全部slave须要加上skip_slave_start=1的配置参数,避免启动后仍是使用老的复制协议。

方法2、

一、若是是新搭建的服务器,直接启动就好了。

7、master-slave搭建的注意事项:

(一)、使用GTID的方式,把salve端挂载master端:

一、启动之后最好不要当即执行事务,而是先change master上。
二、而后在执行事务,固然知不是必须的。
三、使用下面的sql切换slave到新的master。

stop slave;

CHANGE MASTER TO
MASTER_HOST='127.0.0.1',
MASTER_PORT=3306,
MASTER_USER='repl',
MASTER_PASSWORD='repl',
master_auto_position = 1;

(二)、若是给已经运行的GTID的master端添加一个新的slave

有两种方法:

方法1、适用于master也是新建不久的状况。

一、若是你的master全部的binlog还在。能够选择相似于上面的方法,安装slave,直接change master to到master端。

二、原理是直接获取master全部的GTID并执行。

三、优势:简单方便。

四、缺点:若是binlog太多,数据彻底同步须要时间较长,而且master一开始就启用了GTUD。

方法2、适用于拥有较大数据的状况。(推荐)

一、经过master或者其余slave的备份搭建新的slave。(看第三部分)

二、原理:获取master的数据和这些数据对应的GTID范围,而后经过slave设置@@global.gtid_purged跳过备份包含的gtid。

三、优势:是能够避免第一种方法的不足。

四、缺点:相对来讲有点复杂。

(三)、经过备份搭建新的slave:(方法二的扩展)

两种方法:

方法1、mysqldump的方式:

一、在备份的时候指定--master-data=2(来保存binlog的文件号和位置的命令)。
二、使用mysqldump的命令在dump文件里能够看到下面两个信息:
  SET @@SESSION.SQL_LOG_BIN=0;
  SET @@GLOBAL.GTID_PURGED='7800a22c-95ae-11e4-983d-080027de205a:1-8';
三、将备份还原到slave后,使用change master to命令挂载master端。

注意:在mysql5.6.9之后的命令才支持这个功能。

方法2、percona Xtrabackup

一、Xtrabackup_binlog_info文件中,包含global.gtid_purged='XXXXXX:XXXX'的信息。
二、而后到slave去手工的 SET @@GLOBAL.GTID_PURGED='XXXXXX:XXXX'。
三、恢复备份,开启change master to 命令。

注意:若是系统运行了好久,没法找到GTID的编号了,能够经过上面的方式进行查找。

8、GTID如何跳过事务冲突:

一、这个功能主要跳过事务,代替原来的set global sql_slave_skip_counter = 1。

二、因为在这个GTID必须是连续的,正常状况同一个服务器产生的GTID是不会存在空缺的。因此不能简单的skip掉一个事务,只能经过注入空事物的方法替换掉一个实际操做事务。

三、注入空事物的方法:

stop slave;
set gtid_next='xxxxxxx:N';
begin;commit;
set gtid_next='AUTOMATIC';
start slave;

四、这里的xxxxx:N 也就是你的slave sql thread报错的GTID,或者说是你想要跳过的GTID。

9、GTID的参数注释:

[master]>show global variables like '%gtid%';
一、enforce_gtid_consistency:开启gtid的一些安全限制(介意开启)。

二、gtid_executed:全局和seeeion级别均可以用。用来保存已经执行过的GTIDs。
贴士:show  master status\G;输出结果中的Executed_Gtid_Set和gitd_executed一致。reset master时,此值会被清空。

三、gtid_owned:全局和session级别均可用,全局表示全部服务器拥有GTIDs,session级别表示当前client拥有全部GTIDs。(此功能用的少)

四、gtid_mode:是否开启GTID功能。

五、gtid_purged:全局参数,设置在binlog中,已经purged的GTIDs,而且purged掉的GTIDs会包含到gtid_executed中。

贴士:从而致使slave不会再去master请求这些GTIDs,而且Executed_Gtid_Set为空时,才能够设置此值。

六、gtid_next:这个时session级别的参数:
[master]>show session variables like '%gtid_next%';

10、关于GTID的一些功能限制:

(一)、更新非事务引擎:

一、Case重现:
master:对一个innodb表作一个多sql更新的事物,效果是产生一个GTID。
slave:对应的表是MYISAM引擎,执行这个GTID的第一个语句后就会报错,由于非事务引擎一个sql就是一个事务。

二、错误编号:
last_Errno:1756

三、异常恢复方案:
(1)、简单的stop slave; start slave;就可以忽略错误。可是这个时候主从的一致性已经出现问题。须要手工的把slave差的数据补上。
(2)、首先将引擎调整为同样的,slave也改成事务引擎。

(二)、create table ....select statements

一、case重现:
master:直接执行一个create table select * from table;的sql

二、报错:
error 1786

三、原理:
因为create table ...select语句会生成两个sql,一个是DDL建立表SQL,一个是insert into 插入数据的sql。因为DDL会致使自动提交,因此这个sql至少须要两个GTID,可是GTID模式下,只能给这个sql生成一个GTID,若是强制执行会致使和上面更新非事务引擎同样的结果。

(三)、一个sql同事操做innodb引擎和myisam引擎:

case重现:t1表是innodb,t2表是myisam
一、update t1,t2 set t1.id=1000,t2.id=1000 where t1.id=t2.id;
二、报错:1785
三、原理和第二个相同。

(四)、在一个replication grouop 中,全部的mysql必需要统一开启或者关闭GTID功能。

一、case重现:
将一个未开启gtid的slave经过原始的binlog和pos方式链接到开启GTID的master。

二、报错:
The slave IO thread stops because the master has @@GLOBAL.GTID_MODE ON and this server has @@GLOBAL.GTID_MODE OFF。

(五)、在一个replication group中,若是开启GTID之后,就再也不容许使用classic的复制方式:

一、case重现:
将一个开启gtid的slave经过原始的binlog和pos方式链接到开启GTID的master。

二、报错:
ERROR 1776(HY000):Parameters MASTER_LOG_FILE,MASTER_LOG_POS,RELAY_LOG_FILE and RELAY_LOG_POS cannot be set when MASTER_AUTO_POSITION is active。

(六)、GTID_MODE是not online的:

须要重启才能生效,官方暂时不支持平滑的从classic replication切换到GTID replication。
贴士:
因为GTID开启须要重启系统,一个复制组中全部的实例必须统一开启或者关闭GTID,开启GTID之后不能在使用classic复制。
问题:
也就是说在线业务必须统一关闭,而后再启动,会致使服务中断。

解决方案:
一、针对这种状况,社区有两种对应的平滑升级的方案:
一种是booking.com出品,这两个差异在淘宝9月份数据库月报里有说明,加了一个桥接的服务器,既能够运行GTID模式下,也能够运行classic模式下。
另一种是facebook.com出品。全部的slave能够在开启GTID模式的状况下,能够链接到没有开启GTID模式的master。

二、能够关闭一个部分,中止写操做,可是读不用,将另外一部分改为GTID模式。

(七)、Temporary tables。

一、create temporary table和drop temporary table语句同样在GTID环境下不支持。
若是--enforce_gtid_consistency参数开启,而且autocommit=1,那么可使用。

(八)、关于Errant transaction

一、Errant transaction:所谓的errant transaction也就是没有规范的从master执行,而是直接从slave执行的事务。
二、因为GTID协议的缘由,最开始已经提过(参见GTID architecture)。
三、若是slave有errant transaction产生,因为GTID协议中的规则,很容易致使failover失败。主要有两种状况:

a、在slave上作了无用的或者临时的errant transaction操做,若是该slave升级成为master的话,链接到它的全部数据库都会获取到这个事务。若是同样就会产生冲突。

b、因为作了这个errant transaction这个事务之后,其余的slave尚未获取这个errant transaction的GTID,须要从master上发同步给其余的slave,可是主的binlog又被删掉了,这时将会报错。

四、总之:尽可能避免产生errant transaction。能够经过:set sql_log_bin=off的方式在slave执行sql,可是也要考虑到数据一致性。


··············跳过错误
从库已经执行过的事务是'e10c75be-5c1b-11e6-ab7c-000c296078ae:1-5',执行出错的事务是'e10c75be-5c1b-11e6-ab7c-000c296078ae:6',当前主备的数据实际上是一致的,能够经过设置gtid_next跳过这个出错的事务。

在从库上执行如下SQL:

mysql> set gtid_next='e10c75be-5c1b-11e6-ab7c-000c296078ae:6';
Query OK, 0 rows affected (0.00 sec)

mysql> begin;
Query OK, 0 rows affected (0.00 sec)

mysql> commit;
Query OK, 0 rows affected (0.00 sec)

mysql> set gtid_next='AUTOMATIC';
Query OK, 0 rows affected (0.00 sec)

mysql> start slave;
Query OK, 0 rows affected (0.02 sec)

设置gtid_next的方法一次只能跳过一个事务,要批量的跳过事务能够经过设置gtid_purged完成。


11、GTID与crash safe salve

crash safe slave是MySQL 5.6提供的功能,意思是说在slave crash后,把slave从新拉起来能够继续从Master进行复制,不会出现复制错误也不会出现数据不一致。

一、基于binlog文件位置的复制

在基于binlog文件位置的复制下,要保证crash safe slave,配置下面的参数便可。
relay_log_info_repository = TABLE
relay_log_recovery = ON

这样可行的缘由是,relay_log_info_repository = TABLE时,apply event和更新relay_log_info表的操做被包含在同一个事务里,innodb要么让它们同时生效,要么同时不生效,保证位点信息和已经应用的事务精确匹配。同时relay_log_recovery = ON时,会抛弃master_log_info中记录的复制位点,根据relay_log_info的执行位置从新从Master获取binlog,这就回避了因为未同步刷盘致使的binlog文件接受位置和实际不一致以及relay log文件被截断的问题。

在同时使用MTS(multi-threaded slave)时,为保证crash safe slave基于binlog文件位置的复制还须要设置sync_relay_log=1,由于MySQL在Crash恢复时必须先经过读取relay log补齐MTS致使的事务空洞。

二、基于GTID的复制

上面的设置并不适用于基于GTID的复制。在基于GTID的复制下,crash的Slave重启后,从binlog中解析的gtid_executed决定了要apply哪些binlog记录,因此binlog必须和innodb存储引擎的数据保持一致。要作到这一点,须要把sync_binlog和innodb_flush_log_at_trx_commit都设置为1,即所谓的"双1"。

另外MySQL启动时,会从relay log文件中获取已接收的GTIDs并更新Retrieved_Gtid_Set。因为relay log文件可能不完整,因此须要抛弃已接收的relay log文件。所以relay_log_recovery = ON也是必须的。

这样,对于基于GTID的复制,保证crash safe slave的设置就是下面这样。

sync_binlog = 1
innodb_flush_log_at_trx_commit = 1
relay_log_recovery = ON

关于如何设置以确保crash safe slave,官方文档有明确记载,见 17.3.2 Handling an Unexpected Halt of a Replication Slave。

可是其中关于GTID的记载中存在笔误,将relay_log_recovery=1写成了relay_log_recovery=0 (#83711)。同时也没有提到必须设置"双1",可是"双1"是必要的,不然crash的Slave重启后,可能会重复应用binlog event也可能会遗漏应用binlog event(#70659)。其中遗漏应用binlog event的状况更可怕,由于Slave在不触发SQL错误的状况下就默默的和Master不一致了。

三、设置"双1"对性能的影响

出于安全考虑,强烈推荐设置"双1"。"双1"会增大每一个事务的RT,但得益于MySQL的组提交机制,高并发下"双1"对系统总体tps的影响在可接受范围内。

sysbench oltp.lua 10张表每张表100w记录(qps/并发数)

对更新同一行这样没法有效并行的场景,"双1"对性能的影响很是大。

sysbench update_non_index.lua 1张表1条记录(qps/并发数)

对不能有效并行的Slave replay,存在一样的问题。

经过指定tx-rate执行sysbench的update_non_index.lua脚本压测30秒,完成后检查主备延迟。

能够发如今Slave被配置为"双1"的状况下,延迟很是严重,1000以上的QPS就会出现延迟,非"双1"下QPS到5000以上才会出现延迟(主库配置为"双1")。

sysbench update_non_index.lua 1张表100w条记录 128并发(延迟/qps)

以上测试环境是Percona Server 5.6运行在配置HDD的8 core虚机,因为测试结果和系统IO能力有很大关系,仅供参考。

四、如何在非"双1"下保证crash safe slave

若是是MySQL 5.7能够关闭log_slave_updates,这样MySQL会将已执行的GTIDs实时记录到系统表mysql.gtid_executed中,mysql.gtid_executed是和用户事务一块儿提交的,所以能够保证和实际的数据一致。

log_slave_updates = OFF
relay_log_recovery = ON

若是是MySQL 5.6能够采用以下变通的方式。

按照基于binlog文件复制时crash safe slave的要求设置

relay_log_info_repository = TABLE
relay_log_info_repository = TABLE
relay_log_recovery = ON

在Slave crash后,根据relay_log_info_repository设置相应的gitd_purged再开启复制,
步骤以下:

1.启动MySQL,但不开启复制
mysqld --skip-slave-start

2.在Slave上修改成基于binlog文件位置的复制
change master to MASTER_AUTO_POSITION = 0

3.启动slave IO线程
start slave io_thread
这里不能启动SQL线程,若是接受到的GTID已经在Slave的gtid_executed里了,会被Slave skip掉。

4.检查binlog传输的开始位置(即Retrieved_Gtid_Set的值)
show slave status\G
假设输出的Retrieved_Gtid_Set值为e10c75be-5c1b-11e6-ab7c-000c296078ae:7-10

5.在Master上检查gtid_executed
show master status
假设输出的Executed_Gtid_Set值为e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10

6.在Slave上设置gitd_purged为binlog传输位置的前面的GTID的集合
reset master;
set global gitd_purged='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-6';

7.修改回auto position的复制
change master to MASTER_AUTO_POSITION = 1

8.启动slave SQL线程
start slave sql_thread

可是,这种变通的方法不适合多线程复制。由于多线程复制可能产生gtid gap和Gap-free low-watermark position,这会致使Salve上重复apply已经apply过的event。后果就是数据不一致或者复制中断,除非设置binlog格式为row模式而且slave_exec_mode=IDEMPOTENT,slave_exec_mode=IDEMPOTENT容许Slave回放binlog时忽略重复键和找不到键的错误,使得binlog回放具备幂等性,但这也意味着若是真的出现了主备数据不一致也会被它忽略。

五、MTS下特有的问题

在同时使用MTS(slave_parallel_workers > 1)时,即便按上面crash safe slave的要求设置了基于GTID的复制,Slave crash后再重启仍是会致使复制中断。

经过强制杀掉MySQL所在虚机的方式模拟Slave宕机,而后再启动MySQL,MySQL日志中有以下错误消息:
启动slave时也会报错

mysql> start slave;
ERROR 1872 (HY000): Slave failed to initialize relay log info structure from the repository

出现这种现象的缘由在于,relay_log_recovery=1 且 slave_parallel_workers>1的状况下,mysql启动时会进入MTS Group恢复流程,即读取relay log,尝试填补因为多线程复制致使的gap。而后relay log文件因为不是实时刷新的,在relay log文件中找不到gap对应的relay log记录(覆盖了gap的relay log起始和结束位置分别被称为低水位和高水位,低水位点即slave_relay_log_info.Relay_log_pos的值)就会报这个错。

实际上,在GTID模式下,slave在apply event的时候能够跳太重复事件,因此能够安全的从低水位点应用日志,不必解析relay log文件。 这看上去是一个bug,因而提交了一个bug报告#83713,目前尚未收到回复。

做为回避方法,能够经过清除relay log文件,跳过这个错误。执行步骤以下:

reset slave;
change master to MASTER_AUTO_POSITION = 1
start slave;

在这里,单纯的调reset slave不能把状态清理干净,内部的Relay_log_info.inited标志位仍然处于未被初始化状态,此时调用start slave仍然会失败。所以须要补一刀change master。

六、Master的crash safe

前面一直在讲crash safe slave,Master的crash safe一样重要。 要想Master保持crash safe须要按下面的参数进行设置,不然不只会丢失事务,gtid_executed还可能和实际的innodb存储引擎中的数据不一致。

sync_binlog = 1
innodb_flush_log_at_trx_commit = 1

在Master配置为"双1"的状况下,Master crash后,若是没有发生failover,能够继续做为Master。 若是发生了failover,能够检查旧Master和新Master上由旧Master执行的事务集合是否一致。
show master status

若是一致,能够按MASTER_AUTO_POSITION = 1的方式将旧Master做为Slave和新Master创建复制关系。不然,考虑作事务补偿或重新Master上拉取备份进行恢复。

在Master配置不是"双1"的状况下,在Master crash后因为难以准确知道旧Master上究竟执行了哪些事务,安全的作法是实施主备切换,并重新Master上拉取备份,把旧Master做为新Master的Slave进行恢复。

相关文章
相关标签/搜索