MySQL 5.7 基於 GTID 的主從複製實踐

MySQL 5.7 基於 GTID 的主從複製實踐

 

Posted by Mike on 2017-07-03

在 「MySQL 5.7多源複製實踐」 一文中咱們講解了 MySQL 5.7 新特性多源複製的實現方法。今天咱們來說講 MySQL 5.7 的另外一個新特性基於 GTID 的主從複製實現。html

什麼是GTID Replication

從 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功能的具體概括主要有如下兩點:

  • 根據 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_filemaster_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_purgedgtid_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_RunningSlave_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_nextgtid_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_nextgtid_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/

相關文章
相關標籤/搜索