基於GTID搭建主從MySQL

基於gtid搭建主從MySQL

一、GTID的使用

想讓主從之間使用gtid的方式同步數據,需要我們在配置文件中開啟mysql對gtid相關的配置信息

找到my.cnf ,在mysqld模塊中加入如下的配置。(主庫從庫都這樣)

# on表示開啟,OFF表示關閉
gtid-mode = ON
# 下面的兩個變量必須開啟,否則MySQL拒絕啟動
log-slave-updates = 1
log-bin = MySQL-bin
# 必須開啟,否則MySQL不啟動,因為MySQL的SQL和gtid
# 許多MySQL的SQL和GTID是不兼容的。比如開啟ROW 格式時,CREATE TABLE … SELECT
# 在binlog中會形成2個不同的事務,GTID無法唯一。
# 另外在事務中更新MyISAM表也是不允許的。
enforce_gtid_consistency = 1  #
log-bin-index = MySQL-bin.index

對主庫來說依然需要創建一個用於同步數據的賬號

mysql> grant replication slave on *.* to MySQLsync@"10.123.123.213" identified by "MySQLsync123";
Query OK, 0 rows affected, 1 warning (0.00 sec)

對從庫中執行如下命令:即可完成主從同步

CHANGE MASTER TO
    MASTER_HOST='10.123.123.123',
    MASTER_USER='MySQLsync',
    MASTER_PASSWORD='MySQLsync123',
    MASTER_PORT=8882,
    MASTER_AUTO_POSITION = 1;

這種自動找點對方式,相對於之前使用bin-log+position找點就顯得及其方便了。

省去了手動查看master執行到那個binlog,以及binlog的position。

做了如上的配置後,還是可以繼續使用fileName和position找點,但是不推薦這樣做了,如果非要這樣做,設置MASTER-AUTO-POSITION=0


假設我們想在現有的主從集群上新加一個庫從,如果這個主庫已經運行很久了,binlog肯定曾經被purge過,所以如果主庫中原來的數據不重要,不介意主從數據不一致,在從庫中執行:

reset master; 

# 缺失的GTID集合設置為purged ,執行這個命令請確保從庫:@@global.gtid_executed為空
set global gtid_purged = 『主庫中曾經purge過的gtid記錄』

# 然後通過MASTER_AUTO_POSITION=1完成自動找點

如果介意主從數據強一致可以考慮使用可以熱備份的工具從主庫拷貝數據到從庫,完成數據的同步再在從庫執行如上set global gtid_purged = ‘xxx’

熱備份工具如:xtrabackup , 它可以拷貝物理文件和redolog來支持熱備份


二、GTID的簡介

GTID (global transcation identifier)

GTID是MySQL5.6版本中添加進來的新特性 ,通過GTID取代同步模式1中手動查找fileName和position, 實現了自動找點

比如一條update有語句進入MySQL之後經歷如下過程:

1. 寫undolog 
2. 寫redolog(prepare)
3. 寫binlog 
4. 寫redolog(commit)

MySQL5.6之後加入了GTID新特性後,update語句經歷如下過程

1. 寫undolog # 回滾
2. 寫redolog(prepare)# 保證提交的不會丟失
3. 寫一個特殊的Binlog Event,類型為GTID_Event,指定下一個事務的GTID 
4. 寫binlog # 主從同步事物使用
5. 寫redolog(commit)

這個GTID的作用就是用於去唯一的標示一個事物的id。

主從之間,之所以能完成數據的同步,是因為從庫會dump主庫記錄的binlog, 主庫將自己成功執行過的事物都寫在binlog用於給從庫回放。當我們在mysql的配置文件中將上面的配置都打開時,主庫在記錄binlog的同時在binlog中會混雜着gtid的信息,這個gtid和當前事物唯一對應。

當從庫向主庫發送同步數據當請求時:bin-log和gtid都會傳送到slave端,slave在回放日誌同步數據時,同樣會使用gtid寫bin-log,這樣主庫和從庫之間的數據,就通過GTID強制性的關聯並且保持同步了。

下圖中淺色的背景是一條完整的binlog,從binlog的記錄中可以發現,gtid也寫在binlog中,當前事物提交commit後,還會為下一個事物生成一個gtid待使用。

三、GTID的構成

gtid由兩部分組成,server_uuid:transaction_id

  • server_uuid是一個只讀變量,保存在 MySQL/var/auto.cnf, 也可以通過命令查看show global variables like 'server_uuid' ;

    第一種查看方式:

cat auto.cnf

第二種查看方式:

  • transaction_id是事物id, 一般他會遞增。

四、查看GTID的執行情況

在主庫中查看gtid的執行情況:

如果不出意外的話,從庫中的gtid執行情況和主庫是一樣的。

這是如果我們往主庫插入一條信息,主庫的gtid_executed則會變成:

f6b65f0b-a251-11ea-8921-b8599f2ef058:1-5

同樣去從庫中查看,從庫的gtid信息和主庫是一樣的。

4.1 gtid_executed

