Contents
  1. 1. 一、理论篇
    1. 1.1. 1.1 GTID是什么(what)
      1. 1.1.1. 1.1.1 GTID组成和架构
      2. 1.1.2. 1.1.2 GTID和Binlog的关系
      3. 1.1.3. 1.1.3 重要参数的持久化
      4. 1.1.4. 1.1.4 开启GTID的必备条件
      5. 1.1.5. 1.1.5 新的复制协议 COM_BINLOG_DUMP_GTID
      6. 1.1.6. 1.1.6 GTID重要函数和新语法
    2. 1.2. 1.2 GTID有什么好处(why)
      1. 1.2.1. 1.2.1 classic replication [运维之痛]
      2. 1.2.2. 1.2.2 GTID replication [so easy]
    3. 1.3. 1.3 GTID的Limitation
    4. 1.4. 1.4 MySQL5.7 GTID crash-safe
  2. 2. 二、实战篇(how)
    1. 2.1. 2.1 使用GTID搭建Replication
      1. 2.1.1. 2.1.1 从0开始搭建
      2. 2.1.2. 2.1.2 从备份中恢复&搭建
    2. 2.2. 2.2 如何从classic replication 升级成 GTID replication
      1. 2.2.1. 2.2.1 offline 方式升级
      2. 2.2.2. 2.2.2 online 方式升级
    3. 2.3. 2.3 GTID failover
      1. 2.3.1. 2.3.1 MySQL crash
      2. 2.3.2. 2.3.2 OS crash
    4. 2.4. 2.4 GTID 运维和错误处理
      1. 2.4.1. 2.4.1 错误场景: Errant transaction
    5. 2.5. 重点: 当发生inject empty transction后,有可能会丢失事务
  3. 3. 三、QA
    1. 3.1. 3.1 如何重置gtid_executed,gtid_purged。
    2. 3.2. 3.2 如果auto.cnf 被删掉了,对于GTID的复制会有什么影响?
    3. 3.3. 3.3 手动设置 set @@gtid_purged = xx:yy, mysql会去主动修改binlog的头么
    4. 3.4. 3.4 GTID和复制过滤规则之间如何协同工作?MySQL,test还能愉快的过滤掉吗?
  4. 4. 四、后续分享: Automatic failover with mysqlfailover+GTID
    1. 4.1. 4.1 Planned promotion (switchover)
    2. 4.2. 4.2 Unplanned promotion (failover)

一、理论篇

1.1 GTID是什么(what)

1.1.1 GTID组成和架构

  • GTID 架构

a) GTID = server_uuid:transaction_id
b) server_uuid 来源于 auto.cnf
c) GTID: 在一组复制中,全局唯一

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
gtid_set:
uuid_set [, uuid_set] ...
| ''
uuid_set:
uuid:interval[:interval]...
uuid:
hhhhhhhh-hhhh-hhhh-hhhh-hhhhhhhhhhhh
h:
[0-9|A-F]
interval:
n[-n]
(n >= 1)

1.1.2 GTID和Binlog的关系

  • GTID在binlog中的结构

gtid_binlog

  • GTID event 结构

gtid_event

  • Previous_gtid_log_event

    1. Previous_gtid_log_event 在每个binlog 头部都会有
    2. 每次binlog rotate的时候存储在binlog头部
    3. Previous-GTIDs在binlog中只会存储在这台机器上执行过的所有binlog,不包括手动设置gtid_purged值。
    4. 换句话说,如果你手动set global gtid_purged=xx; 那么xx是不会记录在Previous_gtid_log_event中的。
  • GTID和Binlog之间的关系是怎么对应的呢

如何才能找到GTID=?对应的binlog文件呢?

1
2
3
4
5
6
7
8
9
* 假设有4个binlog: bin.001,bin.002,bin.003,bin.004
* bin.001 : Previous-GTIDs=empty; binlog_event有:1-40
* bin.002 : Previous-GTIDs=1-40; binlog_event有:41-80
* bin.003 : Previous-GTIDs=1-80; binlog_event有:81-120
* bin.004 : Previous-GTIDs=1-120; binlog_event有:121-160
1. 假设现在我们要找GTID=$A,那么MySQL的扫描顺序为: 从最后一个binlog开始扫描(即:bin.004
2. bin.004Previous-GTIDs=1-120,如果$A=140 > Previous-GTIDs,那么肯定在bin.004
3. bin.004Previous-GTIDs=1-120,如果$A=88 包含在Previous-GTIDs中,那么继续对比上一个binlog文件 bin.003,然后再循环前面2个步骤,直到找到为止

1.1.3 重要参数的持久化

  • GTID相关参数
参数 comment
gtid_executed 执行过的所有GTID
gtid_purged 丢弃掉的GTID
gtid_mode gtid模式
gtid_next session级别的变量,下一个gtid
gtid_owned 正在运行的gtid
enforce_gtid_consistency 保证GTID安全的参数
  • 重要参数如何持久化

1) 如何持久化gtid_executed [ log-bin=on,log_slave_update=on ]

1
2
3
1. gtid_executed = mysql.gtid_executed 【normal
or
2. gtid_executed = mysql.gtid_executed + last_binlog中最后没写到mysql.gtid_executed中的gtid_event 【recover

2) 如何持久化重置的gtid_purged值?

reset master; set global gtid_purged=$A:a-b;

1
2
3
4
1. 由于有可能手动设置过gtid_purged=$A:a-b, binlog.index中,last_binlog的Previous-GTIDs并不会包含$A:a-b
2. 由于有可能手动设置过gtid_purged=$A:a-b, binlog.index中,first_binlog的Previous-GTIDs肯定不会出现$A:a-b
3. 重置的gtid_purged = @@global.gtid_executed(mysql.gtid_executed:注意,考虑到这个表的更新触发条件,所以这里用@@global.gtid_executed代替) - last_binlog的Previous-GTIDs - last_binlog所有的gtid_event
4. 下面就用 $reset_gtid_purged 来表示重置的gtid

3)如何持久化gtid_purged [ log-bin=on,log_slave_update=on ]

1
gtid_purged=binlog.index:first_binlog的Previous-GTIDs + $reset_gtid_purged

1.1.4 开启GTID的必备条件

  • MySQL 5.6
1
2
3
4
gtid_mode=ON(必选)
log_bin=ON(必选)
log-slave-updates=ON(必选)
enforce-gtid-consistency(必选)
  • MySQL 5.7

    MySQL5.7.13 or higher

1
2
3
4
gtid_mode=ON(必选)
enforce-gtid-consistency(必选)
log_bin=ON(可选)--高可用切换,最好设置ON
log-slave-updates=ON(可选)--高可用切换,最好设置ON

1.1.5 新的复制协议 COM_BINLOG_DUMP_GTID

  • slave会将已经执行过的gtid,以及以及接受到relay log中的gtid的并集发送给master
1
2
3
* http://dev.mysql.com/doc/refman/5.7/en/change-master-to.html
UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID)
  • Master send all other transactions to slave

  • 同样的GTID不能被执行两次,如果有同样的GTID,会自动被skip掉。

com_binlog_dump_gtid

slave1 : 将自身的UUID1:1 发送给 master,然后接收到了 UUID1:2,UUID1:3 event
slave2 : 将自身的UUID1:1,UUID1:2 发送给 master,然后接收到了UUID1:3 event

1.1.6 GTID重要函数和新语法

  • 重要函数
Name Description
GTID_SUBSET(subset,set) returns true (1) if all GTIDs in subset are also in set
GTID_SUBTRACT(set,subset) returns only those GTIDs from set that are not in subset
WAIT_FOR_EXECUTED_GTID_SET(gtid_set[, timeout]) Wait until the given GTIDs have executed on slave.
WAIT_UNTIL_SQL_THREAD_AFTER_GTIDS(gtid_set[, timeout][,channel]) Wait until the given GTIDs have executed on slave
  • 新语法
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
START SLAVE [thread_types] [until_option] [connection_options]
thread_types:
[thread_type [, thread_type] ... ]
thread_type:
IO_THREAD | SQL_THREAD
until_option:
UNTIL { {SQL_BEFORE_GTIDS | SQL_AFTER_GTIDS} = gtid_set
| MASTER_LOG_FILE = 'log_name', MASTER_LOG_POS = log_pos
| RELAY_LOG_FILE = 'log_name', RELAY_LOG_POS = log_pos
| SQL_AFTER_MTS_GAPS }
* 举个栗子:
1. START SLAVE SQL_THREAD UNTIL SQL_BEFORE_GTIDS = 3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56
表示,当SQL_thread 执行到3E11FA47-71CA-11E1-9E33-C80AA9429562:10 的时候停止,下一个事务是11
2. START SLAVE SQL_THREAD UNTIL SQL_AFTER_GTIDS = 3E11FA47-71CA-11E1-9E33-C80AA9429562:11-56
表示,当SQL_thread 执行到3E11FA47-71CA-11E1-9E33-C80AA9429562:56 的时候停止,56是最后一个提交的事务。

