在 「MySQL 5.7多源复制实践」 一文中咱们讲解了 MySQL 5.7 新特性多源复制的实现方法。今天咱们来说讲 MySQL 5.7 的另外一个新特性基于 GTID 的主从复制实现。html
从 MySQL 5.6.5 开始新增了一种基于 GTID 的复制方式。经过 GTID 保证了每一个在主库上提交的事务在集群中有一个惟一的ID。这种方式强化了数据库的主备一致性,故障恢复以及容错能力。node
在原来基于二进制日志的复制中,从库须要告知主库要从哪一个偏移量进行增量同步,若是指定错误会形成数据的遗漏,从而形成数据的不一致。借助GTID,在发生主备切换的状况下,MySQL的其它从库能够自动在新主库上找到正确的复制位置,这大大简化了复杂复制拓扑下集群的维护,也减小了人为设置复制位置发生误操做的风险。另外,基于GTID的复制能够忽略已经执行过的事务,减小了数据发生不一致的风险。mysql
什么是GTIDlinux
GTID (Global Transaction ID) 是对于一个已提交事务的编号,而且是一个全局惟一的编号。 GTID 实际上 是由 UUID+TID 组成的。其中 UUID 是一个 MySQL 实例的惟一标识。TID 表明了该实例上已经提交的事务数量,而且随着事务提交单调递增。下面是一个GTID的具体形式:git
1 |
3E11FA47-71CA-11E1-9E33-C80AA9429562:23 |
一组连续的事务能够用 -
链接的事务序号范围表示。例如:github
1 |
e6954592-8dba-11e6-af0e-fa163e1cf111:1-5 |
GTID 集合能够包含来自多个 MySQL 实例的事务,它们之间用逗号分隔。若是来自同一 MySQL 实例的事务序号有多个范围区间,各组范围之间用冒号分隔。例如:算法
1 2 |
e6954592-8dba-11e6-af0e-fa163e1cf111:1-5:11-18, e6954592-8dba-11e6-af0e-fa163e1cf3f2:1-27 |
可使用 SHOW MASTER STATUS
实时看当前的事务执行数。sql
GTID的做用数据库
GTID 的使用不仅仅是用单独的标识符替换旧的二进制日志文件和位置,它也采用了新的复制协议。旧的协议每每简单直接,即:首先从服务器上在一个特定的偏移量位置链接到主服务器上一个给定的二进制日志文件,而后主服务器再从给定的链接点开始发送全部的事件。vim
新协议稍有不一样:支持以全局统一事务 ID (GTID) 为基础的复制。当在主库上提交事务或者被从库应用时,能够定位和追踪每个事务。GTID 复制是所有以事务为基础,使得检查主从一致性变得很是简单。若是全部主库上提交的事务也一样提交到从库上,一致性就获得了保证。
GTID 相关操做:默认状况下将一个事务记录进二进制文件时,首先记录它的 GTID,并且 GTID 和事务相关信息一并要发送给从服务器,由从服务器在本地应用认证,可是绝对不会改变原来的事务 ID 号。所以在 GTID 的架构上就算有了N层架构,复制是N级架构,事务 ID 依然不会改变,有效的保证了数据的完整和安全性。
你可使用基于语句的或基于行的复制与 GTID ,可是,为了得到最佳效果,咱们建议你使用基于行(ROW)的格式。
GTID功能的具体概括主要有如下两点:
咱们能够看下在 MySQL 5.6 的 GTID 出现之前 Replication Failover 的操做过程。假设咱们有一个以下图的环境:
此时,Server A 的服务器宕机,须要将业务切换到 Server B 上。同时,咱们又须要将 Server C 的复制源改为 Server B。复制源修改的命令语法很简单即:
1 |
CHANGE MASTER TO MASTER_HOST='xxx', MASTER_LOG_FILE='xxx', MASTER_LOG_POS=nnnn |
而这种方式的难点在于,因为同一个事务在每台机器上所在的 binlog 名字和位置都不同,那么怎么找到 Server C 当前同步中止点对应 Server B 上 master_log_file
和 master_log_pos
的位置就成为了难题。
这也就是为何 M-S
复制集群须要使用 MMM
、MHA
这样的额外管理工具的一个重要缘由。 这个问题在 5.6 的 GTID 出现后,就显得很是的简单。
因为同一事务的 GTID 在全部节点上的值一致,那么根据 Server C 当前中止点的 GTID 就能惟必定位到 Server B 上的GTID。甚至因为 MASTER_AUTO_POSITION
功能的出现,咱们都不须要知道 GTID 的具体值。直接使用如下命令就能够直接完成 Failover 的工做。
1 |
CHANGE MASTER TO MASTER_HOST='xxx', MASTER_AUTO_POSITION=='xxx' |
如何产生GTID
GTID 的生成受 gtid_next
控制。 在主服务器上gtid_next
是默认的 AUTOMATIC
,即在每次事务提交时自动生成新的 GTID 。它从当前已执行的 GTID 集合(即 gtid_executed
)中,找一个大于 0 的未使用的最小值做为下个事务 GTID 。同时在 Binlog 的实际的更新事务事件前面插入一条 set gtid_next
事件。
这里以一条 insert 语句来看看 GTID 的生成过程:
在从库上回放主库的 Binlog 时,先执行 SET @@SESSION.GTID_NEXT
语句,而后再执行 insert 语句,确保在主和备上这条 insert 对应于相同的 GTID。
通常状况下,GTID集合是连续的,但使用多线程复制(MTS)以及经过 gtid_next
进行人工干预时会致使 GTID 空洞。
继续执行事务,MySQL 会分配一个最小的未使用 GTID,也就是从出现空洞的地方分配 GTID,最终会把空洞填上。
这意味着严格来讲咱们即不能假设 GTID 集合是连续的,也不能假定 GTID 序号大的事务在GTID序号小的事务以后执行,事务的顺序应由事务记录在 Binlog 中的前后顺序决定。
什么是server-uuid
MySQL 5.6 之后用 128 位的 server-uuid
代替了本来的 32 位 server-id
的大部分功能。缘由很简单,server-id
依赖于 my.cnf 的手工配置,有可能产生冲突。而自动产生 128 位 UUID 的算法能够保证全部的 MySQL UUID 都不会冲突。
MySQL 在数据目录下有一个 auto.cnf 文件就是用来保存 server-uuid
的,以下:
1 2 3 |
$ cat /var/lib/mysql/auto.cnf [auto] server-uuid=f75ae43f-3f5e-11e7-9b98-001c4297532a |
在 MySQL 再次启动时会读取 auto.cnf 文件,继续使用上次生成的 server_uuid
。使用 SHOW
命令能够查看 MySQL 实例当前使用的 server-uuid
,它是一个 MySQL Global Variables。
1 |
SHOW GLOBAL VARIABLES LIKE ‘server_uuid'; |
这里一共使用了二台机器,MySQL 版本都为 5.7.18。
机器名 | IP地址 | MySQL角色 |
---|---|---|
dev-master-01 | 192.168.2.210 | MySQL 主库 |
dev-node-02 | 192.168.2.212 | MySQL 从库 |
MySQL 安装比较简单,在 「MySQL 5.7多源复制实践」一文中咱们也讲了,这里就不在重复讲了。若是你还不会安装,能够先参考此文安装好 MySQL 。
GTID主从复制的配置思路
配置 MySQL 基于GTID的复制,主要是须要在 MySQL 服务器的主配置文件 [mysqld]
段中添加如下内容:
1 2 3 |
gtid-mode = ON enforce-gtid-consistency = ON log-slave-updates = ON |
在 MySQL 5.6 版本时,基于 GTID 的复制中 log-slave-updates
选项是必须的。可是其增大了从服务器的IO负载, 而在 MySQL 5.7 中该选项已经不是必须项。
MySQL主服务器配置片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
$ vim /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] server-id = 1 binlog_format = row expire_logs_days = 30 max_binlog_size = 100M gtid_mode = ON enforce_gtid_consistency = ON binlog-checksum = CRC32 master-verify-checksum = 1 log-bin = /var/log/mysql/mysql-bin log_bin_index = /var/log/mysql/mysql-bin.index log-slave-updates = ON |
MySQL从服务器配置片段
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
$ vim /etc/mysql/mysql.conf.d/mysqld.cnf [mysqld] server-id = 3 gtid_mode = ON enforce_gtid_consistency = ON log-slave-updates = ON skip-slave-start = true expire_logs_days = 30 max_binlog_size = 100M read_only = ON slave-sql-verify-checksum = 1 log-bin = /var/log/mysql/mysql-bin log_bin_index = /var/log/mysql/mysql-bin.index relay-log = /var/log/mysql/relay-log relay-log-index = /var/log/mysql/relay-log-index relay-log-info-file = /var/log/mysql/relay-log.info master-info-repository = table relay-log-info-repository = table relay-log-recovery = ON report-port = 3306 report-host = 192.168.2.212 replicate-do-db = master1 replicate_wild_do_table=master1.% |
注:server-id
每台必须配置为不同,好比 dev-master-01 为1,dev-node-02 为3。这里没有给出所有配置,其它请根据实际状况自行配置。
1 |
$ service mysql restart |
基于 GTID 的复制会自动地将没有在从库执行过的事务重放,因此不要在其它从库上创建相同的帐号。 若是创建了相同的帐户,有可能形成复制链路的错误。
1 2 3 |
# 在MySQL主服务器上建立 mysql> grant replication slave on *.* to 'repl'@'192.168.2.%' identified by '000000'; mysql> flush privileges; |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
mysql> show variables like "%gtid%"; +----------------------------------+-----------+ | Variable_name | Value | +----------------------------------+-----------+ | binlog_gtid_simple_recovery | ON | | enforce_gtid_consistency | ON | | gtid_executed_compression_period | 1000 | | gtid_mode | ON | | gtid_next | AUTOMATIC | | gtid_owned | | | gtid_purged | | | session_track_gtids | OFF | +----------------------------------+-----------+ 8 rows in set (0.00 sec) mysql> show variables like '%gtid_next%'; +---------------+-----------+ | Variable_name | Value | +---------------+-----------+ | gtid_next | AUTOMATIC | +---------------+-----------+ 1 row in set (0.00 sec) |
简单说下几个经常使用参数的做用:
a) gtid_executed
在当前实例上执行过的 GTID 集合,实际上包含了全部记录到 binlog 中的事务。设置 set sql_log_bin=0
后执行的事务不会生成 binlog 事件,也不会被记录到 gtid_executed
中。执行 RESET MASTER
能够将该变量置空。
b) gtid_purged
binlog 不可能永远驻留在服务上,须要按期进行清理(经过 expire_logs_days
能够控制按期清理间隔),不然早晚它会把磁盘用尽。
gtid_purged
用于记录本机上已经执行过,可是已经被清除了的 binlog 事务集合。它是 gtid_executed
的子集。只有 gtid_executed
为空时才能手动设置该变量,此时会同时更新 gtid_executed
为和 gtid_purged
相同的值。
gtid_executed
为空意味着要么以前没有启动过基于 GTID 的复制,要么执行过 RESET MASTER
。执行 RESET MASTER
时一样也会把 gtid_purged
置空,即始终保持 gtid_purged
是 gtid_executed
的子集。
c) gtid_next
会话级变量,指示如何产生下一个GTID。可能的取值以下:
第一个:AUTOMATIC
自动生成下一个 GTID,实现上是分配一个当前实例上还没有执行过的序号最小的 GTID。
第二个:ANONYMOUS
设置后执行事务不会产生GTID。
第三个:显式指定的GTID
能够指定任意形式合法的 GTID 值,但不能是当前 gtid_executed
中的已经包含的 GTID,不然下次执行事务时会报错。
1 2 3 4 5 6 7 8 |
mysql> show global variables like '%uuid%'; +---------------+--------------------------------------+ | Variable_name | Value | +---------------+--------------------------------------+ | server_uuid | f75ae43f-3f5e-11e7-9b98-001c4297532a | +---------------+--------------------------------------+ 1 row in set (0.00 sec) |
1 |
mysql> CHANGE MASTER TO MASTER_HOST='192.168.2.210',MASTER_USER='repl',MASTER_PASSWORD='000000',MASTER_AUTO_POSITION=1; |
1 |
mysql> START SLAVE; |
启动成功后查看SLAVE的状态
1 2 3 4 5 6 |
mysql> SHOW SLAVE STATUS\G ... Slave_IO_Running: Yes Slave_SQL_Running: Yes ... |
确认 Slave_IO_Running
和 Slave_SQL_Running
两个参数都为 Yes 状态。
在主服务器查看从库链接的主机信息
1 2 3 4 |
mysql> create database master1; mysql> use master1; mysql> CREATE TABLE `test1` (`id` int(11) DEFAULT NULL,`count` int(11) DEFAULT NULL); mysql> insert into test1 values(1,1); |
1 2 3 4 5 6 7 |
mysql> select * from master1.test1; +------+-------+ | id | count | +------+-------+ | 1 | 1 | +------+-------+ 1 row in set (0.00 sec) |
检查从服务器状态
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
mysql> show slave status\G *************************** 1. row *************************** Slave_IO_State: Waiting for master to send event Master_Host: 192.168.2.210 Master_User: repl Master_Port: 3306 Connect_Retry: 60 Master_Log_File: mysql-bin.000001 Read_Master_Log_Pos: 977 Relay_Log_File: relay-log.000002 Relay_Log_Pos: 1190 Relay_Master_Log_File: mysql-bin.000001 Slave_IO_Running: Yes Slave_SQL_Running: Yes Replicate_Do_DB: master1 Replicate_Ignore_DB: Replicate_Do_Table: Replicate_Ignore_Table: Replicate_Wild_Do_Table: master1.% Replicate_Wild_Ignore_Table: Last_Errno: 0 Last_Error: Skip_Counter: 0 Exec_Master_Log_Pos: 977 Relay_Log_Space: 1391 Until_Condition: None Until_Log_File: Until_Log_Pos: 0 Master_SSL_Allowed: No Master_SSL_CA_File: Master_SSL_CA_Path: Master_SSL_Cert: Master_SSL_Cipher: Master_SSL_Key: Seconds_Behind_Master: 0 Master_SSL_Verify_Server_Cert: No Last_IO_Errno: 0 Last_IO_Error: Last_SQL_Errno: 0 Last_SQL_Error: Replicate_Ignore_Server_Ids: Master_Server_Id: 1 Master_UUID: f75ae43f-3f5e-11e7-9b98-001c4297532a Master_Info_File: mysql.slave_master_info SQL_Delay: 0 SQL_Remaining_Delay: NULL Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates Master_Retry_Count: 86400 Master_Bind: Last_IO_Error_Timestamp: Last_SQL_Error_Timestamp: Master_SSL_Crl: Master_SSL_Crlpath: Retrieved_Gtid_Set: f75ae43f-3f5e-11e7-9b98-001c4297532a:1-4 Executed_Gtid_Set: 2c55f623-4fea-11e7-82c7-001c4283459b:1, f75ae43f-3f5e-11e7-9b98-001c4297532a:1-4 Auto_Position: 1 Replicate_Rewrite_DB: Channel_Name: Master_TLS_Version: 1 row in set (0.00 sec) |
能够看到 IO 和 SQL 线程都为 YES ,另外 retrieved_Gtid_Set
接收了4个事务,Executed_Gtid_Set
执行了4个事务。
在基于 GTID 的复制拓扑中,要想修复从库的 SQL 线程错误,过去的 SQL_SLAVE_SKIP_COUNTER
方式再也不适用。须要经过设置 gtid_next
或 gtid_purged
来完成,固然前提是已经确保主从数据一致,仅仅须要跳过复制错误让复制继续下去。
在从库上执行如下SQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
mysql> stop slave; Query OK, 0 rows affected (0.00 sec) mysql> set gtid_next='f75ae43f-3f5e-11e7-9b98-001c4297532a:20'; 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_next
的方法一次只能跳过一个事务,要批量的跳过事务能够经过设置 gtid_purged
完成。假设下面的场景:
此时从库的 Executed_Gtid_Set
已经包含了主库上 1-13 和 20 的事务,再开启复制会从后面的事务开始执行,就不会出错了。在从库上验证刚才插入的数据:
注意,使用 gtid_next
和 gtid_purged
修复复制错误的前提是跳过那些事务后仍能够确保主备数据一致。若是作不到,就要考虑 pt-table-sync 或者从新导入备份的方式了。
在作备份恢复的时候,有时须要恢复出来的 MySQL 实例能够做为从库连上原来的主库继续复制,这就要求从备份恢复出来的 MySQL 实例拥有和主数据库数据一致的 gtid_executed
值。这也是经过设置 gtid_purged
实现的,下面看下 mysqldump 作备份的例子。
这里使用 --all-databases
选项是由于基于 GTID 的复制会记录所有的事务, 因此要构建一个完整的dump。
1 |
$ mysqldump --all-databases --single-transaction --triggers --routines --events --host=127.0.0.1 --port=3306 --user=root -p000000 > dump.sql |
生成的 dump.sql 文件里包含了设置 gtid_purged
的语句
1 2 3 4 5 6 |
SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN; SET @@SESSION.SQL_LOG_BIN= 0; ... SET @@GLOBAL.GTID_PURGED='f75ae43f-3f5e-11e7-9b98-001c4297532a:1-14:20'; ... SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN; |
在从库恢复数据前须要先经过 reset master
清空 gtid_executed
变量
1 2 |
$ mysql -h127.0.0.1 --user=root -p000000 -e 'reset master' $ mysql -h127.0.0.1 --user=root -p000000<dump.sql |
不然执行设置 GTID_PURGED
的 SQL 时会报下面的错误:
1 |
ERROR 1840 (HY000) at line 24: @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty. |
此时恢复出的 MySQL 实例的 GTID_EXECUTED
和在主库备份时的一致:
因为恢复出 MySQL 实例已经被设置了正确的 GTID_EXECUTED
,下面以 master_auto_postion = 1
的方式 CHANGE MASTER
到原来的主节点便可开始复制。
1 |
mysql> CHANGE MASTER TO MASTER_HOST='192.168.2.210', MASTER_USER='repl', MASTER_PASSWORD='000000', MASTER_AUTO_POSITION = 1; |
若是不但愿备份文件中生成设置 GTID_PURGED
的 SQL,能够给 mysqldump
传入 --set-gtid-purged=OFF
关闭。
enforce_gtid_consistency
强制 GTID 一致性, 启用后如下命令没法再使用。
1 2 |
mysql> create table test2 select * from test1; ERROR 1786 (HY000): Statement violates GTID consistency: CREATE TABLE ... SELECT. |
由于其实是两个独立事件,因此只能将其拆分。先创建表,而后再把数据插入到表中。
1 2 3 4 5 |
mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> create temporary table test2(id int); ERROR 1787 (HY000): Statement violates GTID consistency: CREATE TEMPORARY TABLE and DROP TEMPORARY TABLE can only be executed outside transactional context. These statements are also not allowed in a function or trigger because functions and triggers are also considered to be multi-statement transactions. |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
mysql> CREATE TABLE `test_innodb` (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT); Query OK, 0 rows affected (0.04 sec) mysql> CREATE TABLE `test_myisam` (id INT(11) UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT) ENGINE = `MyISAM`; Query OK, 0 rows affected (0.03 sec) mysql> begin; Query OK, 0 rows affected (0.00 sec) mysql> insert into test_innodb(id) value(1); Query OK, 1 row affected (0.00 sec) mysql> insert into test_myisam(id) value(1); ERROR 1785 (HY000): Statement violates GTID consistency: Updates to non-transactional tables can only be done in either autocommitted statements or single-statement transactions, and never in the same statement as updates to transactional tables. |
http://www.google.com
http://www.ywnds.com/?p=3898
http://dbaplus.cn/news-11-857-1.html
http://www.jianshu.com/p/3675fa74bc72
http://www.cnblogs.com/abobo/p/4242417.html
http://cenalulu.github.io/mysql/mysql-5-6-gtid-basic/