1. 程式人生 > 其它 >MySQL是怎麼保證主備一致的?

MySQL是怎麼保證主備一致的?

在前面的文章中,我不止一次地和你提到了binlog,大家知道binlog可以用來歸檔,也可以用來做主備同步,但它的內容是什麼樣的呢?為什麼備庫執行了binlog就可以跟主庫保持一致了呢?今天我就正式地和你介紹一下它。

毫不誇張地說,MySQL能夠成為現下最流行的開源資料庫,binlog功不可沒。

在最開始,MySQL是以容易學習和方便的高可用架構,被開發人員青睞的。而它的幾乎所有的高可用架構,都直接依賴於binlog。雖然這些高可用架構已經呈現出越來越複雜的趨勢,但都是從最基本的一主一備演化過來的。

MySQL主備的基本原理

如圖1所示就是基本的主備切換流程

在狀態1中,客戶端的讀寫都直接訪問節點A,而節點B是A的備庫,只是將A的更新都同步過來,到本地執行。這樣可以保持節點B和A的資料是相同的。

當需要切換的時候,就切成狀態2。這時候客戶端讀寫訪問的都是節點B,而節點A是B的備庫。

在狀態1中,雖然節點B沒有被直接訪問,但是我依然建議你把節點B(也就是備庫)設定成只讀(readonly)模式。這樣做,有以下幾個考慮:

  1. 有時候一些運營類的查詢語句會被放到備庫上去查,設定為只讀可以防止誤操作;

  2. 防止切換邏輯有bug,比如切換過程中出現雙寫,造成主備不一致;

  3. 可以用readonly狀態,來判斷節點的角色。

你可能會問,我把備庫設定成只讀了,還怎麼跟主庫保持同步更新呢?

這個問題,你不用擔心。因為readonly設定對超級(super)許可權使用者是無效的,而用於同步更新的執行緒,就擁有超級許可權。

接下來,我們再看看節點A到B這條線的內部流程是什麼樣的。圖2中畫出的就是一個update語句在節點A執行,然後同步到節點B的完整流程圖。

圖2中,包含了我在上一篇文章中講到的binlog和redo log的寫入機制相關的內容,可以看到:主庫接收到客戶端的更新請求後,執行內部事務的更新邏輯,同時寫binlog。

備庫B跟主庫A之間維持了一個長連線。主庫A內部有一個執行緒,專門用於服務備庫B的這個長連線。一個事務日誌同步的完整過程是這樣的:

  • 在備庫B上通過change master命令,設定主庫A的IP、埠、使用者名稱、密碼,以及要從哪個位置開始請求binlog,這個位置包含檔名和日誌偏移量。在備庫B上執行start slave命令,這時候備庫會啟動兩個執行緒,就是圖中的io_thread和sql_thread。其中io_thread負責與主庫建立連線。
  • 在備庫B上執行start slave命令,這時候備庫會啟動兩個執行緒,就是圖中的io_thread和sql_thread。其中io_thread負責與主庫建立連線。
  • 主庫A校驗完使用者名稱、密碼後,開始按照備庫B傳過來的位置,從本地讀取binlog,發給B。
  • 備庫B拿到binlog後,寫到本地檔案,稱為中轉日誌(relay log)。
  • sql_thread讀取中轉日誌,解析出日誌裡的命令,並執行。

binlog的三種格式對比

binlog有兩種格式,一種是statement,一種是row。可能你在其他資料上還會看到有第三種格式,叫作mixed,其實它就是前兩種格式的混合。

mysql> CREATE TABLE `t` (
  `id` int(11) NOT NULL,
  `a` int(11) DEFAULT NULL,
  `t_modified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  KEY `a` (`a`),
  KEY `t_modified`(`t_modified`)
) ENGINE=InnoDB;

insert into t values(1,1,'2018-11-13');
insert into t values(2,2,'2018-11-12');
insert into t values(3,3,'2018-11-11');
insert into t values(4,4,'2018-11-10');
insert into t values(5,5,'2018-11-09');

如果要在表中刪除一行資料的話,我們來看看這個delete語句的binlog是怎麼記錄的。

注意,下面這個語句包含註釋,如果你用MySQL客戶端來做這個實驗的話,要記得加-c引數,否則客戶端會自動去掉註釋。

mysql> delete from t /*comment*/  where a>=4 and t_modified<='2018-11-10' limit 1;

當binlog_format=statement時,binlog裡面記錄的就是SQL語句的原文。你可以用

mysql> show binlog events in 'master.000001';

命令看binlog中的內容。