1. 程式人生 > >深入MySQL復制(三):半同步復制

深入MySQL復制(三):半同步復制

posit script scrip 動態加載 數據庫狀態 status 半同步 自動切換 point

1.半同步復制

半同步復制官方手冊:https://dev.mysql.com/doc/refman/5.7/en/replication-semisync.html

默認情況下,MySQL的復制是異步的,master將新生成的binlog發送給各slave後,無需等待slave的ack回復(slave將接收到的binlog寫進relay log後才會回復ack),直接就認為這次DDL/DML成功了。

半同步復制(semi-synchronous replication)是指master在將新生成的binlog發送給各slave時,只需等待一個(默認)slave返回的ack信息就返回成功。

技術分享圖片

MySQL 5.7對半同步復制作了大改進,新增了一個master線程。在MySQL 5.7以前,master上的binlog dump線程負責兩件事:dump日誌給slave的io_thread;接收來自slave的ack消息。它們是串行方式工作的。在MySQL 5.7中,新增了一個專門負責接受ack消息的線程ack collector thread。這樣master上有兩個線程獨立工作,可以同時發送binlog到slave和接收slave的ack。

還新增了幾個變量,其中最重要的是 rpl_semi_sync_master_wait_point ,它使得MySQL半同步復制有兩種工作模型。解釋如下。

2.半同步復制的兩種類型

從MySQL 5.7.2開始,MySQL支持兩種類型的半同步復制。這兩種類型由變量 rpl_semi_sync_master_wait_point (MySQL 5.7.2之前沒有該變量)控制,它有兩種值:AFTER_SYNC和AFTER_COMMIT。在MySQL 5.7.2之後,默認值為AFTER_SYNC,在此版本之前,等價的類型為AFTER_COMMIT。

這個變量控制的是master何時提交、何時接收ack以及何時回復成功信息給客戶端的時間點。

  1. AFTER_SYNC模式:master將新的事務寫進binlog(buffer),然後發送給slave,再sync到自己的binlog file(disk)。之後才允許接收slave的ack回復,接收到ack之後才會提交事務,並返回成功信息給客戶端。
  2. AFTER_COMMIT模式:master將新的事務寫進binlog(buffer),然後發送給slave,再sync到自己的binlog file(disk),然後直接提交事務。之後才允許接收slave的ack回復,然後再返回成功信息給客戶端。

畫圖理解就很清晰。

技術分享圖片

技術分享圖片

再來分析下這兩種模式的優缺點。

  • AFTER_SYNC
    • 對於所有客戶端來說,它們看到的數據是一樣的,因為它們看到的數據都是在接收到slave的ack後提交後的數據。
    • 這種模式下,如果master突然故障,不會丟失數據,因為所有成功的事務都已經寫進slave的relay log中了,slave的數據是最新的。
  • AFTER_COMMIT
    • 不同客戶端看到的數據可能是不一樣的。對於發起事務請求的那個客戶端,它只有在master提交事務且收到slave的ack後才能看到提交的數據。但對於那些非本次事務的請求客戶端,它們在master提交後就能看到提交後的數據,這時候master可能還沒收到slave的ack。
    • 如果master收到ack回復前,slave和master都故障了,那麽將丟失這個事務中的數據。

在MySQL 5.7.2之前,等價的模式是 AFTER_COMMIT ,在此版本之後,默認的模式為 AFTER_SYNC ,該模式能最大程度地保證數據安全性,且性能上並不比 AFTER_COMMIT 差。

3.半同步復制插件介紹

MySQL的半同步是通過加載google為MySQL提供的半同步插件 semisync_master.sosemisync_slave.so 來實現的。其中前者是master上需要安裝的插件,後者是slave上需要安裝的插件。

MySQL的插件位置默認存放在$basedir/lib/plugin目錄下。例如,yum安裝的mysql-server,插件目錄為/usr/lib64/mysql/plugin。

[root@xuexi ~]# find / -type f -name "semisync*" 
/usr/lib64/mysql/plugin/debug/semisync_master.so
/usr/lib64/mysql/plugin/debug/semisync_slave.so
/usr/lib64/mysql/plugin/semisync_master.so
/usr/lib64/mysql/plugin/semisync_slave.so

因為要加載插件,所以應該保證需要加載插件的MySQL的全局變量 have_dynamic_loading 已經設置為YES(默認值就是YES),否則無法動態加載插件。

mysql> select @@global.have_dynamic_loading;
+-------------------------------+
| @@global.have_dynamic_loading |
+-------------------------------+
| YES                           |
+-------------------------------+

3.1 MySQL中安裝插件的方式

安裝插件有兩種方式:1.在mysql環境中使用INSTALL PLUGIN語句臨時安裝;2.在配置文件中配置永久生效。

INSTALL安裝插件的語法為:

Syntax:
INSTALL PLUGIN plugin_name SONAME 'shared_library_name'
UNINSTALL PLUGIN plugin_name

例如,使用INSTALL語句在master上安裝 semisync_master.so 插件。

mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';

配置文件中加載插件的方式為:

[mysqld]
plugin-load='plugin_name=shared_library_name'

例如,配置文件中加載semisync_master.so插件。

[mysqld]
plugin-load="rpl_semi_sync_master=sermisync_master.so"

如果需要加載多個插件,則插件之間使用分號分隔。例如,在本節的slave1既是slave,又是master,需要同時安裝兩個半同步插件。

[mysqld]
plugin-load="rpl_semi_sync_master=semisync_master.so;rpl_sync_slave=semisync_slave.so"

安裝插件後,應該使用show plugins來查看插件是否真的激活。

mysql> show plugins;
+----------------------+--------+-------------+--------------------+---------+
| Name                 | Status | Type        | Library            | License |
+----------------------+--------+-------------+--------------------+---------+
......
| rpl_semi_sync_master | ACTIVE | REPLICATION | semisync_master.so | GPL     |
+----------------------+--------+-------------+--------------------+---------+

或者查看information_schema.plugins表獲取更詳細的信息。

mysql> select * from information_schema.plugins where plugin_name like "%semi%"\G
*************************** 1. row ***************************
           PLUGIN_NAME: rpl_semi_sync_master
        PLUGIN_VERSION: 1.0
         PLUGIN_STATUS: ACTIVE
           PLUGIN_TYPE: REPLICATION
   PLUGIN_TYPE_VERSION: 4.0
        PLUGIN_LIBRARY: semisync_master.so
PLUGIN_LIBRARY_VERSION: 1.7
         PLUGIN_AUTHOR: He Zhenxing
    PLUGIN_DESCRIPTION: Semi-synchronous replication master
        PLUGIN_LICENSE: GPL
           LOAD_OPTION: ON
1 row in set (0.00 sec)

插件裝載完成後,半同步功能還未開啟,需要手動設置它們啟動,或者寫入配置文件永久生效。

# 開啟master的半同步
mysql> set @@global.rpl_semi_sync_master_enabled=1;

# 開啟slave半同步
mysql> set @@globale.rpl_semi_sync_slave_enabled=1;

或者配合插件加載選項一起寫進配置文件永久開啟半同步功能。

[mysqld]
rpl_semi_sync_master_enabled=1

[mysqld]
rpl_semi_sync_slave_enabled=1

3.2 半同步插件相關的變量

安裝了 semisync_master.sosemisync_slave.so 後,這兩個插件分別提供了幾個變量。

mysql> show global variables like "%semi%";
+-------------------------------------------+------------+
| Variable_name                             | Value      |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled              | OFF        |
| rpl_semi_sync_master_timeout              | 10000      |
| rpl_semi_sync_master_trace_level          | 32         |
| rpl_semi_sync_master_wait_for_slave_count | 1          |
| rpl_semi_sync_master_wait_no_slave        | ON         |
| rpl_semi_sync_master_wait_point           | AFTER_SYNC |
| rpl_semi_sync_slave_enabled               | OFF        |
| rpl_semi_sync_slave_trace_level           | 32         |
+-------------------------------------------+------------+