1.2 GTID有什么好处(why)

1.2.1 classic replication [运维之痛]

classic_rpl

1.2.2 GTID replication [so easy]

gtid_rpl

1.3 GTID的Limitation

  • 不安全的事务

    设置enforce-gtid-consistency=ON

1
2
3
1. CREATE TABLE ... SELECT statements
2. CREATE TEMPORARY TABLE or DROP TEMPORARY TABLE statements inside transactions
3. 同时更新 事务引擎 和 非事务引擎。

1.4 MySQL5.7 GTID crash-safe

http://dev.mysql.com/doc/refman/5.7/en/replication-solutions-unexpected-slave-halt.html

关于crash safe , 可以参考官方文档列出的安全配置

  • 单线程复制

    Non-GTID 推荐配置: relay_log_recovery=1,relay_log_info_repository=TABLE,master_info_repository=TABLE
    GTID 推荐配置:MASTER_AUTO_POSITION=on,relay_log_recovery=0

safe_1

  • 多线程复制

    Non-GTID 推荐配置: relay_log_recovery=1, sync_relay_log=1,relay_log_info_repository=TABLE,master_info_repository=TABLE
    GTID 推荐配置: MASTER_AUTO_POSITION=on, relay_log_recovery=0

safe_multi

二、实战篇(how)

2.1 使用GTID搭建Replication

2.1.1 从0开始搭建

  • step 1: 让所有server处于同一个点
1
mysql> SET @@global.read_only = ON;
  • step 2: 关闭所有MySQL
1
shell> mysqladmin -uusername -p shutdown
  • step 3: 重启所有MySQL,并开启GTID
1
shell> mysqld --gtid-mode=ON --log-bin --enforce-gtid-consistency &

当然,在my.cnf中配置好最佳

  • step 4: change master
1
2
3
4
5
6
mysql> CHANGE MASTER TO
> MASTER_HOST = host,
> MASTER_PORT = port,
> MASTER_USER = user,
> MASTER_PASSWORD = password,
> MASTER_AUTO_POSITION = 1;
1
mysql> START SLAVE;
  • step 5: 让master 可读可写
1
mysql> SET @@global.read_only = OFF;

2.1.2 从备份中恢复&搭建

  • step 1: 备份
1
2
3
mysqldump xx 获取并且记录gtid_purged值
or
冷备份 --获取并且记录gtid_executed值,这个就相当于mysqldump中得到的gtid_purged
  • step 2: 在新服务器上reset master,导入备份
1
2
3
reset master; --清空gtid信息
导入备份; --如果是逻辑导入,请设置sql_log_bin=off
set global gtid_purged=xx;
  • step 3: change master
1
2
3
4
5
6
mysql> CHANGE MASTER TO
> MASTER_HOST = host,
> MASTER_PORT = port,
> MASTER_USER = user,
> MASTER_PASSWORD = password,
> MASTER_AUTO_POSITION = 1;
1
mysql> START SLAVE;

2.2 如何从classic replication 升级成 GTID replication

2.2.1 offline 方式升级

offline 的方式升级最简单。全部关机,然后配置好GTID,重启,change master to MASTER_AUTO_POSITION=1。

2.2.2 online 方式升级

这里先介绍几个重要GTID_MODE的value

  • GTID_MODE = OFF : 不产生Normal_GTID,只接受来自master的ANONYMOUS_GTID
  • GTID_MODE = OFF_PERMISSIVE : 不产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
  • GTID_MODE = ON_PERMISSIVE : 产生Normal_GTID,可以接受来自master的ANONYMOUS_GTID & Normal_GTID
  • GTID_MODE = ON : 产生Normal_GTID,只接受来自master的Normal_GTID

  • master和slave的gtid_mode 组合搭配矩阵图

    水平的GTID_MODE为:master , 垂直的GTID_MODE为:slave

gtid_mode OFF(master) OFF_PERMISSIVE(master) ON_PERMISSIVE(master) ON(master)
OFF(slave) Y Y N N
OFF_PERMISSIVE(slave) Y Y Y Y(auto_position可以开启)
ON_PERMISSIVE(slave) Y Y Y Y(auto_position可以开启)
ON(slave) N N Y Y(auto_position可以开启)

