1. 程式人生 > >MySQL的GTID複製比傳統複製的優勢

MySQL的GTID複製比傳統複製的優勢

GTID(Global Transaction ID)是MySQL5.6引入的功能,可以在叢集全域性範圍標識事務,用於取代過去通過binlog檔案偏移量定位複製位置的傳統方式。藉助GTID,在發生主備切換的情況下,MySQL的其它Slave可以自動在新主上找到正確的複製位置,這大大簡化了複雜複製拓撲下叢集的維護,也減少了人為設定複製位置發生誤操作的風險。另外,基於GTID的複製可以忽略已經執行過的事務,減少了資料發生不一致的風險。

GTID雖好,要想運用自如還需充分了解其原理與特性,特別要注意與傳統的基於binlog檔案偏移量複製方式不一樣的地方。本文概述了關於GTID的幾個常見問題,希望能對理解和使用基於GTID的複製有所幫助。

GTID長什麼樣

根據官方文件定義,GTID由source_id加transaction_id構成。

GTID = source_id:transaction_id 

上面的source_id指示發起事務的MySQL例項,值為該例項的server_uuid。server_uuid由MySQL在第一次啟動時自動生成並被持久化到auto.cnf檔案裡,transaction_id是MySQL例項上執行的事務序號,從1開始遞增。 例如:

e6954592-8dba-11e6-af0e-fa163e1cf111:1 

一組連續的事務可以用'-'連線的事務序號範圍表示。例如

e6954592-8dba-11e6-af0e-fa163e1cf111:1-5 

更一般的情況是GTID的集合。GTID集合可以包含來自多個source_id的事務,它們之間用逗號分隔;如果來自同一source_id的事務序號有多個範圍區間,各組範圍之間用冒號分隔,例如:

e6954592-8dba-11e6-af0e-fa163e1cf111:1-5:11-18,
e6954592-8dba-11e6-af0e-fa163e1cf3f2:1-27 

即,GTID集合擁有如下的形式定義:

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) 

如何檢視GTID

可以通過MySQL的幾個變數檢視相關的GTID資訊。

  • gtid_executed
    在當前例項上執行過的GTID集合; 實際上包含了所有記錄到binlog中的事務。所以,設定set sql_log_bin=0後執行的事務不會生成binlog 事件,也不會被記錄到gtid_executed中。執行RESET MASTER可以將該變數置空。

  • 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的子集。

  • gtid_next
    會話級變數,指示如何產生下一個GTID。可能的取值如下:

    • AUTOMATIC:
      自動生成下一個GTID,實現上是分配一個當前例項上尚未執行過的序號最小的GTID。
    • ANONYMOUS:
      設定後執行事務不會產生GTID。
    • 顯式指定的GTID: 
      可以指定任意形式合法的GTID值,但不能是當前gtid_executed中的已經包含的GTID,否則,下次執行事務時會報錯。

這些變數可以通過show命令檢視,比如

mysql> show global variables like 'gtid%';
+----------------------+------------------------------------------+
| Variable_name        | Value                                    |
+----------------------+------------------------------------------+
| gtid_deployment_step | OFF                                      |
| gtid_executed        | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-6 |
| gtid_mode            | ON                                       |
| gtid_owned           |                                          |
| gtid_purged          |                                          |
+----------------------+------------------------------------------+
5 rows in set (0.02 sec)

mysql> show  variables like 'gtid_next';
+---------------+-----------+
| Variable_name | Value     |
+---------------+-----------+
| gtid_next     | AUTOMATIC |
+---------------+-----------+
1 row in set (0.00 sec) 

如何產生GTID

GTID的生成受gtid_next控制。 在Master上,gtid_next是預設的AUTOMATIC,即在每次事務提交時自動生成新的GTID。它從當前已執行的GTID集合(即gtid_executed)中,找一個大於0的未使用的最小值作為下個事務GTID。同時在binlog的實際的更新事務事件前面插入一條set gtid_next事件。

以下是一條insert語句生成的binlog記錄

