MySQL二進制日誌複製、GTID複製與半同步複製

NO IMAGE

一、日誌格式

1.1 二進制日誌格式

MySQL 二進制日誌是進行主從複製的基礎,它記錄了所有對 MySQL 數據庫的修改事件,包括增刪改查和表結構修改。當前 MySQL 一共支持三種二進制日誌格式,可以通過 binlog-format 參數來進行控制,其可選值如下:

  • STATEMENT:段格式。是 MySQL 最早支持的二進制日誌格式。其記錄的是實際執行修改的 SQL 語句,因此在進行批量修改時其所需要記錄的數據量比較小,但對於 UUID() 或者其他依賴上下文的執行語句,可能會在主備上產生不一樣的結果。
  • ROW:行格式,是 MySQL 5.7 版本之後默認的二進制日誌格式。其記錄的是修改前後的數據,因此在批量修改時其需要記錄的數據量比較大,但其安全性比較高,不會導致主備出現不一致的情況。同時因為 ROW 格式是在從庫上直接應用更改後的數據,其還能減少鎖的使用。
  • MIXED:是以上兩種日誌的混合方式,默認採用段格式進行記錄,當段格式不適用時 (如 UUID() ),則默認採用 ROW 格式。

通常在主備之間網絡情況良好的時,可以優先考慮使用 ROW 格式,此時數據一致性最高,其次是 MIXED 格式。在設置 ROW 格式時,還有一個非常重要的參數 binlog_row_image :

1.2 binlog_row_image

binlog_row_image 有以下三個可選值:

  • full:默認值,記錄行在修改前後所有列的值。

  • minimal:只記錄修改涉及列的值。

  • noblob:與 full 類似,但如果 BLOB 或 TEXT 列沒有修改,則不對其進行記錄。

binlog-format 和 binlog_row_image 的默認值可能在不同版本存在差異,可以使用以下命令進行查看。通常情況下,為了減少在主備複製中需要傳輸的數據量,可以將 binlog_row_image 的值設置為 minimal 或 noblob。

show variables like 'binlog_format';
show variables like 'binlog_row_image';

二、基於二進制日誌的複製

2.1 複製原理

MySQL 的複製原理如下圖所示:

  • 主庫首先將變更寫入到自己的二進制日誌中;
  • 備庫會啟動一個 IO 線程,然後主動去主庫節點上獲取變更日誌,並寫入到自己的中繼日誌中。
  • 之後從中繼日誌中讀取變更事件,在從庫上執行變更。
  • 當備庫與主庫數據狀態一致,備庫的 IO 線程就會進入睡眠。當主庫再次發生變更時,其會向備庫發出信號,喚醒 IO 線程並再次進行工作。

如果沒有進行任何配置,主庫在將變更寫入到二進制日誌後,就會返回對客戶端的響應,因此默認情況下的複製是完全異步進行的,主備之間可能會短暫存在數據不一致的情況。

MySQL二進制日誌複製、GTID複製與半同步複製

2.2 配置步驟

首先主節點需要開啟二進制日誌,並且在同一個複製環境下,主備節點的 server-id 需要不一樣:

[mysqld]
server-id = 226
# 開啟二進制日誌
log-bin=mysql-bin

在備份節點配置中繼日誌:

[mysqld]
server-id = 227
# 配置中繼日誌
relay_log  = mysql-relay-bin
# 為了保證數據的一致性,從節點應該設置為只讀
read_only = 1
# 以下兩個配置代表是否開啟二進制日誌,如果該從節點還作為其他備庫的主節點,則開啟,否則不用配置
log-bin = mysql-bin
# 是否將中繼節點收到的複製事件寫到自己的二進制日誌中
log_slave_updates = 1

登錄主節點 MySQL 服務,創建用於進行復制賬號,併為其授予權限:

CREATE USER 'repl'@'192.168.0.%' IDENTIFIED WITH mysql_native_password BY '123456'; 
GRANT REPLICATION SLAVE on *.* TO 'repl'@'192.168.0.%' ;

查看主節點二進制日誌的狀態:

mysql> SHOW MASTER STATUS;
+------------------+----------+--------------+------------------+-------------------+
| File             | Position | Binlog_Do_DB | Binlog_Ignore_DB | Executed_Gtid_Set |
+------------------+----------+--------------+------------------+-------------------+
| mysql-bin.000001 |      887 |              |                  |                   |
+------------------+----------+--------------+------------------+-------------------+

基於日誌和偏移量,建立複製鏈路:

CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
MASTER_USER='repl',    \
MASTER_PASSWORD='123456',\
MASTER_LOG_FILE='mysql-bin.000001',\
MASTER_LOG_POS=887;

開始複製:

START SLAVE;

查看從節點複製狀態,主要參數有 Slave_IO_Running 和 Slave_SQL_Running,其狀態都為 Yes 表示用於複製的 IO 進程已經開啟。Seconds_Behind_Master 參數表示從節點複製的延遲量。此時可以在主庫上進行任意更改,並在備庫上查看情況。

mysql> SHOW SLAVE STATUS\G
*************************** 1. row ***************************
Slave_IO_State: Waiting for master to send event
Master_Host: 192.168.0.226
Master_User: repl
Master_Port: 3306
Connect_Retry: 60
Master_Log_File: mysql-bin.000001
Read_Master_Log_Pos: 887
Relay_Log_File: mysql-relay-bin.000002
Relay_Log_Pos: 322
Relay_Master_Log_File: mysql-bin.000001
#    Slave_IO_Running: Yes
#   Slave_SQL_Running: Yes
Replicate_Do_DB:
Replicate_Ignore_DB:
Replicate_Do_Table:
Replicate_Ignore_Table:
Replicate_Wild_Do_Table:
Replicate_Wild_Ignore_Table:
Last_Errno: 0
Last_Error:
Skip_Counter: 0
Exec_Master_Log_Pos: 887
Relay_Log_Space: 530
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: 0
Master_SSL_Verify_Server_Cert: No
Last_IO_Errno: 0
Last_IO_Error:
Last_SQL_Errno: 0
Last_SQL_Error:
Replicate_Ignore_Server_Ids:
Master_Server_Id: 226
#              Master_UUID: e1148574-bdd0-11e9-8873-0800273acbfd
Master_Info_File: mysql.slave_master_info
SQL_Delay: 0
SQL_Remaining_Delay: NULL
Slave_SQL_Running_State: Slave has read all relay log; waiting for more updates
Master_Retry_Count: 86400
Master_Bind:
Last_IO_Error_Timestamp:
Last_SQL_Error_Timestamp:
Master_SSL_Crl:
Master_SSL_Crlpath:
Retrieved_Gtid_Set:
Executed_Gtid_Set:
Auto_Position: 0
Replicate_Rewrite_DB:
Channel_Name:
Master_TLS_Version:
Master_public_key_path:
Get_master_public_key: 0
Network_Namespace:
1 row in set (0.00 sec)

2.3 優缺點

基於二進制日誌的複製是 MySQL 最早使用的複製技術,因此 MySQL 對其的支持比較完善,對執行修改的 SQL 語句幾乎沒有任何限制。其主要的缺點是在一主多從的高可用複製架構中,如果主庫發生宕機,此時想要自動通過從庫的日誌和偏移量來確定新的主庫比較困難。

三、基於 GTID 的複製

2.1 GTID 簡介

MySQL 5.6 版本之後提供了一個新的複製模式:基於 GTID 的複製。GTID 全稱為 Global Transaction ID,即全局事務 ID。它由每個服務節點的唯一標識和其上的事務 ID 共同組成,格式為: server_uuid : transaction_id 。通過 GTID ,可以保證在主庫上的每一個事務都能在備庫上得到執行,不會存在任何疏漏。

2.2 配置步驟

主從服務器均增加以下 GTID 的配置:

gtid-mode = ON
# 防止執行不受支持的語句,下文會有說明
enforce-gtid-consistency = ON

如果配置過上面的基於二進制日誌的複製,還需要在從服務器上執行以下命令,關閉原有複製鏈路:

STOP SLAVE IO_THREAD FOR CHANNEL '';

建立新的基於 GTID 複製鏈路,指定 MASTER_AUTO_POSITION = 1 表示由程序來自動確認開始同步的 GTID 的位置:

CHANGE MASTER TO MASTER_HOST='192.168.0.226',\
MASTER_USER='repl',
MASTER_PASSWORD='123456',
MASTER_AUTO_POSITION=1;

開始複製:

START SLAVE;