归纳总结:
1) 当master产生Normal_GTID的时候(ON_PERMISSIVE,ON),如果slave的gtid_mode(OFF)不能接受Normal_GTID,那么就会报错
2) 当master产生ANONYMOUS_GTID的时候(OFF_PERMISSIVE,OFF),如果slave的gtid_mode(ON)不能接受ANONYMOUS_GTID,那么就会报错
3) 设置auto_position的条件: 当master gtid_mode=ON时,slave可以为OFF_PERMISSIVE,ON_PERMISSIVE,ON。除此之外,都不能设置auto_position = on

下面罗列下,如何online 升级为GTID模式。

  • step 1: 每台server执行

    检查错误日志,直到没有错误出现,才能进行下一步

1
SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = WARN;
  • step 2: 每台server执行
1
SET @@GLOBAL.ENFORCE_GTID_CONSISTENCY = ON;
  • step 3: 每台server执行

    不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步

1
SET @@GLOBAL.GTID_MODE = OFF_PERMISSIVE;
  • step 4: 每台server执行

    不用关心一组复制集群的server的执行顺序,只需要保证每个Server都执行了,才能进行下一步

1
SET @@GLOBAL.GTID_MODE = ON_PERMISSIVE;
  • step 5: 在每台server上执行,如果ONGOING_ANONYMOUS_TRANSACTION_COUNT=0就可以

    不需要一直为0,只要出现过0一次,就ok

1
SHOW STATUS LIKE 'ONGOING_ANONYMOUS_TRANSACTION_COUNT';
  • step 6: 确保所有anonymous事务传递到slave上了
1
2
3
4
5
6
7
8
9
10
1. master
SHOW MASTER STATUS;
2. 每个slave
SELECT MASTER_POS_WAIT(file, position);
或者,等一段时间,只要不是大的延迟,一般都没问题
  • step 7: 每台Server上执行
1
SET @@GLOBAL.GTID_MODE = ON;
  • step 8: 在每台server上将my.cnf中添加好gtid配置
1
2
3
4
gtid_mode=ON(必选)
enforce-gtid-consistency(必选)
log_bin=ON(可选)--高可用切换,最好设置ON
log-slave-updates=ON(可选)--高可用切换,最好设置ON
  • step 9: change master
1
2
3
STOP SLAVE;
CHANGE MASTER TO MASTER_AUTO_POSITION = 1;
START SLAVE;

2.3 GTID failover

2.3.1 MySQL crash

配置好loss-less semi-sync replication,可以更可靠的保证数据零丢失。
以下说的都是crash 后,起不来的情况

  • binlog 在master还有日志没有传递到 slave
1
2
3
1. 选取最新的slave,change master to maseter_auto_position同步好
2. mysqlbinlog 将没传递过来的binlog在新master上replay
3. 打开新master的surper_read_only=off;
  • binlog 已经传递到slave
1
2
1. 选取最新的slave,change master to maseter_auto_position同步好
2. 打开新master的surper_read_only=off;

2.3.2 OS crash

1
2
1. 选取最新的slave,change master to maseter_auto_position同步好
2. 打开新master的surper_read_only=off;

以上操作,在传统模式复制下,只能通过MHA来实现,MHA比较复杂。
现在,在GTID模式下,实现起来非常简单,且非常方便。

2.4 GTID 运维和错误处理

  1. 使用GTID后,对原来传统的运维有不同之处了,需要调整过来。
  2. 使用Row模式且复制配置正确的情况下,基本上很少发现有复制出错的情况。
  3. slave 设置 super_read_only=on

2.4.1 错误场景: Errant transaction

出现这种问题基本有两种情况

  1. 复制参数没有配置正确,当slave crash后,会出现重复键问题
  2. DBA操作不正确,不小心在slave上执行了事务

对于第一个重复键问题

  • 传统模式
1
2
3
4
* skip transation;
SQL> SET GLOBAL SQL_SLAVE_SKIP_COUNTER=1;
SQL> START SLAVE;
  • GTID模式
1
2
3
4
SQL> SET GTID_NEXT='b9b4712a-df64-11e3-b391-60672090eb04:7'; --设置需要跳过的gtid event
SQL> BEGIN;COMMIT;
SQL> SET GTID_NEXT='AUTOMATIC';
SQL> START SLAVE;

