• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录
孙龙 程序员
少时总觉为人易,华年方知立业难
博客园    首页    新随笔    联系   管理    订阅  订阅
mysql GTID 主从复制模式二
mysql GTID 主从复制模式二

一、GTID的概述:

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

2、GTID事物是全局唯一性的,且一个事务对应一个GTID。

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

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

5、MySQL-5.6.5开始支持的,MySQL-5.6.10后开始完善。

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

 

 

二、GTID的组成部分:

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

例如:server_uuid:sequence number

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

UUID:每个mysql实例的唯一ID,由于会传递到slave,所以也可以理解为源ID。

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

 

三、GTID比传统复制的优势:

1、更简单的实现failover,不用以前那样在需要找log_file和log_Pos。

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

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

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

 

四、GTID的工作原理:

1、master更新数据时,会在事务前产生GTID,一同记录到binlog日志中。
2、slave端的i/o 线程将变更的binlog,写入到本地的relay log中。
3、sql线程从relay log中获取GTID,然后对比slave端的binlog是否有记录。
4、如果有记录,说明该GTID的事务已经执行,slave会忽略。
5、如果没有记录,slave就会从relay log中执行该GTID的事务,并记录到binlog。
6、在解析过程中会判断是否有主键,如果没有就用二级索引,如果没有就用全部扫描。

 

 

要点:

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

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

 

 

六、使用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。

2、启动之前,一定要先关闭master的写入,保证所有slave端都已经和master端数据保持同步。

3、所有slave需要加上skip_slave_start=1的配置参数,避免启动后还是使用老的复制协议。

 

方法二、

1、如果是新搭建的服务器,直接启动就行了。

 

 

 

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

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

1、启动以后最好不要立即执行事务,而是先change master上。

2、然后在执行事务,当然知不是必须的。

3、使用下面的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

 有两种方法:

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

1、如果你的master所有的binlog还在。可以选择类似于上面的方法,安装slave,直接change master to到master端。

2、原理是直接获取master所有的GTID并执行。

3、优点:简单方便。

4、缺点:如果binlog太多,数据完全同步需要时间较长,并且master一开始就启用了GTUD。

 

 

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

1、通过master或者其他slave的备份搭建新的slave。(看第三部分)

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

3、优点:是可以避免第一种方法的不足。

4、缺点:相对来说有点复杂。

 

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

两种方法:

方法一、mysqldump的方式:

1、在备份的时候指定--master-data=2(来保存binlog的文件号和位置的命令)。

2、使用mysqldump的命令在dump文件里可以看到下面两个信息:

SET @@SESSION.SQL_LOG_BIN=0;

SET @@GLOBAL.GTID_PURGED='7800a22c-95ae-11e4-983d-080027de205a:1-8';

3、将备份还原到slave后,使用change master to命令挂载master端。

 

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

 

方法二、percona Xtrabackup

1、Xtrabackup_binlog_info文件中,包含global.gtid_purged='XXXXXX:XXXX'的信息。

2、然后到slave去手工的 SET GLOBAL.GTID_PURGED='XXXXXX:XXXX'。

3、恢复备份,开启change master to 命令。

 

注意:如果系统运行了很久,无法找到GTID的变好了,可以通过上面的方式进行查找。

 

 

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

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

2、由于在这个GTID必须是连续的,正常情况同一个服务器产生的GTID是不会存在空缺的。所以不能简单的skip掉一个事务,只能通过注入空事物的方法替换掉一个实际操作事务。

3、注入空事物的方法:

stop slave;

set gtid_next='xxxxxxx:N';

begin;commit;

set gtid_next='AUTOMAIC';

start slave;

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

 

 

九、GTID的参数注释:
[master]>show global variables like '%gtid%';
1、enforce_gtid_consistency:开启gtid的一些安全限制(介意开启)。
 
2、gtid_executed:全局和seeeion级别都可以用。用来保存已经执行过的GTIDs。
贴士:show  master status\G;输出结果中的Executed_Gtid_Set和gitd_executed一致。reset master时,此值会被清空。
 
3、gtid_owned:全局和session级别都可用,全局表示所有服务器拥有GTIDs,session级别表示当前client拥有所有GTIDs。(此功能用的少)
 
4、gtid_mode:是否开启GTID功能。
 