在主節點上執行任意修改操作,並查看從節點狀態,關鍵的輸出如下:Retrieved_Gtid_Set 代表從主節點上接收到的兩個事務,Executed_Gtid_Set 表示這兩個事務已經在從庫上得到執行。

mysql> SHOW SLAVE STATUS\G
....
Master_UUID            : e1148574-bdd0-11e9-8873-0800273acbfd
Retrieved_Gtid_Set    : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
Executed_Gtid_Set    : e1148574-bdd0-11e9-8873-0800273acbfd:1-2
.....

2.3 優缺點

GTID 複製的優點在於程序可以自動確認開始複製的 GTID 點。但其仍然存在以下限制:

  • 不支持 CREATE TABLE … SELECT 語句。 因為在 ROW 格式下,該語句將會被記錄為具有不同 GTID 的兩個事務,此時從服務器將無法正確處理。

  • 事務,過程,函數和觸發器內部的 CREATE TEMPORARY TABLE 和 DROP TEMPORARY TABLE 語句均不受支持。

為防止執行不受支持的語句,建議配置和上文配置一樣,開啟 enforce-gtid-consistency 屬性, 開啟後在主庫上執行以上不受支持的語句都將拋出異常並提示。

四、半同步複製

在上面我們介紹過,不論是基於二進制日誌的複製還是基於 GTID 的複製,其本質上都是異步複製,假設從節點還沒有獲取到二進制日誌信息時主節點就宕機了,那麼就會存在數據不一致的問題。想要解決這個問題可以通過配置半同步複製來實現:進行半同步複製時,主節點會等待至少一個從節點獲取到二進制日誌後才將事務的執行結果返回給客戶端。具體配置步驟如下:

1. 安裝插件

MySQL 從 5.5 之後開始以插件的形式支持半同步複製,所以先需要先進行插件的安裝,命令如下:

-- 主節點上執行
mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
-- 從節點上執行
mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';

如果你的複製是基於高可用架構的,即從節點可能會在主節點宕機後成為新的主節點,而原主節點可能在失敗恢復後成為從節點,那麼為了保證半同步複製仍然有效,此時可以在主從節點上都安裝主從插件。安裝後使用以下命令查看是否安裝成功:

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS WHERE PLUGIN_NAME LIKE '%semi%';
+----------------------+---------------+
| PLUGIN_NAME          | PLUGIN_STATUS |
+----------------------+---------------+
| rpl_semi_sync_master | ACTIVE        |
| rpl_semi_sync_slave  | ACTIVE        |
+----------------------+---------------+

2. 配置半同步複製

半同步複製可以基於日誌複製或 GTID 複製開啟,只需要在其原有配置上增加以下配置:

# 主節點上增加如下配置:
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=1
# 從節點上增加如下配置:
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=1
# 和上面提到的一樣,如果是高可用架構下,則主從節點都可以增加主從配置:
plugin-load = "rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
rpl-semi-sync-master-enabled = 1
rpl-semi-sync-slave-enabled = 1

3. 啟動複製

按照二進制日誌或 GTID 的方式正常啟動複製即可,此時可以使用如下命令查看半同步日誌是否正在執行:

# 主節點
mysql> SHOW STATUS LIKE 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
# 從節點
mysql> SHOW STATUS LIKE 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON    |
+----------------------------+-------+

值為 ON 代表半同步複製配置成功。

4. 可選配置

半同步日誌還有以下兩個可選配置:一個是 rpl_semi_sync_master_wait_for_slave_count,它表示主節點需要至少等待幾個從節點複製完成,默認值為 1,因為等待過多從節點可能會導致長時間的延遲,所以通常使用默認值即可。另一個常用參數是 rpl_semi_sync_master_wait_point ,它主要是用於控制等待的時間點,它有以下兩個可選值:

  • AFTER_SYNC(默認值):主服務器將每個事務寫入其二進制日誌,並將二進制日誌同步到磁盤後開始進行等待。在收到從節點的確認後,才將事務提交給存儲引擎並將結果返回給客戶端。
  • AFTER_COMMIT:主服務器將每個事務寫入其二進制日誌並同步到磁盤,然後將事務提交到存儲引擎,提交後再進行等待。在收到從節點的確認後,才將結果返回給客戶端。