它既可以是一個global類型的變量,也可以是一個session級別的變量。是只讀的,記錄著曾經執行過的gtid集合。比如:f6b65f0b-a251-11ea-8921-b8599f2ef058:1-5 表示曾經執行過1~5共五個事物。

global和session之間的區別:如果在global級別下,我們打開一個會話然後設置值,關掉這個窗口,打開一個新的窗口,依然能看在上一個被關閉的窗口設置的值。 如果在session級別下,打開一個窗口A設置值,然後打開一個新的窗口重新查看是看不到在繪畫A中修改過的值的。

4.2 gtid_own

它既可以是一個global類型的變量,也可以是一個session級別的變量。是只讀的,它記錄的是當前實例正在執行的gtid,以及對應的線程id。

4.3 gtid_purged

它是一個全局的變量,purge就是丟棄的意思,而bin-log是實現主從之間的數據同步,主要是起到一個中間者的作用,主從數據同步之後,binlog其實就可以被清除了,線上的日誌文件被清理最勤的日誌文件恐怕就binlog,可能每隔幾個小時或者1天清理一次。

這個gtid_purged所裝載的就是被丟棄的bin-log對應的gtid集合, gtid_purged是gtid_executed的子集,是不能被隨意更改的,只有在@@global.gtid_executed為空的情況下才能修改這個值。


認識了上面的幾個變量後:整理一下整個流程:

  • 開啟GTID模式後,我們指定MASTER_AUTO_POSITION=1。然後 start slave時,從庫會計算Retrieved_Gtid_SetExecuted_Gtid_Set的並集(通過show slave status可以查看),然後把這個GTID並集發送給主庫。主庫將從庫請求的GTID集合和自己的gtid_executed比較,把從庫GTID集合里缺失的事務全都發送給從庫。從庫再拿着這些gtid在自己本地回放事物,同步數據。

  • gtid是有冪等性的,從庫碰到原來使用過的gtid會直接跳過。

  • 如果從庫缺失的GTID已經被主庫pruge了,那麼從庫報1236錯誤,IO線程中斷。

此外,從庫的Retrieved_Gtid_SetExecuted_Gtid_Set在哪裡查看呢?

通過show slave status 查看

image-20200603191208054


再看這張圖:

這張圖是從庫的gtid相關信息:

從張圖乍一看gtid的信息是比較亂的:

server_uuid後綴為058結尾的是從庫同步的主庫數據時產生的。

server_uuid後綴以b38(從庫自己的server_uuid)結尾的記錄,其實是我直接在從庫insert數據產生的。

五、MySQL的冪等性

默認情況下MySQL冪等級別是:strict , 表示嚴格模式。 舉個例子,在這種情況下:假設id為主鍵,假設數據庫中已經存在了一條id=1 ,value = 1 的數據,我們重複的插入id=1 , value=2的新數據mysql會爆出 主鍵衝突的錯誤。

如果我們將冪等模式調整成:idemptent ,再往MySQL中插入一條id=1,value = 2的數據,此時不會爆出主鍵衝突,這條新數據會將原來value = 1 覆蓋成value=2。

開啟主從的模式下從庫的: slave_exec_mode 參數一般會被設置成:idemptent  , 這樣可以保證從庫中的數據和主庫中的數據保持一致。 並且不會發生這種問題:比如主庫中沒有id = 100的數據,故主庫中可以順利寫入id=100的數據, 從庫中有id=100的數據,但是因為開啟冪等模式,從庫不會爆出主鍵衝突的錯誤而中斷主從關係,而是使用主庫binlog中的新值去覆蓋當前存在的舊值。

六、拓展:

假設存在這樣一種場景:

有一對主從正常運行保持數據同步狀態。然後意外發生了:從庫在回放主庫binlog時有一個gtid對應的事物執行失敗了。具體一點,比如主庫現在 excuted_gtid = 1-20 , 然後從庫回放到第10條事物時還沒問題,但是回放到第11條事物時因為數據對不起來,執行失敗了,緊接着斷開了主從同步到關係。

如果是使用 binlog+position 實現的主從同步,我們可以設置 sql_slave_skip_counter 讓從庫跳過失敗的事物完成再恢復同步關係。

但是使用gtid構建的主從同步就不能skip事物了,它是自動找點的。

並且你通過show variables like '%gtid%' 可以看一下gtid的信息, gtid_next是原子自增的。

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                      | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-9 |
| session_track_gtids              | OFF                                      |
+----------------------------------+------------------------------------------+
8 rows in set (0.01 sec)

查看主庫的執行狀態:也能看到gtid的序號是連續的。


mysql> show master status;
+------------------+----------+--------------+------------------+------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                        |
+------------------+----------+--------------+------------------+------------------------------------------+
| mysql-bin.000006 |      194 |              |                  | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-9 |
+------------------+----------+--------------+------------------+------------------------------------------+

所以如果出現了上面的情景,可以像下面這樣做:

# 第gtid == 11 出問題了,就跳過它
set global sql_slave_skip_counter = 11;

# 執行begin commit 不會讓現有的gtid+1,僅僅是交一個空事物,佔據這個gtid=11的位置
BIGIN;COMMIT;

七、實驗:

小實驗1:

可以做一個小實驗:

先往主庫中寫入一條數據,然後刷新日誌落盤

mysql> INSERT INTO `xxx`.`yyy` (`id` , `val`) VALUES (NULL , '123');
Query OK, 1 row affected (0.01 sec)

mysql> flush logs;
Query OK, 0 rows affected (0.00 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000005 |       506 |
| mysql-bin.000006 |       194 |
+------------------+-----------+
2 rows in set (0.00 sec)

退出mysql查看所有的日誌:

然後我們手動執行 rm -rf mysql-bin.00000*

再登陸mysql查看gtid_pured的變化情況:(你會發現,當我手動刪除主庫的binlog時,gtid_purged中沒有任何記錄)

mysql> show global variables like 'gtid%';
+----------------------------------+------------------------------------------+
| Variable_name                    | Value                                    |
+----------------------------------+------------------------------------------+
| gtid_executed                    | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-7 |
| gtid_executed_compression_period | 1000                                     |
| gtid_mode                        | ON                                       |
| gtid_owned                       |                                          |
| gtid_purged                      |                                          |
+----------------------------------+------------------------------------------+
5 rows in set (0.00 sec)

當然上面手動rm -rf刪除日誌的方式是在開玩笑,真實的purged操作如下:

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000005 |       506 |
| mysql-bin.000006 |       194 |
+------------------+-----------+
2 rows in set (0.00 sec)

mysql> purge binary logs to 'mysql-bin.000006';
Query OK, 0 rows affected (0.00 sec)

mysql> show binary logs;
+------------------+-----------+
| Log_name         | File_size |
+------------------+-----------+
| mysql-bin.000006 |       194 |
+------------------+-----------+
1 row in set (0.00 sec)

mysql> show global variables like 'gtid%';
+----------------------------------+------------------------------------------+
| Variable_name                    | Value                                    |
+----------------------------------+------------------------------------------+
| gtid_executed                    | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-9 |
| gtid_executed_compression_period | 1000                                     |
| gtid_mode                        | ON                                       |
| gtid_owned                       |                                          |
| gtid_purged                      | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-9 |
+----------------------------------+------------------------------------------+
5 rows in set (0.00 sec)

命令中的purge binary logs to 'xxx' 是通過mysql的機制去刪除binlog。刪除的範圍是 mysql-bin00000x之前的所有binlog,而不包含 mysql-bin00000x。

小實驗2:

假設我現在有AB兩台主從MySQL,兩台都開啟gtid, 我先通過gtid 完成主從之間的同步關係,插入幾條數據後,主從的gtid值都為 1-10 。

然後我開始搞事情:斷開主從,先往主庫寫入一條數據(gtid=11)。

mysql> INSERT INTO `dktest00`.`testaaa00` (`id` , `val`)VALUES (NULL , '123');
Query OK, 1 row affected (10.00 sec)

mysql> show master status;
+------------------+----------+--------------+------------------+-------------------------------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set                         |
+------------------+----------+--------------+------------------+-------------------------------------------+
| mysql-bin.000006 |      732 |              |                  | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-11 |
+------------------+----------+--------------+------------------+-------------------------------------------+

**再通過 binlog + position 完成兩者都同步關係。再往主庫中插入一條數據:

# set position = 732 
# binlog = f6b65f0b-a251-11ea-8921-b8599f2ef058:1-11

mysql> INSERT INTO `dktest00`.`testaaa00` (`id` ,`val`)VALUES (NULL , '123');
Query OK, 1 row affected (0.00 sec)

然後我去查看從庫的gtid信息:

mysql> show  global variables like 'gtid%';
+----------------------------------+----------------------------------------------+
| Variable_name                    | Value                                        |
+----------------------------------+----------------------------------------------+
| gtid_executed                    | f6b65f0b-a251-11ea-8921-b8599f2ef058:1-10:12 |
| gtid_executed_compression_period | 1000                                         |
| gtid_mode                        | ON                                           |
| gtid_owned                       |                                              |
| gtid_purged                      |                                              |
+----------------------------------+----------------------------------------------+
5 rows in set (0.00 sec)

我們會發現,雖然是使用position+binlog同步數據,但是只要gtid開啟了,gtid就會記錄曾經執行的事物的信息

那並且如果我們定位potion = 732 ,從庫回放的事物就不會包含 gtid = 11, 這一點我們可以通過查看binlog作證

mysqlbinlog --no-defaults -vv /binlog的絕對路徑。

image-20200603200056676

這說明啥事呢? 比如現在主庫中有1,2,3條數據,然後你執行show master status; 看到這個position = 732

那這個732其實不是不包含第三條數據的。換句話說從庫從732同步數據,不會同步上第3條數據。

然後我們在此基礎上繼續搞事情:我們斷開主從,重新設置從庫position 為 732的上一個事物的位點(459),然後觀察從庫的 slave staus 情況。

image-20200603212632898

開始同步後查看從庫中的數據,可以發現剛才跳過的gtid=11對應的數據已經被同步回來了。

Tags: