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,否則,下次執行事務時會報錯。
-
AUTOMATIC:
這些變數可以通過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過程如下:
- 檢查slave_gtid_executed是否是主庫gtid_executed的子集,如否那麼主備資料可能不一致,報錯。
- 檢查主庫的purged_executed是否是slave_gtid_executed的子集,如否代表缺失備庫需要的binlog,報錯
- 從最後一個Binlog開始掃描,獲取檔案頭部的PREVIOUS_GTIDS_LOG_EVENT,如果它是slave_gtid_executed的子集,則這是需要傳送給Slave的第一個binlog檔案,否則繼續向前掃描。
- 從第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再開啟複製,步驟如下。
-
啟動mysql,但不開啟複製
mysqld --skip-slave-start
-
在Slave上修改為基於binlog檔案位置的複製
change master to MASTER_AUTO_POSITION = 0
-
啟動slave IO執行緒
start slave io_thread
這裡不能啟動SQL執行緒,如果接受到的GTID已經在Slave的gtid_executed裡了,會被Slave skip掉。
-
檢查binlog傳輸的開始位置(即Retrieved_Gtid_Set的值)
show slave status\G
假設輸出的Retrieved_Gtid_Set值為e10c75be-5c1b-11e6-ab7c-000c296078ae:7-10
-
在Master上檢查gtid_executed
show master status
假設輸出的Executed_Gtid_Set值為e10c75be-5c1b-11e6-ab7c-000c296078ae:1-10
-
在Slave上設定gitd_purged為binlog傳輸位置的前面的GTID的集合
reset master; set global gitd_purged='e10c75be-5c1b-11e6-ab7c-000c296078ae:1-6';
-
修改回auto position的複製
change master to MASTER_AUTO_POSITION = 1
-
啟動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