下面還多給了兩個和半同步相關的狀態變量的解釋,可以通過show status like %semi%;查看它們。

  1. master相關的變量:
    • ①.Rpl_semi_sync_master_clients:(狀態變量)master所擁有的半同步復制slave的主機數量。
    • ②.Rpl_semi_sync_master_status :(狀態變量)master當前是否以半同步復制狀態工作(ON),OFF表示降級為了異步復制。
    • ③.rpl_semi_sync_master_enabled:master上是否啟用了半同步復制。
    • ④.rpl_semi_sync_master_timeout:等待slave的ack回復的超時時間,默認為10秒。
    • ⑤.rpl_semi_sync_master_trace_level:半同步復制時master的調試級別。
    • ⑥.rpl_semi_sync_master_wait_for_slave_count:master在超時時間內需要收到多少個ack回復才認為此次DML成功,否則就降級為異步復制。該變量在MySQL5.7.3才提供,在此之前的版本都默認為收到1個ack則確認成功,且不可更改。MySQL 5.7.3之後該變量的默認值也是1。
    • ⑦.rpl_semi_sync_master_wait_no_slave:值為ON(默認)或者OFF。ON表示master在超時時間內如果未收到指定數量的ack消息,則會一直等待下去直到收滿ack,即一直采用半同步復制方式,不會降級;OFF表示如果在超時時間內未收到指定數量的ack,則超時時間一過立即降級為異步復制。

      更官方的解釋是:當設置為ON時,即使狀態變量Rpl_semi_sync_master_clients中的值小於rpl_semi_sync_master_wait_for_slave_count,Rpl_semi_sync_master_status依舊為ON;當設置為OFF時,如果clients的值小於count的值,則Rpl_semi_sync_master_status立即變為OFF。通俗地講,就是在超時時間內,如果slave宕機的數量超過了應該要收到的ack數量,master是否降級為異步復制。

      該變量在MySQL 5.7.3之前似乎沒有效果,因為默認設置為ON時,超時時間內收不到任何ack時仍然會降級為異步復制。

    • ⑧.rpl_semi_sync_master_wait_point:控制master上commit、接收ack、返回消息給客戶端的時間點。值為 AFTER_SYNCAFTER_COMMIT ,該選項是MySQL5.7.2後引入的,默認值為 AFTER_SYNC ,在此版本之前,等價於使用了 AFTER_COMMIT 模式。關於這兩種模式,見前文對兩種半同步類型的分析。

  2. slave相關的變量:
    • ①.rpl_semi_sync_slave_enabled:slave是否開啟半同步復制。
    • ②.rpl_semi_sync_slave_trace_level:slave的調試級別。

4.配置半同步復制

需要註意的是,"半同步"是同步/異步類型的一種情況,既可以實現半同步的傳統復制,也可以實現半同步的GTID復制。其實半同步復制是基於異步復制的,它是在異步復制的基礎上通過加載半同步插件的形式來實現半同步性的。

此處以全新的環境進行配置,方便各位道友"依葫蘆畫瓢"。

本文實現如下拓撲圖所示的半同步傳統復制。如果要實現半同步GTID復制,也只是在gtid復制的基礎上改改配置文件而已。

技術分享圖片

具體環境:

稱呼 主機IP MySQL版本 OS 角色(master/slave) 數據庫狀態
master 192.168.100.21 MySQL 5.7.22 CentOS 7.2 master 全新實例
salve1 192.168.100.22 MySQL 5.7.22 CentOS 7.2 semi_slave for master
semi_master for other slaves
全新實例
slave2 192.168.100.23 MySQL 5.7.22 CentOS 7.2 semi_slave for slave1 全新實例
slave3 192.168.100.24 MySQL 5.7.22 CentOS 7.2 semi_slave for slave1 全新實例

因為都是全新的實例環境,所以無需考慮基準數據和binlog坐標的問題。如果開始測試前,已經在master上做了一些操作,或者創建了一些新數據,那麽請將master上的數據恢復到各slave上,並獲取master binlog的坐標,具體操作方法可參見前文:將slave恢復到master指定的坐標。

4.1 半同步復制的配置文件

首先提供各MySQL Server的配置文件。

以下是master的配置文件。