mysql> use `test`
Database changed
mysql> insert into tbx1 values(1);
Query OK, 1 row affected (0.01 sec)
mysql> show binlog events IN 'binlog.000015';
+---------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name      | Pos | Event_type     | Server_id | End_log_pos | Info                                                              |
+---------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
...
| binlog.000015 | 707 | Gtid           |         1 |         755 | SET @@SESSION.GTID_NEXT= 'e10c75be-5c1b-11e6-ab7c-000c296078ae:9' |
| binlog.000015 | 755 | Query          |         1 |         834 | BEGIN                                                             |
| binlog.000015 | 834 | Query          |         1 |         934 | use `test`; insert into tbx1 values(1)                            |
| binlog.000015 | 934 | Xid            |         1 |         965 | COMMIT /* xid=20 */                                               | 

在Slave上回放主庫的binlog時,先執行set gtid_next ...,然後再執行真正的insert語句,確保在主和備上這條insert對應於相同的GTID。

一般情況下,GTID集合是連續的,但使用多執行緒複製(MTS)以及通過gtid_next進行人工干預時會導致gtid空洞。比如下面這樣:

mysql> show master status;
+---------------+----------+--------------+------------------+------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                        |
+---------------+----------+--------------+------------------+------------------------------------------+
| binlog.000015 |      965 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-9 |
+---------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec)

mysql> set gtid_next='e10c75be-5c1b-11e6-ab7c-000c296078ae:12';
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> show master status;
+---------------+----------+--------------+------------------+---------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                           |
+---------------+----------+--------------+------------------+---------------------------------------------+
| binlog.000015 |     1158 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-9:12 |
+---------------+----------+--------------+------------------+---------------------------------------------+
1 row in set (0.00 sec) 

繼續執行事務,MySQL會分配一個最小的未使用GTID,也就是從出現空洞的地方分配GTID,最終會把空洞填上。

mysql> insert into tbx1 values(1);
Query OK, 1 row affected (0.01 sec)

mysql> show master status;
+---------------+----------+--------------+------------------+----------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                            |
+---------------+----------+--------------+------------------+----------------------------------------------+
| binlog.000015 |     1416 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10:12 |
+---------------+----------+--------------+------------------+----------------------------------------------+
1 row in set (0.00 sec) 

這意味著嚴格來說我們即不能假設GTID集合是連續的,也不能假定GTID序號大的事務在GTID序號小的事務之後執行,事務的順序應由事務記錄在binlog中的先後順序決定。

GTID的持久化

GTID相關的資訊儲存在binlog檔案中,為此MySQL5.6新增了下面2個binlog事件。

  • Previous_gtids_log_event 在每個binlog檔案的開頭部分,記錄在該binlog檔案之前已執行的GTID集合。
  • Gtid_log_event 即前面看到的set gtid_next ...,它出現在每個事務的前面,表明下一個事務的gtid。

示例如下:

mysql> show binlog events IN 'binlog.000015';
+---------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| Log_name      | Pos | Event_type     | Server_id | End_log_pos | Info                                                              |
+---------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
| binlog.000015 |   4 | Format_desc    |         1 |         120 | Server ver: 5.6.31-77.0-log, Binlog ver: 4                        |
| binlog.000015 | 120 | Previous_gtids |         1 |         191 | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-6                          |
| binlog.000015 | 191 | Gtid           |         1 |         239 | SET @@SESSION.GTID_NEXT= 'e10c75be-5c1b-11e6-ab7c-000c296078ae:7' |
| binlog.000015 | 239 | Query          |         1 |         318 | BEGIN                                                             |
| binlog.000015 | 318 | Query          |         1 |         418 | use `test`; insert into tbx1 values(1)                            |
| binlog.000015 | 418 | Xid            |         1 |         449 | COMMIT /* xid=13 */                                               |
| binlog.000015 | 449 | Gtid           |         1 |         497 | SET @@SESSION.GTID_NEXT= 'e10c75be-5c1b-11e6-ab7c-000c296078ae:8' |
| binlog.000015 | 497 | Query          |         1 |         576 | BEGIN                                                             |
| binlog.000015 | 576 | Query          |         1 |         676 | use `test`; insert into tbx1 values(1)                            |
| binlog.000015 | 676 | Xid            |         1 |         707 | COMMIT /* xid=17 */                                               |
| binlog.000015 | 707 | Gtid           |         1 |         755 | SET @@SESSION.GTID_NEXT= 'e10c75be-5c1b-11e6-ab7c-000c296078ae:9' |
| binlog.000015 | 755 | Query          |         1 |         834 | BEGIN                                                             |
| binlog.000015 | 834 | Query          |         1 |         934 | use `test`; insert into tbx1 values(1)                            |
| binlog.000015 | 934 | Xid            |         1 |         965 | COMMIT /* xid=20 */                                               |
+---------------+-----+----------------+-----------+-------------+-------------------------------------------------------------------+
14 rows in set (0.00 sec) 