对于第二种不小心多执行了事务

这种情况就比较难了,这样已经导致了数据不一致,大多数情况,建议slave重做
如何避免: slave 设置 super_read_only=on

重点: 当发生inject empty transction后,有可能会丢失事务

这里说下inject empty transction的隐患

  1. 当slave上inject empty transction,说明有一个master的事务被忽略了(这里假设是 $uuid:100)
  2. 事务丢失一:如果此时此刻master挂了,这个slave被选举为新master,那么其他的slave如果还没有执行到$uuid:100,就会丢失掉$uuid:100这个事务。
  3. 事务丢失二:如果从备份中重新搭建一个slave,需要重新执行之前的所有事务,而此时,master挂了, 又回到了事务丢失一的场景。

三、QA

3.1 如何重置gtid_executed,gtid_purged。

  • 设置gtid_executed
1
目前只能够reset master
  • 设置gtid_purged
1
2
* 当gtid_executed 非空的时候,不能设置gtid_purged
* 当gtid_executed 为空的时候(即刚刚备份好的镜像,刚搭建的mysql),可以直接SET @@GLOBAL.GTID_PURGED='0ad6eae9-2d66-11e6-864f-ecf4bbf1f42c:1-3';

3.2 如果auto.cnf 被删掉了,对于GTID的复制会有什么影响?

1
如果被删掉,重启后,server-uuid 会变

3.3 手动设置 set @@gtid_purged = xx:yy, mysql会去主动修改binlog的头么

1
不会

3.4 GTID和复制过滤规则之间如何协同工作?MySQL,test还能愉快的过滤掉吗?

1
可以,改过滤的会自己过滤,不用担心

四、后续分享: Automatic failover with mysqlfailover+GTID

http://dev.mysql.com/doc/refman/5.7/en/replication-solutions-switch.html

4.1 Planned promotion (switchover)

4.2 Unplanned promotion (failover)

Contents
  1. 1. 一、理论篇
    1. 1.1. 1.1 GTID是什么(what)
      1. 1.1.1. 1.1.1 GTID组成和架构
      2. 1.1.2. 1.1.2 GTID和Binlog的关系
      3. 1.1.3. 1.1.3 重要参数的持久化
      4. 1.1.4. 1.1.4 开启GTID的必备条件
      5. 1.1.5. 1.1.5 新的复制协议 COM_BINLOG_DUMP_GTID
      6. 1.1.6. 1.1.6 GTID重要函数和新语法
    2. 1.2. 1.2 GTID有什么好处(why)
      1. 1.2.1. 1.2.1 classic replication [运维之痛]
      2. 1.2.2. 1.2.2 GTID replication [so easy]
    3. 1.3. 1.3 GTID的Limitation
    4. 1.4. 1.4 MySQL5.7 GTID crash-safe
  2. 2. 二、实战篇(how)
    1. 2.1. 2.1 使用GTID搭建Replication
      1. 2.1.1. 2.1.1 从0开始搭建
      2. 2.1.2. 2.1.2 从备份中恢复&搭建
    2. 2.2. 2.2 如何从classic replication 升级成 GTID replication
      1. 2.2.1. 2.2.1 offline 方式升级
      2. 2.2.2. 2.2.2 online 方式升级
    3. 2.3. 2.3 GTID failover
      1. 2.3.1. 2.3.1 MySQL crash
      2. 2.3.2. 2.3.2 OS crash
    4. 2.4. 2.4 GTID 运维和错误处理
      1. 2.4.1. 2.4.1 错误场景: Errant transaction
    5. 2.5. 重点: 当发生inject empty transction后,有可能会丢失事务
  3. 3. 三、QA
    1. 3.1. 3.1 如何重置gtid_executed,gtid_purged。
    2. 3.2. 3.2 如果auto.cnf 被删掉了,对于GTID的复制会有什么影响?
    3. 3.3. 3.3 手动设置 set @@gtid_purged = xx:yy, mysql会去主动修改binlog的头么
    4. 3.4. 3.4 GTID和复制过滤规则之间如何协同工作?MySQL,test还能愉快的过滤掉吗?
  4. 4. 四、后续分享: Automatic failover with mysqlfailover+GTID
    1. 4.1. 4.1 Planned promotion (switchover)
    2. 4.2. 4.2 Unplanned promotion (failover)

幸福,不在于得到的多

而在于计较的少