[mysqld]
datadir=/data
socket=/data/mysql.sock
log-error=/data/error.log
pid-file=/data/mysqld.pid
log-bin=/data/master-bin
sync-binlog=1
server-id=100
plugin-load="rpl_semi_sync_master=semisync_master.so"
rpl_semi_sync_master_enabled=1

以下是slave1的配置文件,註意slave1同時還充當著slave2和slave3的master的角色。

[mysqld]
datadir=/data
socket=/data/mysql.sock
log-error=/data/error.log
pid-file=/data/mysqld.pid
log-bin=/data/master-bin
sync-binlog=1
server-id=110
relay-log=/data/slave-bin
log-slave-updates
plugin-load="rpl_semi_sync_master=semisync_master.so;rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled=1
rpl_semi_sync_master_enabled=1

以下是slave2和slave3的配置文件,它們配置文件除了server-id外都一致。

[mysqld]
datadir=/data
socket=/data/mysql.sock
log-error=/data/error.log
pid-file=/data/mysqld.pid
server-id=120      # slave3的server-id=130
relay-log=/data/slave-bin
plugin-load="rpl_semi_sync_slave=semisync_slave.so"
rpl_semi_sync_slave_enabled=1
read-only=on

4.2 啟動復制線程

現在master上創建一個專門用於復制的用戶。

mysql> create user repl@'192.168.100.%' identified by 'P@ssword1!';
mysql> grant replication slave on *.* to repl@'192.168.100.%';

因為master和所有的slave都是全新的實例,所以slave上指定的binlog坐標可以從任意位置開始。不過剛才master上創建了一個用戶,也會寫binlog,所以建議還是從master的第一個binlog的position=4開始。

以下是slave1上的change master to參數:

mysql> change master to 
            master_host='192.168.100.21',
            master_port=3306,
            master_user='repl',
            master_password='P@ssword1!',
            master_log_file='master-bin.000001',
            master_log_pos=4;

以下是slave2和slave3的change master to參數:

mysql> change master to 
            master_host='192.168.100.22',
            master_port=3306,
            master_user='repl',
            master_password='P@ssword1!',
            master_log_file='master-bin.000001',
            master_log_pos=4;

啟動各slave上的兩個SQL線程。

mysql> start slave;

一切就緒後,剩下的事情就是測試。在master上對數據做一番修改,然後查看是否會同步到slave1、slave2、slave3上。

5.半同步復制的狀態信息

首先是semisync相關的可修改變量,這幾個變量在前文已經解釋過了。

例如以下是開啟了半同步復制後的master上的semisync相關變量。

mysql> show global variables like "%semi%";
+-------------------------------------------+------------+
| Variable_name                             | Value      |
+-------------------------------------------+------------+
| rpl_semi_sync_master_enabled              | ON         |
| rpl_semi_sync_master_timeout              | 10000      |
| rpl_semi_sync_master_trace_level          | 32         |
| rpl_semi_sync_master_wait_for_slave_count | 1          |
| rpl_semi_sync_master_wait_no_slave        | ON         |
| rpl_semi_sync_master_wait_point           | AFTER_SYNC |
+-------------------------------------------+------------+

關於半同步復制,還有幾個狀態變量很重要。

例如,以下是master上關於semi_sync的狀態變量信息。

mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |  # 註意行1
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 5     |
| Rpl_semi_sync_master_no_times              | 1     |
| Rpl_semi_sync_master_no_tx                 | 1     |
| Rpl_semi_sync_master_status                | ON    |  # 註意行2
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 384   |
| Rpl_semi_sync_master_tx_wait_time          | 1537  |
| Rpl_semi_sync_master_tx_waits              | 4     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
| Rpl_semi_sync_master_yes_tx                | 4     |
+--------------------------------------------+-------+

除了上面標註"註意行"的變量,其他都無需關註,而且其中有一些是廢棄了的狀態變量。

Rpl_semi_sync_master_clients是該master所連接到的slave數量。

Rpl_semi_sync_master_status是該master的半同步復制功能是否開啟。在有些時候半同步復制會降級為異步復制,這時它的值為OFF。

以下是slave1上關於semi_sync的狀態變量信息。