MySQL伺服器啟動時,通過讀binlog檔案,初始化gtid_executed和gtid_purged,使它們的值能和上次MySQL執行時一致。

  • gtid_executed被設定為最新的binlog檔案中Previous_gtids_log_event和所有Gtid_log_event的並集。
  • gtid_purged為最老的binlog檔案中Previous_gtids_log_event。

由於這兩個重要的變數值記錄在binlog中,所以開啟gtid_mode時必須同時在主庫上開啟log_bin在備庫上開啟log_slave_updates。

但是,在MySQL5.7中沒有這個限制。MySQL5.7中,新增加一個系統表mysql.gtid_executed用於持久化已執行的GTID集合。當主庫上沒有開啟log_bin或在備庫上沒有開啟log_slave_updates時,mysql.gtid_executed會跟使用者事務一起每次更新。否則只在binlog日誌發生rotation時更新mysql.gtid_executed。

如何配置基於GTID的複製

MySQL伺服器的my.cnf配置檔案中增加GTID相關的引數

log_bin                        = /mysql/binlog/mysql_bin
log_slave_updates              = true
gtid_mode                      = ON 
enforce_gtid_consistency       = true 
relay_log_info_repository      = TABLE
relay_log_recovery             = ON 

然後在Slave上指定MASTER_AUTO_POSITION = 1執行CHANGE MASTER TO即可。比如:

CHANGE MASTER TO MASTER_HOST='node1',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_AUTO_POSITION=1; 

基於GTID的複製如何工作

在MASTER_AUTO_POSITION = 1的情況下 ,MySQL會使用 COM_BINLOG_DUMP_GTID 協議進行復制。過程如下:

備庫發起複製連線時,將自己的已接受和已執行的gtids的並集(後面稱為slave_gtid_executed)傳送給主庫。即下面的集合:

UNION(@@global.gtid_executed, Retrieved_gtid_set - last_received_GTID) 

主庫將自己的gtid_executed與slave_gtid_executed的差集的binlog傳送給Slave。主庫的binlog dump過程如下:

  1. 檢查slave_gtid_executed是否是主庫gtid_executed的子集,如否那麼主備資料可能不一致,報錯。
  2. 檢查主庫的purged_executed是否是slave_gtid_executed的子集,如否代表缺失備庫需要的binlog,報錯
  3. 從最後一個Binlog開始掃描,獲取檔案頭部的PREVIOUS_GTIDS_LOG_EVENT,如果它是slave_gtid_executed的子集,則這是需要傳送給Slave的第一個binlog檔案,否則繼續向前掃描。
  4. 從第3步找到的binlog檔案的開頭讀取binlog記錄,判斷binlog記錄是否已被包含在slave_gtid_executed中,如果已包含跳過不傳送。

從上面的過程可知,在指定MASTER_AUTO_POSITION = 1時,Master傳送哪些binlog記錄給Slave,取決於Slave的gtid_executed和Retrieved_Gtid_Set以及Master的gtid_executed,和relay_log_info以及master_log_info中儲存的複製位點沒有關係。

如何修復複製錯誤

在基於GTID的複製拓撲中,要想修復Slave的SQL執行緒錯誤,過去的SQL_SLAVE_SKIP_COUNTER方式不再適用。需要通過設定gtid_next或gtid_purged完成,當然前提是已經確保主從資料一致,僅僅需要跳過複製錯誤讓複製繼續下去。比如下面的場景:

在從庫上建立表tb1

mysql> set sql_log_bin=0;
Query OK, 0 rows affected (0.00 sec)

mysql> create table tb1(id int primary key,c1 int);
Query OK, 0 rows affected (1.06 sec)

mysql> set sql_log_bin=1;
Query OK, 0 rows affected (0.00 sec) 

在主庫上建立表tb1

mysql> create table tb1(id int primary key,c1 int);
Query OK, 0 rows affected (1.06 sec) 

由於從庫上這個表已經存在,從庫的複製SQL執行緒出錯停止。

mysql> show slave status\G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.125.134
                  Master_User: sn_repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: binlog.000001
          Read_Master_Log_Pos: 1422
               Relay_Log_File: mysqld-relay-bin.000003
                Relay_Log_Pos: 563
        Relay_Master_Log_File: binlog.000001
             Slave_IO_Running: Yes
            Slave_SQL_Running: No
              Replicate_Do_DB: 
          Replicate_Ignore_DB: 
           Replicate_Do_Table: 
       Replicate_Ignore_Table: 
      Replicate_Wild_Do_Table: 
  Replicate_Wild_Ignore_Table: 
                   Last_Errno: 1050
                   Last_Error: Error 'Table 'tb1' already exists' on query. Default database: 'test'. Query: 'create table tb1(id int primary key,c1 int)'
                 Skip_Counter: 0
          Exec_Master_Log_Pos: 1257
              Relay_Log_Space: 933
              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: NULL
Master_SSL_Verify_Server_Cert: No
                Last_IO_Errno: 0
                Last_IO_Error: 
               Last_SQL_Errno: 1050
               Last_SQL_Error: Error 'Table 'tb1' already exists' on query. Default database: 'test'. Query: 'create table tb1(id int primary key,c1 int)'
  Replicate_Ignore_Server_Ids: 
             Master_Server_Id: 1
                  Master_UUID: e10c75be-5c1b-11e6-ab7c-000c296078ae
             Master_Info_File: mysql.slave_master_info
                    SQL_Delay: 0
          SQL_Remaining_Delay: NULL
      Slave_SQL_Running_State: 
           Master_Retry_Count: 86400
                  Master_Bind: 
      Last_IO_Error_Timestamp: 
     Last_SQL_Error_Timestamp: 161203 15:14:17
               Master_SSL_Crl: 
           Master_SSL_Crlpath: 
           Retrieved_Gtid_Set: e10c75be-5c1b-11e6-ab7c-000c296078ae:5-6
            Executed_Gtid_Set: e10c75be-5c1b-11e6-ab7c-000c296078ae:1-5
                Auto_Position: 1
1 row in set (0.00 sec) 

從上面的輸出可以知道,從庫已經執行過的事務是'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完成。假設下面的場景:

主庫上已執行的事務

mysql> show master status;
+---------------+----------+--------------+------------------+-------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+---------------+----------+--------------+------------------+-------------------------------------------+
| binlog.000001 |     2364 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10 |
+---------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec) 

從庫上已執行的事務

mysql> show master status;
+---------------+----------+--------------+------------------+------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                        |
+---------------+----------+--------------+------------------+------------------------------------------+
| binlog.000001 |     1478 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-6 |
+---------------+----------+--------------+------------------+------------------------------------------+
1 row in set (0.00 sec) 

假設經過修復從庫已經和主庫的資料一致了,但由於複製錯誤Slave的SQL執行緒依然處於停止狀態。現在可以通過把從庫的gtid_purged設定為和主庫的gtid_executed一樣跳過不一致的GTID使複製繼續下去,步驟如下。

在從庫上執行

mysql> reset master;
Query OK, 0 rows affected (0.01 sec)

mysql> set GLOBAL gtid_purged='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10';
Query OK, 0 rows affected (0.03 sec)

mysql> show master status;
+---------------+----------+--------------+------------------+-------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+---------------+----------+--------------+------------------+-------------------------------------------+
| binlog.000002 |      191 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10 |
+---------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec) 

此時從庫的Executed_Gtid_Set已經包含了主庫上'1-10'的事務,再開啟複製會從後面的事務開始執行,就不會出錯了。

mysql> start slave;
Query OK, 0 rows affected (0.01 sec) 

使用gtid_next和gtid_purged修復複製錯誤的前提是,跳過那些事務後仍可以確保主備資料一致。如果做不到,就要考慮pt-table-sync或者拉備份的方式了。

GTID與備份恢復

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

通過mysqldump進行備份

通過mysqldump做一個全量備份

[[email protected] ~]# mysqldump --all-databases --single-transaction --routines --events --host=127.0.0.1 --port=3306 --user=root > dump.sql 

生成的dump.sql檔案裡包含了設定gtid_purged的語句

dump.sql:

SET @MYSQLDUMP_TEMP_LOG_BIN = @@SESSION.SQL_LOG_BIN;
SET @@SESSION.SQL_LOG_BIN= 0;
...
SET @@GLOBAL.GTID_PURGED='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10';
...
SET @@SESSION.SQL_LOG_BIN = @MYSQLDUMP_TEMP_LOG_BIN; 

恢復資料前需要先通過reset master清空gtid_executed變數

[[email protected] ~]# mysql -h127.1 -e 'reset master'
[[email protected] ~]# mysql -h127.1 <dump.sql 

否則執行設定GTID_PURGED的SQL時會報下面的錯誤

ERROR 1840 (HY000) at line 24: @@GLOBAL.GTID_PURGED can only be set when @@GLOBAL.GTID_EXECUTED is empty. 

此時恢復出的MySQL例項的GTID_EXECUTED和備份時點一致

mysql> show master status;
+---------------+----------+--------------+------------------+-------------------------------------------+
| File          | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+---------------+----------+--------------+------------------+-------------------------------------------+
| binlog.000002 |      191 |              |                  | e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10 |
+---------------+----------+--------------+------------------+-------------------------------------------+
1 row in set (0.00 sec) 

由於恢復出的MySQL例項已經被設定的正確的GTID_EXECUTED,以master_auto_postion = 1的方式CHANGE MASTER到原來的主節點即可開始複製。

CHANGE MASTER TO MASTER_HOST='node1', MASTER_USER='repl', MASTER_PASSWORD='repl', MASTER_AUTO_POSITION = 1 

如果不希望備份檔案中生成設定GTID_PURGED的SQL,可以給mysqldump傳入--set-gtid-purged=OFF關閉。

通過Xtrabackup進行備份

相比mysqldump,Xtrabackup是效率更高並且被廣泛使用的備份方式。使用Xtrabackup進行備份的舉例如下。

通過Xtrabackup創一個全量備份(可以在Slave上建立備份,以避免對主庫的效能衝擊)

innobackupex --defaults-file=/etc/my.cnf --host=127.1 --user=root --password=mysql --no-timestamp --safe-slave-backup --slave-info /mysql/bak 

應用日誌

innobackupex --apply-log /mysql/bak 

檢視備份目錄中的xtrabackup_binlog_info檔案可以找到備份時已經執行過的gtids

[[email protected] ~]# cat /mysql/bak/xtrabackup_binlog_info
mysql_bin.000001    191 e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10 

由於備份時添加了”--slave-info”選項並且從Slave節點拉取的備份,所以會生成xtrabackup_slave_info檔案,也可以從這個檔案裡查詢建立複製的SQL語句。

[[email protected] ~]# cat /mysql/bak/xtrabackup_slave_info
SET GLOBAL gtid_purged='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10';
CHANGE MASTER TO MASTER_AUTO_POSITION=1 

將備份檔案傳送到新的節點node3的/mysql/bak目錄並恢復(如果直接把備份傳輸到資料目錄了,這一步可以省略)。

[[email protected] ~]# innobackupex --defaults-file=/etc/my.cnf --copy-back /mysql/bak 

啟動MySQL。

[[email protected] ~]# mysqld --defaults-file=/home/mysql/etc/my.cnf --skip-slave-start & 

如果是從Slave拉的備份,一定不能直接開啟Slave複製,這時的gtid_executed是錯誤的。需要手動設定gtid_purged後再start slave

reset master;
SET GLOBAL gtid_purged='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10';
CHANGE MASTER TO MASTER_HOST='node1',MASTER_USER='repl',MASTER_PASSWORD='repl',MASTER_AUTO_POSITION=1;
start slave; 

GTID與MHA

MHA是被廣泛使用MySQL HA元件,MHA 0.56以後支援基於GTID的複製。 MHA在failover時會自動判斷是否是GTID based failover,需要滿足下面3個條件即為GTID based failover

  • 所有節點gtid_mode=1
  • 所有節點Executed_Gtid_Set不為空
  • 至少一個節點Auto_Position=1

和之前的基於binlog檔案位置的複製相比,基於GTID複製下,MHA在故障切換時的變化主要如下:

  • 基於binlog檔案位置的複製

    • 在Master宕機後會嘗試從Master上拷貝binlog日誌進行補償   
    • 如果候選Master不擁有最新的relay log,會從擁有最新relay log的Slave上生成差異的binlog傳送到候選Master並實施補償  
    • 新Master的日誌補償完成後,同樣採用應用差異binlog的方式將其它Slave和新Master同步後再change master到新Master  
  • 基於GTID的複製  

    • 如果候選Master不擁有最新的relay log,讓候選Master連上擁有最新relay log的Salve進行補償。  
    • 嘗試從binlog server上拉取缺失的binlog並應用
    • 新Master的資料同步到最新後,讓其它的Slave連上新Master並等待資料完成同步。並且可以給masterha_master_switch傳入--wait_until_gtid_in_sync=1引數使其不等其它Slave完成資料同步,以加快切換速度。

在GTID模式下MHA不會嘗試從舊Master上拷貝binlog日誌進行補償,所以在MySQL程序crash而OS仍然健康的情況下,應儘量不要做主備切換而是原地重啟MySQL,除非有其它能確保切換後不丟資料的措施。

在GTID模式下MHA支援在複製拓撲中增加一個或多個binlog server起到日誌補償的作用,非GTID模式下即使配置了binlog server也會被MHA忽略。

日誌補償可以說是MHA中最複雜也最精華的部分,有了GTID後故障切換變得更簡單了,不再需要原本複雜的binlog日誌解析和補償。所以Oracle官方推出了只支援GTID複製的切換工具mysqlfailover,在GTID的幫助下,我們有更多靠譜的HA工具可以選擇。

GTID與crash safe slave

crash safe slave是MySQL 5.6提供的功能,意思是說在slave crash後,把slave重新拉起來可以繼續從Master進行復制,不會出現複製錯誤也不會出現資料不一致。

基於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導致的事務空洞。

基於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 

但是其中關於GTID的記載中存在筆誤,將relay_log_recovery=1寫成了relay_log_recovery=0 (#83711)。同時也沒有提到必須設定"雙1",但是"雙1"是必要的,否則crash的Slave重啟後,可能會重複應用binlog event也可能會遺漏應用binlog event(#70659)。其中遺漏應用binlog event的情況更可怕,因為Slave在不觸發SQL錯誤的情況下就默默的和Master不一致了。

設定"雙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能力有很大關係,僅供參考。

如何在非"雙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回放具有冪等性,但這也意味著如果真的出現了主備資料不一致也會被它忽略。

MTS下特有的問題

在同時使用MTS(slave_parallel_workers > 1)時,即使按上面crash safe slave的要求設定了基於GTID的複製,Slave crash後再重啟還是會導致複製中斷。

通過強制殺掉MySQL所在虛機的方式模擬Slave宕機,然後再啟動MySQL,mysql日誌中有如下錯誤訊息:

---------------------------------
2016-10-26 21:00:23 2699 [Warning] Neither --relay-log nor --relay-log-index were used; so replication may break when this MySQL server acts as a slave and has his hostname changed!! Please use '--relay-log=mysql-relay-bin' to avoid this problem.
2016-10-26 21:00:24 2699 [Note] Slave: MTS group recovery relay log info based on Worker-Id 1, group_relay_log_name ./mysql-relay-bin.000011, group_relay_log_pos 2017523 group_master_log_name binlog.000007, group_master_log_pos 2017363
2016-10-26 21:00:24 2699 [ERROR] Error looking for file after ./mysql-relay-bin.000012.
2016-10-26 21:00:24 2699 [ERROR] Failed to initialize the master info structure
2016-10-26 21:00:24 2699 [Note] Check error log for additional messages. You will not be able to start replication until the issue is resolved and the server restarted.
2016-10-26 21:00:24 2699 [Note] Event Scheduler: Loaded 0 events
2016-10-26 21:00:24 2699 [Note] mysqld: ready for connections.
Version: '5.6.31-77.0-log'  socket: '/data/mysql/mysql.sock'  port: 3306  Percona Server (GPL), Release 77.0, Revision 5c1061c
--------------------------------- 

啟動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。

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進行恢復。

相關推薦

MySQL的GTID複製傳統複製優勢

GTID(Global Transaction ID)是MySQL5.6引入的功能,可以在叢集全域性範圍標識事務,用於取代過去通過binlog檔案偏移量定位複製位置的傳統方式。藉助GTID,在發生主備切換的情況下,MySQL的其它Slave可以自動在新主上找到正確的複製位

Mysql傳統複製空庫搭建過程中reset slave以及reset slave all對複製的影響

Mysql傳統複製空庫搭建過程中reset slave以及reset slave all對複製的影響 主庫資訊 從庫資訊 操作過程 主庫上操作 從庫上操作 主庫上檢視 解決方法 思路探討

傳統複製常見錯誤及填坑方法

傳統複製常見錯誤及填坑方法 環境 1、表不存在導致插入更新失敗 1.1、模擬複製錯誤產生 1.1.1、生成測試表sbtest.test 1.1.2、在備庫上檢視是否同步test表 1.1.3、主庫上往test表中

MySQL傳統複製與GTID複製的原理闡述

MySQL複製 MySQL非同步複製架構中傳統複製的原理闡述 MySQL非同步複製架構中GTID複製的原理闡述 一、GTID的概述: 二、GTID的組成部分: 三、GTID如何產生 四、GTID相關的變數 五、GTID比

mysql主從(傳統複製模式)

環境搭建: 搭建兩臺MySQL伺服器,一臺作為主伺服器,一臺作為從伺服器,主伺服器進行寫操作,從伺服器進行讀操作。 主從配置需要注意的點 主從伺服器作業系統版本和位數一致; Master 和 Slave 資料庫的版本要一致; Master 和 Slave 資料庫中的資料要一致; Ma

mysql傳統複製(postion)與GTID原理解析

大家都知道,mysql的複製其實都是基於日誌檔案的,不管什麼複製,都離不開日誌檔案的同步,那它是怎麼同步的,靠什麼同步的,從節點怎麼知道要從哪塊進行同步  以傳統的主從複製為例: 1.同步怎樣進行的? master使用者寫入資料,生成event記到binary log中.

MySQL傳統複製和GTID複製參考

1.mysqldump或者xtrabackup匯出資料庫scp到需要需要的備庫上,然後倒入到資料庫中,本例子使用mysqldump,xtrabacup請參考另外部落格http://blog.51cto.com/1937519/2283779 mysqldump -uroot -p --single_tran

MySQL傳統複製與GTID複製原理及操作詳解

mysql複製在業界裡有叫:mysql同步,ab複製等。專業名稱就是叫:複製 複製是單向的,只能從master複製到slave上,延時基本上是毫秒級別的。 一組複製結構中可以有多個slave,對於master一般場景推薦只有一個。 master使用者寫入資料,生成event記到binary log中 sla

傳統複製線上切換到GTID模式

傳統複製切換到GTID模式 5.7.6以後引數gtid_mode可以動態修改 GTID_MODE: OFF 徹底關閉GTID,如果關閉狀態的備庫接受到帶GTID的事務,則複製中斷 OFF_PERMISSIVE 可以認為是關閉GTID

一種簡單的對象賦值方法,定義實例後以{}賦值,傳統方法更簡潔

method ott static set num arr nbsp st2 () public class Rectangle { public Point TopLeft { get; set; } public Point Botto

傳統事務快10倍?一張圖讀懂阿裏雲全局事務服務GTS

架構 分布式 摘要: 近日,阿裏雲全局事務服務GTS正式上線,為微服務架構中的分布式事務提供一站式解決方案。GTS有哪些功能,相比傳統事務的優勢在哪呢?我們通過一張圖讀懂GTS。近日,阿裏雲全局事務服務GTS正式上線,為微服務架構中的分布式事務提供一站式解決方案。GTS的原理是將分布式事務與具體業務分

C 深複製和淺複製

分享一下我老師大神的人工智慧教程!零基礎,通俗易懂!http://blog.csdn.net/jiangjunshow 也歡迎大家轉載本篇文章。分享知識,造福人民,實現我們中華民族偉大復興!        

複製和深複製

Python中,複製有以下幾種方法 1.賦值複製 >>>a = [1, 2, 3] >>>b = a >>>b [1, 2, 3] 此時的複製,b僅僅是指向了a所在的記憶體空間,在記憶體中,並沒有申請一處新的空間來儲存列表b,a和b在

複製與深複製

淺複製 舉個複製程式碼的例子: function clone(p,s) { var s = s || {}; for (var prop in p) { s[prop] = p[prop]; } return s; } var a = {name: ’Che

MySQL主從複製,並行複製,半同步複製和組複製

主從複製 主從複製過程存在三個執行緒,Master端的I/O執行緒,Slave的I/O執行緒與SQL執行緒。Master端需要開啟binlog日誌,Slave端需要開啟relaylog。 1、Slave端的I/O讀取master.info檔案,獲取binlog檔名和位置點,然後向Mast

深度複製與淺複製

淺複製 淺複製會連引用型別一起復制 public class Content { public int val; } public class Cloner { public Content MyContent = new Content(); public Cloner(

Mysql 半同步複製和非同步複製

mysql 半同步複製和非同步複製 -- 在主庫中安裝半同步外掛,開啟半同步複製功能 install plugin rpl_semi_sync_master soname 'semisync_master.so'; set global rpl_semi_sync_master_enab

MySQL 主從複製 主主複製(3)

一.主從複製 1.描述 mysql主從複製實現的原理就是binlog日誌,主節點負責資料庫寫操作,從節點負責讀操作,從節點上不需要使用事務,能夠大大提高資料庫的效能. 準備三臺節點: 192.168.100.193  master 192.168.100.194&nbs

docker 複製映象和複製容器

複製映象和複製容器都是通過儲存為新映象而進行的。 具體為: 儲存映象 docker save ID > xxx.tar docker load < xxx.tar 儲存容器 docker export ID >xxx.tar docker import xxx.tar cont

SQL Server複製入門(一)----複製簡介 (轉載)

簡介SQL Server中的複製(Replication)是SQL Server高可用性的核心功能之一,在我看來,複製指的並不僅僅是一項技術,而是一些列技術的集合,包括從儲存轉發資料到同步資料到維護資料一致性。使用複製功能不僅僅需要你對業務的熟悉,還需要對複製功能的整體有一個全面的瞭解,本系列文章旨在對SQL