GTID


什么是GTID Replication

從 MySQL 5.6.5 開始新增了一種基於 GTID 的復制方式。通過 GTID 保證了每個在主庫上提交的事務在集群中有一個唯一的ID。這種方式強化了數據庫的主備一致性,故障恢復以及容錯能力。

在原來基於二進制日志的復制中,從庫需要告知主庫要從哪個偏移量進行增量同步,如果指定錯誤會造成數據的遺漏,從而造成數據的不一致。借助GTID,在發生主備切換的情況下,MySQL的其它從庫可以自動在新主庫上找到正確的復制位置,這大大簡化了復雜復制拓撲下集群的維護,也減少了人為設置復制位置發生誤操作的風險。另外,基於GTID的復制可以忽略已經執行過的事務,減少了數據發生不一致的風險。

什么是GTID

GTID (Global Transaction ID) 是對於一個已提交事務的編號,並且是一個全局唯一的編號。 GTID 實際上 是由 UUID+TID 組成的。其中 UUID 是一個 MySQL 實例的唯一標識。TID 代表了該實例上已經提交的事務數量,並且隨着事務提交單調遞增。下面是一個GTID的具體形式:

1
3E11FA47-71CA-11E1-9E33-C80AA9429562:23

一組連續的事務可以用 - 連接的事務序號范圍表示。例如:

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 實時看當前的事務執行數。

GTID的作用

GTID 的使用不單單是用單獨的標識符替換舊的二進制日志文件和位置,它也采用了新的復制協議。舊的協議往往簡單直接,即:首先從服務器上在一個特定的偏移量位置連接到主服務器上一個給定的二進制日志文件,然后主服務器再從給定的連接點開始發送所有的事件。

新協議稍有不同:支持以全局統一事務 ID (GTID) 為基礎的復制。當在主庫上提交事務或者被從庫應用時,可以定位和追蹤每一個事務。GTID 復制是全部以事務為基礎,使得檢查主從一致性變得非常簡單。如果所有主庫上提交的事務也同樣提交到從庫上,一致性就得到了保證。

GTID 相關操作:默認情況下將一個事務記錄進二進制文件時,首先記錄它的 GTID,而且 GTID 和事務相關信息一並要發送給從服務器,由從服務器在本地應用認證,但是絕對不會改變原來的事務 ID 號。因此在 GTID 的架構上就算有了N層架構,復制是N級架構,事務 ID 依然不會改變,有效的保證了數據的完整和安全性。

你可以使用基於語句的或基於行的復制與 GTID ,但是,為了獲得最佳效果,我們建議你使用基於行(ROW)的格式。

GTID功能的具體歸納主要有以下兩點:

  • 根據 GTID 可以知道事務最初是在哪個實例上提交的
  • GTID 的存在方便了 Replication 的 Failover

我們可以看下在 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 復制集群需要使用 MMMMHA 這樣的額外管理工具的一個重要原因。 這個問題在 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';

配置GTID主從復制

環境准備

這里一共使用了二台機器,MySQL 版本都為 5.7.18。

機器名 IP地址 MySQL角色
dev-master-01 192.168.2.210 MySQL 主庫
dev-node-02 192.168.2.212 MySQL 從庫

安裝MySQL

MySQL 安裝比較簡單,在 「MySQL 5.7多源復制實踐」一文中我們也講了,這里就不在重復講了。如果你還不會安裝,可以先參考此文安裝好 MySQL 。

配置MySQL基於GTID的復制

GTID主從復制的配置思路

  • 修改MySQL主配置文件

配置 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。這里沒有給出全部配置,其它請根據實際情況自行配置。

  • 重啟MySQL服務器
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;
  • 查看主庫與從庫的GTID是否開啟
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,否則下次執行事務時會報錯。

  • 查看服務器server_uuid
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 狀態。

在主服務器查看從庫連接的主機信息

測試GTID主從復制

  • 在主庫(dev-master-01)實例創建一些數據。
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);
  • 在從庫(dev-node-02)實例檢查數據是否成功復制。
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復制錯誤

在基於 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 或者重新導入備份的方式了。

GTID與備份恢復

在做備份恢復的時候,有時需要恢復出來的 MySQL 實例可以作為從庫連上原來的主庫繼續復制,這就要求從備份恢復出來的 MySQL 實例擁有和主數據庫數據一致的 gtid_executed 值。這也是通過設置 gtid_purged實現的,下面看下 mysqldump 做備份的例子。

  • 通過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 一致性, 啟用后以下命令無法再使用。

  • create table … select …
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.
  • 同一事務中不能同時更新事務表與非事務表(MyISAM),建議都選擇 Innodb 作為默認的數據庫引擎。
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/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM