MySQL -- 主從切換
- 虛線箭頭為 主從關係 ,
A
和A'
互為主從,B
、C
、D
指向主庫A
- 一主多從的設定,一般用於 讀寫分離 ,主庫負責 所有的寫入 和 一部分讀 ,其它讀請求由從庫分擔
主庫故障切換
A'
成為新的主庫, B
、 C
、 D
指向主庫 A'
基於位點的切換
B
原先是 A
的從庫,本地記錄的也是 A
的位點,但 相同的日誌 , A
的位點與 A'
的位點是 不同 的
-- 節點B設定為節點A'的從庫 CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password MASTER_LOG_FILE=$master_log_name MASTER_LOG_POS=$master_log_pos
尋找位點
- 很難精確,只能大概獲取一個位置
- 由於在切換過程中 不能丟資料 ,在尋找位點的時候,總是找一個 稍微往前的位點 ,跳過那些已經在
B
執行過的事務
常規步驟
- 等待新主庫
A'
將所有relaylog
全部執行完 - 在
A'
上執行SHOW MASTER STATUS
,得到A'
上最新的File
和Position
- 獲取原主庫
A
發生故障的時刻T
- 使用
mysqlbinlog
解析A'
的File
,得到時刻T
的位點
-- end_log_pos=123,表示在時刻T,A'寫入新binlog的位置,作為B的CHANGE MASTER TO命令的MASTER_LOG_POS引數 $ mysqlbinlog /var/lib/mysql/slave-bin.000009 --start-datetime='2019-02-26 17:44:00' --stop-datetime='2019-02-26 17:45:00' | grep end_log_pos #190226 17:42:01 server id 2end_log_pos 123 CRC32 0x5b852e9b Start: binlog v 4, server v 5.7.25-log created 190226 17:42:01 at startup
位點不精確
- 假設在時刻
T
,原主庫A
已經執行完成了一個INSERT
語句,插入一行記錄R
- 並且已經將
binlog
傳給A'
和B,然後原主庫A
掉電
- 並且已經將
- 在
B
上,由於已經同步了binlog
,R
這一行是已經存在的 - 在新主庫
A'
上,R
這一行也是存在的,日誌寫在了123
這個位置之後 - 在
B
上執行CHANGE MASTER TO
,執行A'
的File
檔案的123
位置- 就會把插入
R
這一行資料的binlog
又同步到B
去執行 -
B
的同步執行緒會報 重複主鍵 錯誤,然後停止同步
- 就會把插入
跳過錯誤
方式1:主動跳過一個事務,需要 持續觀察 ,每次碰到這些錯誤,就執行一次跳過命令
SET GLOBAL sql_slave_skip_counter=1; START SLAVE;
方式1:設定 slave_skip_errors=1032,1062
, 1032
錯誤是刪除資料時 找不到行 , 1062
錯誤是插入資料時報 唯一鍵衝突
在 主從切換過程 中,直接跳過 1032
和 1062
是 無損 的,等主從間的同步關係建立完成後,需要將 slave_skip_errors
恢復為 OFF
mysql> SHOW VARIABLES LIKE '%slave_skip_errors%'; +-------------------+-------+ | Variable_name| Value | +-------------------+-------+ | slave_skip_errors | OFF| +-------------------+-------+
基於GTID的切換
- GTID: Global Transaction Identifier, 全域性事務ID
- 在事務 提交 時生成,是事務的唯一標識,組成
GTID = server_uuid:gno
-
server_uuid
是例項第一次 啟動 時自動生成的,是一個 全域性唯一 的值 -
gno
是一個整數,初始值為1
,每次 提交事務 時分配,+1
-
- 官方定義:
GTID = source_id:transaction_id
-
source_id
即server_uuid
-
transaction_id
容易造成誤解-
transaction_id
一般指事務ID,是在事務 執行過程 中分配的,即使事務 回滾 了,事務ID也會 遞增 - 而
gno
只有在事務 提交 時才會分配,因此GTID
往往是 連續 的
-
-
- 開啟
GTID
模式,新增啟動引數gtid_mode=ON
和enforce_gtid_consistency=ON
- 在
GTID
模式下,每個事務都會跟一個GTID
一一對應,生成GTID
的方式由引數gtid_next
(Session)控制 - 每個MySQL例項都維護了一個
GTID
集合,用於表示: 例項執行過的所有事務
gtid_next
-
gtid_next=AUTOMATIC
,MySQL會將server_uuid:gno
分配給該事務- 記錄
binlog
時,會先記錄一行SET @@SESSION.GTID_NEXT=server_uuid:gno
,將該GTID
加入到本例項的GTID
集合
- 記錄
-
gtid_next=UUID:NUMBER
,通過SET @@SESSION.GTID_NEXT=current_gtid
執行- 如果
current_gtid
已經 存在 於例項的GTID
集合中,那麼接下來執行的這個事務會直接被系統 忽略 - 如果
current_gtid
並 沒有存在 於例項的GTID
集合中,那麼接下來執行的這個事務會被分配為current_gtid
-
current_gtid
只能給 一個事務 使用,如果執行下一個事務,需要把gtid_next
設定成另一個GTID
或者AUTOMATIC
- 如果
-- gtid_next=AUTOMATIC mysql> SHOW BINLOG EVENTS IN 'master-bin.000003'; +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | Log_name| Pos | Event_type| Server_id | End_log_pos | Info| +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+ | master-bin.000003 |4 | Format_desc|1 |123 | Server ver: 5.7.25-log, Binlog ver: 4| | master-bin.000003 | 123 | Previous_gtids |1 |194 | b8502fe3-3b4a-11e9-9562-0242ac110002:1-5| | master-bin.000003 | 194 | Gtid|1 |259 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:6'| | master-bin.000003 | 259 | Query|1 |484 | GRANT REPLICATION SLAVE ON *.* TO 'replication'@'%' IDENTIFIED WITH 'mysql_native_password' AS '*6BB4837EB74329105EE4568DDA7DC67ED2CA2AD9' | | master-bin.000003 | 484 | Gtid|1 |549 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:7'| | master-bin.000003 | 549 | Query|1 |643 | CREATE DATABASE test| +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------------------------------------------------------------------------------+
表初始化
CREATE TABLE `t` ( `id` INT(11) NOT NULL, `c` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB; INSERT INTO t VALUES (1,1);
對應的binlog
mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+-------------------------------------------+ | File| Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set| +-------------------+----------+--------------+------------------+-------------------------------------------+ | master-bin.000004 |877 ||| b8502fe3-3b4a-11e9-9562-0242ac110002:1-12 | +-------------------+----------+--------------+------------------+-------------------------------------------+ mysql> SHOW BINLOG EVENTS IN 'master-bin.000004'; +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+ | Log_name| Pos | Event_type| Server_id | End_log_pos | Info| +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+ | master-bin.000004 |4 | Format_desc|1 |123 | Server ver: 5.7.25-log, Binlog ver: 4| | master-bin.000004 | 123 | Previous_gtids |1 |194 | b8502fe3-3b4a-11e9-9562-0242ac110002:1-9| | master-bin.000004 | 194 | Gtid|1 |259 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:10' | | master-bin.000004 | 259 | Query|1 |373 | use `test`; DROP TABLE `t` /* generated by server */| | master-bin.000004 | 373 | Gtid|1 |438 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:11' | | master-bin.000004 | 438 | Query|1 |620 | use `test`; CREATE TABLE `t` ( `id` INT(11) NOT NULL, `c` INT(11) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB | | master-bin.000004 | 620 | Gtid|1 |685 | SET @@SESSION.GTID_NEXT= 'b8502fe3-3b4a-11e9-9562-0242ac110002:12' | | master-bin.000004 | 685 | Query|1 |757 | BEGIN| | master-bin.000004 | 757 | Table_map|1 |802 | table_id: 109 (test.t)| | master-bin.000004 | 802 | Write_rows|1 |846 | table_id: 109 flags: STMT_END_F| | master-bin.000004 | 846 | Xid|1 |877 | COMMIT /* xid=27 */| +-------------------+-----+----------------+-----------+-------------+--------------------------------------------------------------------+
- 事務
BEGIN
之前有一條SET @@SESSION.GTID_NEXT
- 如果例項X有從庫Z,那麼將
CREATE TABLE
和INSERT
語句的binlog
同步到從庫Z執行- 執行事務之前,會先執行兩個
SET
命令,這樣兩個GTID
就會被加入到從庫Z的GTID
集合
- 執行事務之前,會先執行兩個
主鍵衝突
- 如果例項X是例項Y的從庫,之前例項Y上執行
INSERT INTO t VALUES (1,1)
- 對應的
GTID
為aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12
- 例項X需要同步該事務過來執行,會報 主鍵衝突 的錯誤,例項X的同步執行緒停止,處理方法如下
- 對應的
-- 例項X提交一個空事務,將該GTID加到例項X的GTID集合中 SET gtid_next='aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12'; BEGIN; COMMIT; -- 例項X的Executed_Gtid_Set已經包含了aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12 mysql> SHOW MASTER STATUS; +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ | File| Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set| +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ | master-bin.000004 |1087 ||| aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12,b8502fe3-3b4a-11e9-9562-0242ac110002:1-12| +-------------------+----------+--------------+------------------+------------------------------------------------------------------------------------+ -- 恢復GTID的預設分配行為 SET gtid_next=AUTOMATIC; -- 例項X還是會繼續執行例項Y傳過來的事務 -- 但由於例項X的GTID集合已經包含了aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee:12,因此例項X會直接跳過該事務 START SLAVE;
主從切換
CHANGE MASTER TO MASTER_HOST=$host_name MASTER_PORT=$port MASTER_USER=$user_name MASTER_PASSWORD=$password master_auto_position=1
-
master_auto_position=1
:主從關係使用的是GTID
協議,不再需要指定MASTER_LOG_FILE
和MASTER_LOG_POS
- 例項
A'
的GTID
集合記為set_a
,例項B
的GTID
集合記為set_b
- 例項
B
執行START SLAVE
,取binlog
的邏輯如下
START SLAVE
- 例項
B
指定新主庫A'
,基於 主從協議 建立連線 - 例項
B
把set_b
傳送給A'
- 例項
A'
計算出seb_a
和set_b
的GTID
差集(存在於set_a
,但不存在於set_b
的GTID
集合)- 判斷例項
A'
本地是否包含了 差集需要的所有binlog
事務 - 如果 沒有全部包含 ,說明例項
A'
已經把例項B
所需要的binlog
刪除掉了,直接返回錯誤 - 如果 全部包含 ,例項
A'
從自己的binlog
檔案裡面,找到第1個不在set_b
的事務,傳送給例項B
- 然後從該事務開始,往後讀檔案,按順序讀取
binlog
,發給例項B
去執行
- 然後從該事務開始,往後讀檔案,按順序讀取
- 判斷例項
位點 VS GTID
- 基於
GTID
的主從關係裡面,系統認為只要 建立了主從關係 ,就必須保證 主庫發給從庫的日誌是完整 的 - 如果例項
B
需要的日誌已經不存在了,那麼例項A'
就拒絕將日誌傳送給例項B
- 基於 位點 的協議,是由 從庫決定 的,從庫指定哪個位點,主庫就傳送什麼位點,不做 日誌完整性 的判斷
- 基於
GTID
的協議,主從切換 不再需要找位點 ,而找位點的工作在例項A'
內部 自動完成
日誌格式
- 切換前
- 例項
B
的GTID
集合:server_uuid_of_A:1-N
- 例項
- 新主庫
A'
自己生成的binlog
對應的GTID
集合:server_uuid_of_A':1-M
- 切換後
- 例項
B
的GTID
集合:server_uuid_of_A:1-N,server_uuid_of_A':1-M
- 例項
參考資料
《MySQL實戰45講》
轉載請註明出處:http://zhongmingmao.me/2019/02/27/mysql-master-slave-switch/
訪問原文「MySQL -- 主從切換」獲取最佳閱讀體驗並參與討論