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)模式。這樣做,有以下幾個考慮:
-
有時候一些運營類的查詢語句會被放到備庫上去查,設定為只讀可以防止誤操作;
-
防止切換邏輯有bug,比如切換過程中出現雙寫,造成主備不一致;
-
可以用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中的內容。