第二種方式是 MySQL 5.7.2 之前默認方式,但這種方式會導致數據的丟失,所以在 5.7.2 版本後就引入了第一種方式作為默認方式,它可以實現無損複製 (lossless replication),數據基本無丟失,因此 rpl_semi_sync_master_wait_point 參數通常也不用進行修改,採用默認值即可。想要查看當前版本該參數的值,可以使用如下命令:

mysql> SHOW VARIABLES LIKE 'rpl_semi_sync_master_wait_point';
+---------------------------------+------------+
| Variable_name                   | Value      |
+---------------------------------+------------+
| rpl_semi_sync_master_wait_point | AFTER_SYNC |
+---------------------------------+------------+

雖然半同步複製能夠最大程度的避免數據的丟失,但是因為網絡通訊會導致額外的等待時間的開銷,所以儘量在低延遲的網絡環境下使用,如處於同一機房的主機之間。

五、高可用架構

不論是主主複製結構,還是一主多從複製結構,單存依靠複製只能解決數據可靠性的問題,並不能解決系統高可用的問題,想要保證高可用,系統必須能夠自動進行故障轉移,即在主庫宕機時,主動將其它備庫升級為主庫。常用的有以下兩種解決方案:

5.1 MMM

MMM (Master-Master replication manager for MySQL) 是由 Perl 語言開發的一套支持雙主故障切換以及雙主日常管理的第三方軟件。它包含兩類角色:writerreader,分別對應讀寫節點和只讀節點。使用 MMM 管理的雙主節點在同一時間上只允許一個進行寫入操作,當 writer 節點出現宕機(假設是 Master1),程序會自動移除該節點上的讀寫 VIP,然後切換到 Master2 ,並設置 Master2 的 read_only = 0,即關閉只讀限制,同時將所有 Slave 節點重新指向 Master2。

除了管理雙主節點,MMM 也負責管理所有 Slave節點,在出現宕機、複製延遲或複製錯誤,MMM 會移除該節點的 VIP,直至節點恢復正常。MMM 高可用的架構示例圖如下:

MySQL二進制日誌複製、GTID複製與半同步複製

MMM 架構的缺點在於雖然其能實現自動切換,但卻不會主動補齊丟失的數據,所以會存在數據不一致性的風險。另外 MMM 的發佈時間比較早,所以其也不支持 MySQL 最新的基於 GTID 的複製,如果你使用的是基於 GTID 的複製,則只能採用 MHA。

5.2 MHA

MHA (Master High Availability) 是由 Perl 實現的一款高可用程序,相對於 MMM ,它能儘量避免數據不一致的問題。它監控的是一主多從的複製架構,架構如下圖所示:

MySQL二進制日誌複製、GTID複製與半同步複製

在 Master 節點宕機後,其處理流程如下:

  1. 嘗試從宕機 Master 中保存二進制日誌;
  2. 找到含有最新中繼日誌的 Slave;
  3. 把最新中繼日誌應用到其他實例,保證各實例數據一致;
  4. 應用從 Master 保存的二進制日誌事件;
  5. 提升一個 Slave 為 Master ;
  6. 其他 Slave 向該新 Master 同步。

按照以上的處理流程,MHA 能夠最大程序的避免數據不一致的問題。但如果 Master 所在的服務器也宕機了,那麼過程的第一步就會失敗。在 MySQL 5.5 後,可以開啟半同步複製後來避免這個問題,從而可以保證數據的一致性和幾乎無丟失。當然 MHA 集群也存在一下一些缺點:

  • 集群中所有節點之間需要開啟 SSH 服務,所以會存在一定的安全影響。
  • 沒有實現 Slave 的高可用。
  • 自帶的腳本不足,例如虛擬 IP 的配置需要自己通過命令或者其他第三方軟件來實現。
  • 需要手動清理中繼日誌。

以上就是 MMM 和 MHA 架構的簡單介紹,關於其具體搭建步驟,可以參考以下兩篇博客: MySQL集群搭建(3)-MMM高可用架構MySQL集群搭建(5)-MHA高可用架構

參考資料

更多文章,歡迎訪問 [全棧工程師手冊] ,GitHub 地址:github.com/heibaiying/…

相關文章

如何衡量一個人的JavaScript水平?

《程序人生》2020無畏年少青春,迎風瀟灑前行|年度徵文

將前端技術棧移植到掌上遊戲機

圖解JavaScript對象—現代JavaScript教程