mysql>  show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 2     |  # 註意行1
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 8     |
| Rpl_semi_sync_master_no_times              | 2     |
| Rpl_semi_sync_master_no_tx                 | 4     |
| Rpl_semi_sync_master_status                | ON    |  # 註意行2
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 399   |
| Rpl_semi_sync_master_tx_wait_time          | 1199  |
| Rpl_semi_sync_master_tx_waits              | 3     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
| Rpl_semi_sync_master_yes_tx                | 3     |
| Rpl_semi_sync_slave_status                 | ON    |  # 註意行3
+--------------------------------------------+-------+

此外,從MySQL的錯誤日誌、show slave status也能獲取到一些半同步復制的狀態信息。下一節測試半同步復制再說明。

6.測試半同步復制(等待、降級問題)

前面已經搭建好了下面的半同步復制結構。

                                     |------> slave2
                master ---> slave1 ---
                                     |------> slave3

下面來測試半同步復制降級為異步復制的問題,借此來觀察一些semisync的狀態變化。

首先,只停掉slave2或slave3中其中一個io線程的話,slave1是不會出現降級的,因為默認的半同步復制只需等待一個ack回復即可返回成功信息。

如果同時停掉slave2和slave3的io線程,當master更新數據後,slave1在10秒(默認)之後將降級為異步復制。如下:

在slave2和slave3上執行:

mysql> stop slave io_thread;

在master上執行:

create database test1;
create table test1.t(id int);
insert into test1.t values(33);

在slave1上查看(在上面的步驟之後的10秒內查看):

mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 0     |  # clients=0
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 8     |
| Rpl_semi_sync_master_no_times              | 2     |
| Rpl_semi_sync_master_no_tx                 | 4     |
| Rpl_semi_sync_master_status                | ON    |  # status=ON
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 399   |
| Rpl_semi_sync_master_tx_wait_time          | 1199  |
| Rpl_semi_sync_master_tx_waits              | 3     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 1     |
| Rpl_semi_sync_master_yes_tx                | 3     |
| Rpl_semi_sync_slave_status                 | ON    |
+--------------------------------------------+-------+

可以看到在這一小段時間內,slave1還是半同步復制。此時用show slave status查看slave1。

# slave1上執行
mysql> show slave status \G
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 192.168.100.21
                  Master_User: repl
                  Master_Port: 3306
                Connect_Retry: 60
              Master_Log_File: master-bin.000004
          Read_Master_Log_Pos: 1762
               Relay_Log_File: slave-bin.000005
                Relay_Log_Pos: 1729
        Relay_Master_Log_File: master-bin.000004
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
................................................
      Slave_SQL_Running_State: Waiting for semi-sync ACK from slave
................................................

此時slave的SQL線程狀態是Waiting for semi-sync ACK from slave

但10秒之後再查看。

mysql> show status like "%semi%";
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 0     |
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 8     |
| Rpl_semi_sync_master_no_times              | 3     |
| Rpl_semi_sync_master_no_tx                 | 5     |
| Rpl_semi_sync_master_status                | OFF   |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 399   |
| Rpl_semi_sync_master_tx_wait_time          | 1199  |
| Rpl_semi_sync_master_tx_waits              | 3     |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0     |
| Rpl_semi_sync_master_wait_sessions         | 0     |
| Rpl_semi_sync_master_yes_tx                | 3     |
| Rpl_semi_sync_slave_status                 | ON    |
+--------------------------------------------+-------+

發現slave1已經關閉半同步功能了,也就是說降級為異步復制了。

此時查看slave1的錯誤日誌。

2018-06-11T03:43:21.765384Z 4 [Warning] Timeout waiting for reply of binlog (file: master-bin.000001, pos: 2535), semi-sync up to file master-bin.000001, position 2292.
2018-06-11T03:43:21.765453Z 4 [Note] Semi-sync replication switched OFF.

它先記錄了當前slave2/slave3中已經同步到slave1的哪個位置。然後將Semi-sync復制切換為OFF狀態,即降級為異步復制。

在下次slave2或slave3啟動IO線程時,slave1將自動切換回半同步復制,並發送那些未被復制的binlog。

深入MySQL復制(三):半同步復制