5、gtid_purged:全局参数,设置在binlog中,已经purged的GTIDs,并且purged掉的GTIDs会包含到gtid_executed中。
贴士:从而导致slave不会再去master请求这些GTIDs,并且Executed_Gtid_Set为空时,才可以设置此值。
 
6、gtid_next:这个时session级别的参数:
[master]>show session variables like '%gtid_next%';

 

 

 

十、关于GTID的一些功能限制:
(一)、更新非事务引擎:
1、Case重现:
master:对一个innodb表做一个多sql更新的事物,效果是产生一个GTID。
slave:对应的表是MYISAM引擎,执行这个GTID的第一个语句后就会报错,因为非事务引擎一个sql就是一个事务。
 
2、错误编号:
last_Errno:1756
 
3、异常恢复方案:
(1)、简单的stop slave; start slave;就能够忽略错误。但是这个时候主从的一致性已经出现问题。需要手工的把slave差的数据补上。
(2)、首先将引擎调整为一样的,slave也改为事务引擎。
 
 
 
 
(二)、create table ....select statements
1、case重现:
 master:直接执行一个create table select * from table;的sql
 
2、报错:
error 1786
 
3、原理:
由于create table ...select语句会生成两个sql,一个是DDL创建表SQL,一个是insert into 插入数据的sql。由于DDL会导致自动提交,所以这个sql至少需要两个GTID,但是GTID模式下,只能给这个sql生成一个GTID,如果强制执行会导致和上面更新非事务引擎一样的结果。
 
 
(三)、一个sql同事操作innodb引擎和myisam引擎:
case重现:t1表是innodb,t2表是myisam
1、update t1,t2 set t1.id=1000,t2.id=1000 where t1.id=t2.id;
2、报错:1785
3、原理和第二个相同。
 
 
 
(四)、在一个replication grouop 中,所有的mysql必须要统一开启或者关闭GTID功能。
1、case重现:
将一个未开启gtid的slave通过原始的binlog和pos方式连接到开启GTID的master。
 
2、报错:
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的复制方式:
1、case重现:
将一个开启gtid的slave通过原始的binlog和pos方式连接到开启GTID的master。
 
2、报错:
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复制。
问题:
也就是说在线业务必须统一关闭,然后再启动,会导致服务中断。
 
解决方案:
1、针对这种情况,社区有两种对应的平滑升级的方案:
一种是booking.com出品,这两个差别在淘宝9月份数据库月报里有说明,加了一个桥接的服务器,既可以运行GTID模式下,也可以运行classic模式下。
另外一种是facebook.com出品。所有的slave可以在开启GTID模式的情况下,可以连接到没有开启GTID模式的master。
 
2、可以关闭一个部分,停止写操作,但是读不用,将另一部分改成GTID模式。
 
 
(七)、Temporary tables。
1、create temporary table和drop temporary table语句一样在GTID环境下不支持。
如果--enforce_gtid_consistency参数开启,并且autocommit=1,那么可以使用。
 
 
(八)、关于Errant transaction
1、Errant transaction:所谓的errant transaction也就是没有规范的从master执行,而是直接从slave执行的事务。
2、由于GTID协议的原因,最开始已经提过(参见GTID architecture)。
3、如果slave有errant transaction产生,由于GTID协议中的规则,很容易导致failover失败。主要有两种情况:
 
a、在slave上做了无用的或者临时的errant transaction操作,如果该slave升级成为master的话,连接到它的所有数据库都会获取到这个事务。如果一样就会产生冲突。
 
b、由于做了这个errant transaction这个事务以后,其他的slave还没有获取这个errant transaction的GTID,需要从master上发同步给其他的slave,但是主的binlog又被删掉了,这时将会报错。
 
4、总之:尽量避免产生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完成。
 
-------------------------------

GTID与crash safe salve

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

1、基于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导致的事务空洞。

2、基于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不一致了。

3、设置"双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能力有很大关系,仅供参考。

4、如何在非"双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回放具有幂等性,但这也意味着如果真的出现了主备数据不一致也会被它忽略。

5、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。

6、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进行恢复。

本文来自博客园,作者:孙龙-程序员,转载请注明原文链接:https://www.cnblogs.com/sunlong88/p/9221004.html

posted on 2018-06-24 16:58  孙龙-程序员  阅读(370)  评论(0)    收藏  举报
刷新页面返回顶部
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3