MySQL 半同步復制模式說明及配置示例 - 運維小結


 

MySQL主從復制包括異步模式、半同步模式、GTID模式以及多源復制模式,默認是異步模式 (如之前詳細介紹的mysql主從復制)。所謂異步模式指的是MySQL 主服務器上I/O thread 線程將二進制日志寫入binlog文件之后就返回客戶端結果,不會考慮二進制日志是否完整傳輸到從服務器以及是否完整存放到從服務器上的relay日志中,這種模式一旦主服務(器)宕機,數據就可能會發生丟失。

異步模式是一種基於偏移量的主從復制,實現原理是: 主庫開啟binlog功能並授權從庫連接主庫,從庫通過change master得到主庫的相關同步信息然后連接主庫進行驗證,主庫IO線程根據從庫slave線程的請求,從master.info開始記錄的位置點向下開始取信息,同時把取到的位置點和最新的位置與binlog信息一同發給從庫IO線程,從庫將相關的sql語句存放在relay-log里面,最終從庫的sql線程將relay-log里的sql語句應用到從庫上,至此整個同步過程完成,之后將是無限重復上述過程。

mysql主從復制的步驟: 1)在主庫與從庫都安裝mysql數據庫; 2) 在主庫的my.cnf配置文件中配置server-id 和log-bin; 3) 在登陸主庫后創建認證用戶並做授權; 4) 在從庫的my.cnf配置文件中配置server-id; 5) 登陸從庫后,指定master並開啟同步開關。

需要注意的是server-id主從庫的配置是不一樣的。

server-id存在作用: mysql同步的數據中是包含server-id的,而server-id用於標識該語句最初是從哪個server寫入的。因此server-id一定要有的

server-id不能相同的原因:每一個同步中的slave在master上都對應一個master線程,該線程就是通過slave的server-id來標識的;每個slave在master端最多有一個master線程,如果兩個slave的server-id 相同,則后一個連接成功時,slave主動連接master之后,如果slave上面執行了slave stop;則連接斷開,但是master上對應的線程並沒有退出;當slave start之后,master不能再創建一個線程而保留原來的線程,那樣同步就可能有問題;

在mysql做主主同步時,多個主需要構成一個環狀,但是同步的時候又要保證一條數據不會陷入死循環,這里就是靠server-id來實現的;

                                                                                                                                                                                               

描述msyql replication 機制的實現原理,如何在不停掉mysql主庫的情況下,恢復數據不一致的slave的數據庫節點?

MySQL的復制(replication)是一個異步的復制,從一個MySQL instace(稱之為Master)復制到另一個MySQL instance(稱之Slave)。實現整個復制操作主要由三個進程完成的,其中兩個進程在Slave(Sql進程和IO進程),另外一個進程在Master(IO進程)上。簡單來說,Mysql復制就是一種基於binlog的單線程異步復制過程!!

MySQL Replication復制的基本過程如下:
1) Slave上面的IO進程連接上Master,並請求從指定日志文件的指定位置(或者從最開始的日志)之后的日志內容

mysql> CHANGE MASTER TO
    -> MASTER_HOST='master_host_name',
    -> MASTER_USER='replication_user_name',
    -> MASTER_PASSWORD='replication_password',
    -> MASTER_LOG_FILE='recorded_log_file_name',
    -> MASTER_LOG_POS=recorded_log_position;
    -> MASTER_CONNECT_RETRY=10;                           #連接主庫失敗時,每隔10s鍾就重新連一下! 一般省略這一行配置,不用配置這一行。

2) Master接收到來自Slave的IO進程的請求后,通過負責復制的IO進程根據請求信息讀取制定日志指定位置之后的日志信息,返回給Slave的IO進程。返回信息中除了日志所包含的信息之外,還包括本次返回的信息已經到Master端的bin-log文件的名稱以及bin-log的位置;

3) Slave的IO進程接收到信息后,將接收到的日志內容依次添加到Slave端的relay-log文件的最末端,並將讀取到的Master端的bin-log的文件名和位置記錄到master-info文件中,以便在下一次讀取的時候能夠清楚的高速Maste "我需要從某個bin-log的哪個位置開始往后的日志內容,請發給我"!

4) Slave的Sql進程檢測到relay-log中新增加了內容后,會馬上解析relay-log的內容成為在Master端真實執行時候的那些可執行的內容,並在自身執行。

                                                                                                                                                                                               

上面提到的是mysql默認的異步同步模式,接下來重點說下Mysql半同步復制,從MySQL5.5開始,MySQL以插件的形式支持半同步復制。先來區別下mysql幾個同步模式概念:

異步復制(Asynchronous replication)
MySQL默認的復制即是異步的,主庫在執行完客戶端提交的事務后會立即將結果返給給客戶端,並不關心從庫是否已經接收並處理,這樣就會有一個問題,主如果crash掉了,此時主上已經提交的事務可能並沒有傳到從上,如果此時,強行將從提升為主,可能導致新主上的數據不完整。

全同步復制(Fully synchronous replication)
指當主庫執行完一個事務,所有的從庫都執行了該事務才返回給客戶端。因為需要等待所有從庫執行完該事務才能返回,所以全同步復制的性能必然會收到嚴重的影響。

半同步復制(Semisynchronous replication)
介於異步復制和全同步復制之間,主庫在執行完客戶端提交的事務后不是立刻返回給客戶端,而是等待至少一個從庫接收到並寫到relay log中才返回給客戶端。相對於異步復制,半同步復制提高了數據的安全性,同時它也造成了一定程度的延遲,這個延遲最少是一個TCP/IP往返的時間。所以,半同步復制最好在低延時的網絡中使用

對於異步復制,主庫將事務Binlog事件寫入到Binlog文件中,此時主庫只會通知一下Dump線程發送這些新的Binlog,然后主庫就會繼續處理提交操作,而此時不會保證這些Binlog傳到任何一個從庫節點上。

對於全同步復制,當主庫提交事務之后,所有的從庫節點必須收到,APPLY並且提交這些事務,然后主庫線程才能繼續做后續操作。這里面有一個很明顯的缺點就是,主庫完成一個事務的時間被拉長,性能降低。

對於半同步復制,是介於全同步復制和異步復制之間的一種,主庫只需要等待至少一個從庫節點收到並且Flush Binlog到Relay Log文件即可,主庫不需要等待所有從庫給主庫反饋。同時,這里只是一個收到的反饋,而不是已經完全執行並且提交的反饋,這樣就節省了很多時間。

                                                        Mysql半同步復制技術                                                                
一般而言,普通的replication,即MySQL的異步復制,依靠MySQL二進制日志也即binary log進行數據復制。比如兩台機器,一台主機(master),另外一台是從機(slave)。
正常的復制為:事務一(t1)寫入binlog buffer;dumper線程通知slave有新的事務t1;binlog buffer進行checkpoint;slave的io線程接收到t1並寫入到自己的的relay log;slave的sql線程寫入到本地數據庫。 這時,master和slave都能看到這條新的事務,即使master掛了,slave可以提升為新的master。
異常的復制為:事務一(t1)寫入binlog buffer;dumper線程通知slave有新的事務t1;binlog buffer進行checkpoint;slave因為網絡不穩定,一直沒有收到t1;master掛掉,slave提升為新的master,t1丟失。
很大的問題是:主機和從機事務更新的不同步,就算是沒有網絡或者其他系統的異常,當業務並發上來時,slave因為要順序執行master批量事務,導致很大的延遲。

為了彌補以上幾種場景的不足,MySQL從5.5開始推出了半同步復制。相比異步復制,半同步復制提高了數據完整性,因為很明確知道,在一個事務提交成功之后,這個事務就至少會存在於兩個地方。即在master的dumper線程通知slave后,增加了一個ack(消息確認),即是否成功收到t1的標志碼,也就是dumper線程除了發送t1到slave,還承擔了接收slave的ack工作。如果出現異常,沒有收到ack,那么將自動降級為普通的復制,直到異常修復后又會自動變為半同步復制。 

半同步復制具體特性
-  從庫會在連接到主庫時告訴主庫,它是不是配置了半同步。
-  如果半同步復制在主庫端是開啟了的,並且至少有一個半同步復制的從庫節點,那么此時主庫的事務線程在提交時會被阻塞並等待,結果有兩種可能,要么至少一個從庫節點通知它已經收到了所有這個事務的Binlog事件,要么一直等待直到超過配置的某一個時間點為止,而此時,半同步復制將自動關閉,轉換為異步復制。
-  從庫節點只有在接收到某一個事務的所有Binlog,將其寫入並Flush到Relay Log文件之后,才會通知對應主庫上面的等待線程。
-  如果在等待過程中,等待時間已經超過了配置的超時時間,沒有任何一個從節點通知當前事務,那么此時主庫會自動轉換為異步復制,當至少一個半同步從節點趕上來時,主庫便會自動轉換為半同步方式的復制。
-  半同步復制必須是在主庫和從庫兩端都開啟時才行,如果在主庫上沒打開,或者在主庫上開啟了而在從庫上沒有開啟,主庫都會使用異步方式復制。

下面來看看半同步復制的原理圖:

半同步復制的意思表示MASTER 只需要接收到其中一台SLAVE的返回信息,就會commit;否則需等待直至達到超時時間然后切換成異步再提交。這個做可以使主從庫的數據的延遲較小,可以在損失很小的性能的前提下提高數據的安全性。

主庫產生binlog到主庫的binlog file,傳到從庫中繼日志,然后從庫應用;也就是說傳輸是異步的,應用也是異步的。半同步復制指的是傳輸同步,應用還是異步的!
好處:保證數據不丟失(本機和遠端都有binlog)
壞處:不能保證應用的同步。

mysql半同步復制模式的流程圖

即主庫忽然崩了時,從庫雖然說有延遲,但是延遲過后,可以把從庫提升為主庫繼續服務,事后恢復到主庫即可

mysql異步復制模式的流程圖

半同步復制的潛在問題
客戶端事務在存儲引擎層提交后,在得到從庫確認的過程中,主庫宕機了,此時,可能的情況有兩種
事務還沒發送到從庫上
此時,客戶端會收到事務提交失敗的信息,客戶端會重新提交該事務到新的主上,當宕機的主庫重新啟動后,以從庫的身份重新加入到該主從結構中,會發現,該事務在從庫中被提交了兩次,一次是之前作為主的時候,一次是被新主同步過來的。
事務已經發送到從庫上
此時,從庫已經收到並應用了該事務,但是客戶端仍然會收到事務提交失敗的信息,重新提交該事務到新的主上。

無數據丟失的半同步復制
針對上述潛在問題,MySQL 5.7引入了一種新的半同步方案:Loss-Less半同步復制。針對上面這個圖,"Waiting Slave dump"被調整到"Storage Commit"之前。當然,之前的半同步方案同樣支持,MySQL 5.7.2引入了一個新的參數進行控制: rpl_semi_sync_master_wait_point, 這個參數有兩種取值:1) AFTER_SYNC , 這個是新的半同步方案,Waiting Slave dump在Storage Commit之前。2) AFTER_COMMIT, 這個是老的半同步方案。

來看下面半同步復制原理圖,分析下半同步復制潛在問題

master將每個事務寫入binlog(sync_binlog=1),傳遞到slave刷新到磁盤(sync_relay=1),同時主庫提交事務(commit)。master等待slave反饋收到relay log,只有收到ACK后master才將commit OK結果反饋給客戶端。

在MySQL 5.5-5.6使用after_commit的模式下,客戶端事務在存儲引擎層提交后,在得到從庫確認的過程中,主庫宕機了。此時,即主庫在等待Slave ACK的時候,雖然沒有返回當前客戶端,但事務已經提交,其他客戶端會讀取到已提交事務。如果Slave端還沒有讀到該事務的events,同時主庫發生了crash,然后切換到備庫。那么之前讀到的事務就不見了,出現了幻讀。

如果主庫永遠啟動不了,那么實際上在主庫已經成功提交的事務,在從庫上是找不到的,也就是數據丟失了,這是MySQL不願意看到的。所以在MySQL 5.7版本中增加了after_sync(無損復制)參數,並將其設置為默認半同步方式,解決了數據丟失的問題。

半同步復制的安裝部署條件
要想使用半同步復制,必須滿足以下幾個條件:
1)MySQL 5.5及以上版本
2)變量have_dynamic_loading為YES (查看命令:show variables like "have_dynamic_loading";)
3)主從復制已經存在 (即提前部署mysql主從復制環境,主從同步要配置基於整個數據庫的,不要配置基於某個庫的同步,即同步時不要過濾庫)

-  首先加載插件
因用戶需執行INSTALL PLUGIN, SET GLOBAL, STOP SLAVE和START SLAVE操作,所以用戶需有SUPER權限。

半同步復制是一個功能模塊,庫要能支持動態加載才能實現半同步復制! (安裝的模塊存放路徑為/usr/local/mysql/lib/plugin)
主數據庫執行:

mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';

[要保證/usr/local/mysql/lib/plugin/目錄下有semisync_master.so文件 (默認編譯安裝后就有)]
---------------------------------------------------------------------------------------
如果要卸載(前提是要關閉半同步復制功能),就執行
mysql> UNINSTALL PLUGIN rpl_semi_sync_master;

從數據庫執行:

mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';

[要保證/usr/local/mysql/lib/plugin/目錄下有semisync_slave.so文件 (默認編譯安裝后就有)]
---------------------------------------------------------------------------------------
如果要卸載(前提是要關閉半同步復制功能),就執行
mysql> UNINSTALL PLUGIN rpl_semi_sync_slave;

-  查看插件是否加載成功的兩種方式:
1) 方式一

mysql> show plugins;
........
| rpl_semi_sync_master       | ACTIVE   | REPLICATION        | semisync_master.so | GPL     |

2) 方式二

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS  WHERE PLUGIN_NAME LIKE '%semi%';
+----------------------+---------------+
| PLUGIN_NAME          | PLUGIN_STATUS |
+----------------------+---------------+
| rpl_semi_sync_master | ACTIVE        |
+----------------------+---------------+
1 row in set (0.00 sec)

-  啟動半同步復制
在安裝完插件后,半同步復制默認是關閉的,這時需設置參數來開啟半同步
主數據庫執行:

mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;

從數據庫執行:

mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;

以上的啟動方式是在登錄mysql后的命令行操作,也可寫在my.cnf配置文件中(推薦這種啟動方式)。
主數據庫的my.cnf配置文件中添加:

plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=1

從數據庫的my.cnf配置文件中添加:

plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=1

在個別高可用架構下,master和slave需同時啟動,以便在切換后能繼續使用半同步復制!即在主從數據庫的my.cnf配置文件中都要添加:

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

-  重啟從數據庫上的IO線程

mysql> STOP SLAVE IO_THREAD;
mysql> START SLAVE IO_THREAD;

特別注意: 如果沒有重啟,則默認的還是異步復制模式!,重啟后,slave會在master上注冊為半同步復制的slave角色。這時候,主的error.log中會打印如下信息:

2019-01-05T10:03:40.104327Z 5 [Note] While initializing dump thread for slave with UUID <ce9aaf22-5af6-11e6-850b-000c2988bad2>, found a zombie dump thread with the same UUID. Master is killing the zombie dump thread(4).
2019-01-05T10:03:40.111175Z 4 [Note] Stop asynchronous binlog_dump to slave (server_id: 2)
2019-01-05T10:03:40.119037Z 5 [Note] Start binlog_dump to master_thread_id(5) slave_server(2), pos(mysql-bin.000003, 621)
2019-01-05T10:03:40.119099Z 5 [Note] Start semi-sync binlog_dump to slave (server_id: 2), pos(mysql-bin.000003, 621)

-  查看半同步是否在運行
主數據庫:

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
1 row in set (0.00 sec)

從數據庫:

mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON    |
+----------------------------+-------+
1 row in set (0.20 sec)

這兩個變量常用來監控主從是否運行在半同步復制模式下。至此,MySQL半同步復制環境就部署完成了!

                                                                                                                                                                      

需要注意下,其實Mysql半同步復制並不是嚴格意義上的半同步復制。當半同步復制發生超時時(由rpl_semi_sync_master_timeout參數控制,單位是毫秒,默認為10000,即10s),會暫時關閉半同步復制,轉而使用異步復制。當master dump線程發送完一個事務的所有事件之后,如果在rpl_semi_sync_master_timeout內,收到了從庫的響應,則主從又重新恢復為半同步復制。[一旦有一次超時自動降級為異步].

mysql> show variables like "rpl_semi_sync_master_timeout";
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| rpl_semi_sync_master_timeout | 10000 |
+------------------------------+-------+
1 row in set (0.01 sec)

 接下來可以測試下:

1) 主數據庫 (從數據庫在執行"stop slave"之前)

mysql> create database bobo;
Query OK, 1 row affected (0.05 sec)

mysql> create table bobo.ceshi(id int);
Query OK, 0 row affected (0.28 sec)

mysql> insert into bobo.ceshi values(1);
Query OK, 1 row affected (0.09 sec)

2) 從數據執行"stop slave"

mysql> stop slave;

再觀察主數據庫

mysql> insert into bobo.ceshi values(2);
Query OK, 1 row affected (10.01 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | OFF   |
+-----------------------------+-------+
1 row in set (0.00 sec)

查看從數據庫

mysql> show status like 'Rpl_semi_sync_slave_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_slave_status  | OFF   |
+-----------------------------+-------+
1 row in set (0.01 sec)

3) 接着再在從數據庫執行"start slave"

mysql> start slave;

再觀察主數據

mysql> insert into bobo.ceshi values(3);
Query OK, 1 row affected (0.00 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
1 row in set (0.00 sec)

查看從數據庫

mysql> show status like 'Rpl_semi_sync_slave_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_slave_status  | ON   |
+-----------------------------+-------+
1 row in set (0.00 sec)

以上驗證分為三個階段:
1) 在Slave執行stop slave之前,主的insert操作很快就能返回。
2) 在Slave執行stop slave后,主的insert操作需要10.01s才返回,而這與rpl_semi_sync_master_timeout參數的時間相吻合。這時,查看兩個狀態的值,均為“OFF”了。同時,主的error.log中打印如下信息:

2019-01-05T11:51:49.855452Z 6 [Warning] Timeout waiting for reply of binlog (file: mysql-bin.000003, pos: 1447), semi-sync up to file mysql-bin.000003, position 1196.
2019-01-05T11:51:49.855742Z 6 [Note] Semi-sync replication switched OFF.

3) 在Slave執行start slave后,主的insert操作很快就能返回,此時,兩個狀態的值也變為“ON”了。同時,主的error.log中會打印如下信息:

2019-01-05T11:52:40.477098Z 7 [Note] Start binlog_dump to master_thread_id(7) slave_server(2), pos(mysql-bin.000003, 1196)
2019-01-05T11:52:40.477168Z 7 [Note] Start semi-sync binlog_dump to slave (server_id: 2), pos(mysql-bin.000003, 1196)
2019-01-05T11:52:40.523475Z 0 [Note] Semi-sync replication switched ON at (mysql-bin.000003, 1447)

                                                 其他變量說明                                                  

環境變量(show variables like '%Rpl%';)

mysql> show variables like '%Rpl%';
+-------------------------------------------+------------+
| 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 |
| rpl_stop_slave_timeout                    | 31536000   |
+-------------------------------------------+------------+
7 rows in set (0.30 sec)

rpl_semi_sync_master_wait_for_slave_count
MySQL 5.7.3引入的,該變量設置主需要等待多少個slave應答,才能返回給客戶端,默認為1。

rpl_semi_sync_master_wait_no_slave
ON
默認值,當狀態變量Rpl_semi_sync_master_clients中的值小於rpl_semi_sync_master_wait_for_slave_count時,Rpl_semi_sync_master_status依舊顯示為ON。

OFF
當狀態變量Rpl_semi_sync_master_clients中的值於rpl_semi_sync_master_wait_for_slave_count時,Rpl_semi_sync_master_status立即顯示為OFF,即異步復制。

簡單來說,如果mysql架構是1主2從,2個從都采用了半同步復制,且設置的是rpl_semi_sync_master_wait_for_slave_count=2,如果其中一個掛掉了,對於rpl_semi_sync_master_wait_no_slave設置為ON的情況,此時顯示的仍然是半同步復制,如果rpl_semi_sync_master_wait_no_slave設置為OFF,則會立刻變成異步復制。

狀態變量(show status like '%Rpl_semi%';)

mysql> show status like '%Rpl_semi%';
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |
| Rpl_semi_sync_master_net_avg_wait_time     | 0     |
| Rpl_semi_sync_master_net_wait_time         | 0     |
| Rpl_semi_sync_master_net_waits             | 6     |
| Rpl_semi_sync_master_no_times              | 1     |
| Rpl_semi_sync_master_no_tx                 | 1     |
| Rpl_semi_sync_master_status                | ON    |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 1120  |
| Rpl_semi_sync_master_tx_wait_time          | 4483  |
| 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     |
+--------------------------------------------+-------+
14 rows in set (0.00 sec)

Rpl_semi_sync_master_clients
當前半同步復制從的個數,如果是一主多從的架構,並不包含異步復制從的個數。

Rpl_semi_sync_master_no_tx
The number of commits that were not acknowledged successfully by a slave.
具體到上面的測試中,指的是insert into bobo.ceshi values(2)這個事務。

Rpl_semi_sync_master_yes_tx
The number of commits that were acknowledged successfully by a slave.
具體到上面的測試中,指的是以下四個事務:
mysql> create database bobo;
mysql> create table bobo.ceshi(id int);
mysql> insert into bobo.ceshi values(1);
mysql> insert into bobo.ceshi values(3); 

簡單總結:
1) 在一主多從的架構中,如果要開啟半同步復制,並不要求所有的從都是半同步復制。
2) MySQL 5.7極大的提升了半同步復制的性能。
    5.6版本的半同步復制,dump thread 承擔了兩份不同且又十分頻繁的任務:傳送binlog 給slave ,還需要等待slave反饋信息,而且這兩個任務是串行的,dump thread 必須等待 slave 返回之后才會傳送下一個 events 事務。dump thread 已然成為整個半同步提高性能的瓶頸。在高並發業務場景下,這樣的機制會影響數據庫整體的TPS 。
    5.7版本的半同步復制中,獨立出一個 ack collector thread ,專門用於接收slave 的反饋信息。這樣master 上有兩個線程獨立工作,可以同時發送binlog 到slave ,和接收slave的反饋。

                                                                           Mysql半同步模式配置示例                                                                           

mysql主數據庫: 172.16.60.205
mysql從數據庫: 172.16.60.206
mysql5.6.39 安裝部署,參考:https://www.cnblogs.com/kevingrace/p/6109679.html

主數據庫172.16.60.205配置:
[root@mysql-master ~]# cat /usr/local/mysql/my.cnf
.......
server-id=1
log-bin=mysql-bin
sync_binlog = 1
binlog_checksum = none
binlog_format = mixed

從數據庫172.16.60.206配置:
[root@mysql-slave ~]# cat /usr/local/mysql/my.cnf
.........
server-id=2
log-bin=mysql-bin
slave-skip-errors = all

然后從庫同步操作,不需要跟master_log_file 和 master_log_pos=120
mysql> change master to master_host = '172.16.60.205', master_port = 3306, master_user ='slave', master_password ='slave@123';

其他的配置,參考https://www.cnblogs.com/kevingrace/p/6256603.html
即主從同步配置不過濾庫,是基於整個數據庫的同步。其他的操作和這個文檔中記錄的差不多

========================================================
主數據庫
[root@mysql-master ~]# mysql -p123456
......
mysql> use kevin;
mysql> show tables;
+-----------------+
| Tables_in_kevin |
+-----------------+
| haha            |
+-----------------+
1 row in set (0.00 sec)
  
mysql> select * from haha;
+----+--------+
| id | name   |
+----+--------+
|  2 | anxun  |
|  3 | huoqiu |
|  4 | xihuju |
+----+--------+
3 rows in set (0.00 sec)
  
從數據庫:
[root@mysql-slave ~]# mysql -p123456
..........
..........
mysql> show slave status \G;
*************************** 1. row ***************************
               Slave_IO_State: Waiting for master to send event
                  Master_Host: 172.16.60.205
                  Master_User: slave
                  Master_Port: 3306
                Connect_Retry: 60
.........
.........
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
  
mysql> select * from kevin.haha;
+----+--------+
| id | name   |
+----+--------+
|  2 | anxun  |
|  3 | huoqiu |
|  4 | xihuju |
+----+--------+
3 rows in set (0.00 sec)
  
主數據庫插入數據
mysql> insert into haha values(1,"linux");
Query OK, 1 row affected (0.03 sec)
mysql> insert into haha values(11,"linux-ops");
Query OK, 1 row affected (0.04 sec)
  
從數據庫查看
mysql> select * from kevin.haha;
+----+-----------+
| id | name      |
+----+-----------+
|  1 | linux     |
|  2 | anxun     |
|  3 | huoqiu    |
|  4 | xihuju    |
| 11 | linux-ops |
+----+-----------+
5 rows in set (0.00 sec)
  
mysql> select version();
+------------+
| version()  |
+------------+
| 5.6.39-log |
+------------+
1 row in set (0.00 sec)
  
mysql> show variables like "have_dynamic_loading";
+----------------------+-------+
| Variable_name        | Value |
+----------------------+-------+
| have_dynamic_loading | YES   |
+----------------------+-------+
1 row in set (0.00 sec)
  
從上面可知,已滿足mysql半同步復制功能部署的條件:
1)MySQL 5.5及以上版本!
2)變量have_dynamic_loading為YES
3)主從復制已經存在!
  
===================================================
接下來進行mysql半同步復制環境部署:
  
1)加載mysql半同步復制的插件
主數據庫執行:
mysql> INSTALL PLUGIN rpl_semi_sync_master SONAME 'semisync_master.so';
Query OK, 0 row affected (0.04 sec)
  
從數據庫執行:
mysql> INSTALL PLUGIN rpl_semi_sync_slave SONAME 'semisync_slave.so';
Query OK, 0 row affected (0.04 sec)
  
2)查看插件是否加載成功的兩種方式:
主數據庫執行:
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS  WHERE PLUGIN_NAME LIKE '%semi%';
+----------------------+---------------+
| PLUGIN_NAME          | PLUGIN_STATUS |
+----------------------+---------------+
| rpl_semi_sync_master | ACTIVE        |
+----------------------+---------------+
1 row in set (0.00 sec)
  
從數據庫執行:
mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS  WHERE PLUGIN_NAME LIKE '%semi%';
+----------------------+---------------+
| PLUGIN_NAME          | PLUGIN_STATUS |
+----------------------+---------------+
| rpl_semi_sync_slave  | ACTIVE        |
+----------------------+---------------+
2 rows in set (0.01 sec)
  
3) 啟動半同步復制
主數據庫執行:
mysql> SET GLOBAL rpl_semi_sync_master_enabled = 1;
Query OK, 0 rows affected (0.00 sec)
  
從數據庫執行:
mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 1;
Query OK, 0 rows affected (0.00 sec)
  
----------------------------------------------------------------------------------------------------------------------------------------------
溫馨提示:
除了上面的設置方法以外, 還可以以下面方式啟動半同步復制,即在my.cnf文件中添加啟動配置(推薦這種方式):
主數據庫
[root@mysql-master ~]# vim /usr/local/mysql/my.cnf      #在[mysqld]區域添加下面內容
........
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=ON         #或者設置為"1",即開啟半同步復制功能
rpl-semi-sync-master-timeout=1000       #超時時間為1000ms,即1s
  
[root@mysql-master ~]# /etc/init.d/mysqld restart
  
主數據庫
[root@mysql-slave ~]# vim /usr/local/mysql/my.cnf
........
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=ON
  
[root@mysql-slave ~]# /etc/init.d/mysqld restart
----------------------------------------------------------------------------------------------------------------------------------------------
  
4)查看半同步是否在運行
主數據庫執行:
mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
1 row in set (0.00 sec)
  
從數據庫執行(此時可能還是OFF狀態,需要在下一步重啟IO線程后,從庫半同步狀態才會為ON):
mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)
 
5)重啟從數據庫上的IO線程
mysql> STOP SLAVE IO_THREAD;
Query OK, 0 rows affected (0.04 sec)
  
mysql> START SLAVE IO_THREAD;
Query OK, 0 rows affected (0.00 sec)
  
重啟從數據的IO線程之后,  slave會在master上注冊為半同步復制的slave角色
查看主數據庫上的error日志,就會發現下面信息:
[root@mysql-master ~]# tail -f /data/mysql/data/mysql-error.log
2019-01-06 23:23:34 10436 [Note] Semi-sync replication initialized for transactions.
2019-01-06 23:23:34 10436 [Note] Semi-sync replication enabled on the master.
2019-01-06 23:27:28 10436 [Note] Stop asynchronous binlog_dump to slave (server_id: 2)
2019-01-06 23:27:28 10436 [Note] Start semi-sync binlog_dump to slave (server_id: 2), pos(mysql-bin.000004, 2944)
2019-01-06 23:32:03 10436 [Note] Stop semi-sync binlog_dump to slave (server_id: 2)
2019-01-06 23:32:03 10436 [Note] Start semi-sync binlog_dump to slave (server_id: 2), pos(mysql-bin.000004, 3357)
  
如上操作,就已經部署了mysql的半同步復制環境
  
現在往主數據庫插入新數據
mysql> insert into kevin.haha values(5,"grace");
Query OK, 1 row affected (0.13 sec)
  
mysql> insert into kevin.haha values(6,"huihui");
Query OK, 1 row affected (0.11 sec)
  
到從數據庫上查看,正常復制過來了
  
mysql> show slave status \G;
.........
.........
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes
  
mysql> select * from kevin.haha;
+----+-----------+
| id | name      |
+----+-----------+
|  1 | linux     |
|  2 | anxun     |
|  3 | huoqiu    |
|  4 | xihuju    |
|  5 | grace     |
|  6 | huihui    |
| 11 | linux-ops |
+----+-----------+
7 rows in set (0.00 sec)
  
查看主數據庫
mysql> show status like '%Rpl_semi%';
+--------------------------------------------+-------+
| Variable_name                              | Value |
+--------------------------------------------+-------+
| Rpl_semi_sync_master_clients               | 1     |
| Rpl_semi_sync_master_net_avg_wait_time     | 475   |               #網絡等待的平均時間
| Rpl_semi_sync_master_net_wait_time         | 1427  |               #網絡等待時間    
| Rpl_semi_sync_master_net_waits             | 3     |
| Rpl_semi_sync_master_no_times              | 0     |
| Rpl_semi_sync_master_no_tx                 | 0     |               #大於0就是異步。半同步是應為0  
| Rpl_semi_sync_master_status                | ON    |
| Rpl_semi_sync_master_timefunc_failures     | 0     |
| Rpl_semi_sync_master_tx_avg_wait_time      | 622   |               # 平均等待時間
| Rpl_semi_sync_master_tx_wait_time          | 1868  |               #總的等待時間 
| 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     |               #大於0就是半同步模式
+--------------------------------------------+-------+
14 rows in set (0.00 sec)
 
==========================================================
現在做下測試:
當半同步復制發生超時(由rpl_semi_sync_master_timeout參數控制,單位是毫秒,默認為10000,即10s),會暫時關閉半同步復制,轉而使用異步復制。
當master dump線程發送完一個事務的所有事件之后,如果在rpl_semi_sync_master_timeout內,收到了從庫的響應,則主從又重新恢復為半同步復制。
 
mysql> show variables like "rpl_semi_sync_master_timeout";
+------------------------------+-------+
| Variable_name                | Value |
+------------------------------+-------+
| rpl_semi_sync_master_timeout | 10000 |
+------------------------------+-------+
1 row in set (0.00 sec)
 
關閉從數據庫的slave
mysql> stop slave;
Query OK, 0 rows affected (0.14 sec)
 
然后在主數據庫插入一條數據,發現半同步復制會發生超時
發生超時后,暫時關閉半同步復制,轉而使用異步復制
mysql> insert into kevin.haha values(55,"laolao");
Query OK, 1 row affected (10.13 sec)
 
mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | OFF   |
+-----------------------------+-------+
1 row in set (0.00 sec)
 
從數據庫也會關閉半同步
mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | OFF   |
+----------------------------+-------+
1 row in set (0.00 sec)
 
接着再打開從數據庫的slave
mysql> start slave; 
Query OK, 0 rows affected (0.04 sec)
 
然后主數據再插入一條數據,就會發現半同步復制不會超時,半同步復制功能打開也打開了
mysql> insert into kevin.haha values(555,"laolaolao");
Query OK, 1 row affected (0.04 sec)
 
mysql> select * from haha;
+-----+-----------+
| id  | name      |
+-----+-----------+
|   1 | linux     |
|   2 | anhui     |
|   3 | huoqiu    |
|   4 | xihuju    |
|   5 | grace     |
|   6 | huihui    |
|  11 | linux-ops |
|  55 | laolao    |
| 555 | laolaolao |
+-----+-----------+
9 rows in set (0.00 sec)
 
mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
1 row in set (0.00 sec)
 
查看從數據庫
mysql> select * from haha;
+-----+-----------+
| id  | name      |
+-----+-----------+
|   1 | linux     |
|   2 | anhui     |
|   3 | huoqiu    |
|   4 | xihuju    |
|   5 | grace     |
|   6 | huihui    |
|  11 | linux-ops |
|  55 | laolao    |
| 555 | laolaolao |
+-----+-----------+
9 rows in set (0.00 sec)
 
mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | ON    |
+----------------------------+-------+
1 row in set (0.00 sec)

通過上面的驗證可知,遇到半同步復制超時情況,就會自動降為異步工作。可以在Slave上停掉半同步協議,然后在Master上創建數據庫看一下能不能復制到Slave上。需要注意一點的是,當Slave開啟半同步后,或者當主從之間網絡延遲恢復正常的時候,半同步復制會自動從異步復制又轉為半同步復制,還是相當智能的。

                                                                          刪除"半同步復制", 恢復"異步復制"模式                                                  

先在從數據庫上關閉半同步復制功能,然后卸載半同步插件
mysql> stop slave;
Query OK, 0 rows affected (0.07 sec)

mysql> SET GLOBAL rpl_semi_sync_slave_enabled = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> show status like 'Rpl_semi_sync_slave_status';
+----------------------------+-------+
| Variable_name              | Value |
+----------------------------+-------+
| Rpl_semi_sync_slave_status | OFF   |
+----------------------------+-------+
1 row in set (0.00 sec)

mysql> UNINSTALL PLUGIN rpl_semi_sync_slave; 
Query OK, 0 rows affected (0.00 sec)

mysql> show status like 'Rpl_semi_sync_slave_status';
Empty set (0.00 sec)

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS  WHERE PLUGIN_NAME LIKE '%semi%';
Empty set (0.01 sec)

刪除my.cnf里面的半同步配置
[root@mysql-slave mysql]# vim /usr/local/mysql/my.cnf
#plugin-load=rpl_semi_sync_slave=semisync_slave.so
#rpl_semi_sync_slave_enabled=ON

一定要重啟mysql服務
[root@mysql-slave mysql]# /etc/init.d/mysql restart
Shutting down MySQL..                                      [  OK  ]
Starting MySQL..                                           [  OK  ]

=========================================================
接着再主數據庫關閉半同步復制功能,卸載半同步插件
mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | ON    |
+-----------------------------+-------+
1 row in set (0.00 sec)

mysql> SET GLOBAL rpl_semi_sync_master_enabled = 0;
Query OK, 0 rows affected (0.00 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
+-----------------------------+-------+
| Variable_name               | Value |
+-----------------------------+-------+
| Rpl_semi_sync_master_status | OFF   |
+-----------------------------+-------+
1 row in set (0.00 sec)

mysql> show status like '%Rpl_semi%';
+--------------------------------------------+--------+
| Variable_name                              | Value  |
+--------------------------------------------+--------+
| Rpl_semi_sync_master_clients               | 0      |                               #確保這一行的數值為0,即沒有半同步復制的客戶端
| Rpl_semi_sync_master_net_avg_wait_time     | 40186  |
| Rpl_semi_sync_master_net_wait_time         | 401860 |
| Rpl_semi_sync_master_net_waits             | 10     |
| Rpl_semi_sync_master_no_times              | 2      |
| Rpl_semi_sync_master_no_tx                 | 1      |
| Rpl_semi_sync_master_status                | OFF    |
| Rpl_semi_sync_master_timefunc_failures     | 0      |
| Rpl_semi_sync_master_tx_avg_wait_time      | 592    |
| Rpl_semi_sync_master_tx_wait_time          | 4737   |
| Rpl_semi_sync_master_tx_waits              | 8      |
| Rpl_semi_sync_master_wait_pos_backtraverse | 0      |
| Rpl_semi_sync_master_wait_sessions         | 0      |
| Rpl_semi_sync_master_yes_tx                | 8      |
+--------------------------------------------+--------+
14 rows in set (0.00 sec)

mysql> UNINSTALL PLUGIN rpl_semi_sync_master;
Query OK, 0 rows affected (0.00 sec)

mysql> SELECT PLUGIN_NAME, PLUGIN_STATUS FROM INFORMATION_SCHEMA.PLUGINS  WHERE PLUGIN_NAME LIKE '%semi%'; 
Empty set (0.00 sec)

mysql> show status like 'Rpl_semi_sync_master_status';
Empty set (0.00 sec)

刪除my.cnf里面的半同步配置
[root@mysql-master ~]# vim /usr/local/mysql/my.cnf 
#plugin-load=rpl_semi_sync_master=semisync_master.so
#rpl_semi_sync_master_enabled=ON
#rpl-semi-sync-master-timeout=1000

一定要重啟mysql服務
[root@mysql-master ~]# /etc/init.d/mysql restart
Shutting down MySQL...                                     [  OK  ]
Starting MySQL..                                           [  OK  ]

=========================================================
再接着重啟從數據庫上的IO線程
mysql> STOP SLAVE IO_THREAD;
Query OK, 0 rows affected (0.04 sec)

mysql> START SLAVE IO_THREAD;  
Query OK, 0 rows affected (0.00 sec)

=========================================================
通過上面操作,就完全刪掉了mysql半同步復制模式,恢復異步復制模式。
在關閉半同步復制模式的過程中,可以查看/data/mysql/data/mysql-error.log日志信息,從中觀察到半同步關閉的信息。

在從機重新start slave
mysql> start slave;
Query OK, 0 rows affected, 1 warning (0.00 sec)

mysql> show slave status \G;
.........
.........
             Slave_IO_Running: Yes
            Slave_SQL_Running: Yes

接着在主數據插入新數據
mysql> select * from kevin.haha;
+-----+-----------+
| id  | name      |
+-----+-----------+
|   1 | linux     |
|   2 | anhui     |
|   3 | huoqiu    |
|   4 | xihuju    |
|   5 | grace     |
|   6 | huihui    |
|  11 | linux-ops |
|  55 | laolao    |
| 555 | laolaolao |
+-----+-----------+
9 rows in set (0.00 sec)

mysql> insert into kevin.haha values(12,"wangjuan");
Query OK, 1 row affected (0.04 sec)

mysql> insert into kevin.haha values(13,"congcong");
Query OK, 1 row affected (0.04 sec)

去從庫上查看,發現已經同步過來了
mysql> select * from kevin.haha;
+-----+-----------+
| id  | name      |
+-----+-----------+
|   1 | linux     |
|   2 | anhui     |
|   3 | huoqiu    |
|   4 | xihuju    |
|   5 | grace     |
|   6 | huihui    |
|  11 | linux-ops |
|  12 | wangjuan |
|  13 | congcong |
|  55 | laolao    |
| 555 | laolaolao |
+-----+-----------+
9 rows in set (0.00 sec)

======================================================
溫馨提示:
經過幾次實驗,發現了一個坑,就是在做mysql半同步復制模式下,主從同步配置要是基於整個數據庫的同步,而不要使用"binlog_do_db"
和"binlog_ingore_db"進行過濾庫的同步配置,否則會造成半同步復制模式下的數據同步失敗。

之前做過一次實驗,mysql主從關系是針對某個具體庫進行配置同步的,即:
主數據庫的my.cnf配置:
#主從同步配置
server-id=1        
log-bin=mysql-bin   
binlog-do-db=kevin
binlog-ignore-db=mysql  
sync_binlog = 1    
binlog_checksum = none
binlog_format = mixed

# 半同步復制功能開啟
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=ON    
rpl-semi-sync-master-timeout=1000  

從數據庫的my.cnf配置:
#主從同步配置
server-id=2   
log-bin=mysql-bin   
replicate-do-db=kevin
replicate-ignore-db=mysql  
slave-skip-errors = all

# 半同步復制功能開啟
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=ON

然后從庫通過下面方式跟主數據庫同步
mysql> change  master to master_host='172.16.60.205,master_user='slave',master_password='slave@123',master_log_file='mysql-bin.000007',master_log_pos=120;

以上配置的mysql主從同步是針對kevin這個具體的庫的,在后續半同步復制模式下,主從數據同步失敗。
而且在這種情況下,刪除半同步復制模式配置,恢復到異步同步模式,主從數據同步還是失敗。

-----------------------------------------------------------
最后修改主從數據庫的my.cnf文件,按照下面方式進行主從同步,則半同步復制模式下,主從數據同步正常。
並且刪除半同步復制模式配置,恢復到異步同步模式,主從數據同步一樣正常。

主數據庫的my.cnf文件調整后的配置:
#主從同步配置
server-id=1
log-bin=mysql-bin
sync_binlog = 1
binlog_checksum = none
binlog_format = mixed

# 半同步復制功能開啟
plugin-load=rpl_semi_sync_master=semisync_master.so
rpl_semi_sync_master_enabled=1
rpl-semi-sync-master-timeout=1000

從數據庫的my.cnf文件調整后的配置:
#主從同步配置
server-id=2
log-bin=mysql-bin
slave-skip-errors = all

# 半同步復制功能開啟
plugin-load=rpl_semi_sync_slave=semisync_slave.so
rpl_semi_sync_slave_enabled=1
 
然后從庫通過下面方式跟主數據庫同步
mysql> change master to master_host = '172.16.60.205', master_port = 3306, master_user ='slave', master_password ='slave@123';

============================================================
特別注意下:
在做mysq主從同步時最好別過濾庫了,即最好進行基於整個數據庫的同步配置,同步命令為:
"change master to master_host = '主數據庫ip', master_port = 主數據庫mysql端口, master_user ='同步的用戶', master_password ='同步的密碼';"

使用過濾庫或過濾表的方式進行主從同步配置,后續會帶來一些比較麻煩的坑。
如果業務數比較多的情況下,就使用mysql多實例方式進行同步,一個業務一個mysql實例,主從同步配置中不要進行過濾庫或表的配置,即基於整個數據庫同步。

另外,在實際使用中還碰到一種情況從庫IO線程有延遲時,主庫會自動把半同步復制降為異步復制;當從庫IO延遲沒有時,主庫又會把異步復制升級為半同步復制。可以進行壓測模擬,但是此時查看Master的狀態跟上面直接關閉Slave半同步有些不同,會發現Rpl_semi_sync_master_clients仍然等於1,而Rpl_semi_sync_master_status等於OFF。隨着MySQL 5.7版本的發布,半同步復制技術升級為全新的Loss-less Semi-Synchronous Replication架構,其成熟度、數據一致性與執行效率得到顯著的提升。

                                                             MySQL 5.7半同步復制的改進                                                          
現在我們已經知道,在半同步環境下,主庫是在事務提交之后等待Slave ACK,所以才會有數據不一致問題。所以這個Slave ACK在什么時間去等待,也是一個很關鍵的問題了。因此MySQL針對半同步復制的問題,在5.7.2引入了Loss-less Semi-Synchronous,在調用binlog sync之后,engine層commit之前等待Slave ACK。這樣只有在確認Slave收到事務events后,事務才會提交。在commit之前等待Slave ACK,同時可以堆積事務,利於group commit,有利於提升性能。

MySQL 5.7安裝半同步模塊,命令如下:

mysql> install plugin rpl_semi_sync_master soname 'semisync_master.so';
Query OK, 0 rows affected (0.00 sec)

看一下相關狀態信息:

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 |
+-------------------------------------------+------------+
6 rows in set (0.00 sec)

支持無損復制(Loss-less Semi-Synchronous)
在Loss-less Semi-Synchronous模式下,master在調用binlog sync之后,engine層commit之前等待Slave ACK(需要收到至少一個Slave節點回復的ACK后)。這樣只有在確認Slave收到事務events后,master事務才會提交,然后把結果返回給客戶端。此時此事務才對其他事務可見。在這種模式下解決了after_commit模式帶來的幻讀和數據丟失問題,因為主庫沒有提交事務。但也會有個問題,假設主庫在存儲引擎提交之前掛了,那么很明顯這個事務是不成功的,但由於對應的Binlog已經做了Sync操作,從庫已經收到了這些Binlog,並且執行成功,相當於在從庫上多了數據,也算是有問題的,但多了數據,問題一般不算嚴重。這個問題可以這樣理解,作為MySQL,在沒辦法解決分布式數據一致性問題的情況下,它能保證的是不丟數據,多了數據總比丟數據要好。

無損復制其實就是對semi sync增加了rpl_semi_sync_master_wait_point參數,來控制半同步模式下主庫在返回給會話事務成功之前提交事務的方式。rpl_semi_sync_master_wait_point該參數有兩個值:AFTER_COMMIT和AFTER_SYNC

第一個值:AFTER_COMMIT(5.6默認值)

master將每個事務寫入binlog(sync_binlog=1),傳遞到slave刷新到磁盤(sync_relay=1),同時主庫提交事務。master等待slave反饋收到relay log,只有收到ACK后master才將commit OK結果反饋給客戶端。

性能提升, Binlog互斥鎖改進

舊版本半同步復制在主提交binlog的寫會話和dump thread讀binlog的操作都會對binlog添加互斥鎖,導致binlog文件的讀寫是串行化的,存在並發度的問題。

MySQL 5.7對binlog lock進行了以下兩方面優化:
-  移除了dump thread對binlog的互斥鎖。
-  加入了安全邊際保證binlog的讀安全。

 

從replication功能引入后,官方MySQL一直在不停完善,但也可以發現當前原生的MySQL主備復制的實現,實際上很難在滿足數據一致性前提下做到高可用高性能。

                                                                 參數sync_binlog/sync_relay與半同步復制                                                                                       

sync_binlog的配置
其實無損復制流程中也會存在着會導致主備數據不一致的情況,使主備同步失敗的情形。

當sync_binlog為0的時候,binlog sync磁盤由操作系統負責。當不為0的時候,其數值為定期sync磁盤的binlog commit group數。sync_binlog值不等於1的時候事務在FLUSH階段就傳輸binlog到從庫了,而值為1時,binlog同步操作是在SYNC階段后。當sync_binlog值大於1的時候,sync binlog操作可能並沒有使binlog落盤。如果沒有落盤,事務在提交前,Master掉電,然后恢復,那么這個時候該事務被回滾。但是Slave上可能已經收到了該事務的events並且執行,這個時候就會出現Slave事務比Master多的情況,主備同步會失敗。所以如果要保持主備一致,需要設置sync_binlog為1。

WAIT_AFTER_SYNC和WAIT_AFTER_COMMIT兩圖中Send Events的位置,也可能導致主備數據不一致,出現同步失敗的情形。實際在rpl_semi_sync_master_wait_point分析的圖中是sync binlog大於1的情況。流程如下圖所示。Master依次執行flush binlog, update binlog position, sync binlog。如果Master在update binlog position后,sync binlog前掉電,Master再次啟動后原事務就會被回滾。但可能出現Slave獲取到Events,這也會導致Slave數據比Master多,主備同步失敗。

由於上面的原因,sync_binlog設置為1的時候,MySQL會update binlog end pos after sync。流程如下圖所示。這時候,對於每一個事務都需要sync binlog,同時sync binlog和網絡發送events會是一個串行的過程,性能下降明顯。

在Slave的IO線程中get_sync_period獲得的是sync_relay_log的值,與sync_binlog對sync控制一樣。當sync_relay_log不是1的時候,semisync返回給Master的position可能沒有sync到磁盤。在gtid_mode下,在保證前面兩個配置正確的情況下,sync_relay_log不是1的時候,僅發生Master或Slave的一次Crash並不會發生數據丟失或者主備同步失敗情況。如果發生Slave沒有sync relay log,Master端事務提交,客戶端觀察到事務提交,然后Slave端Crash。這樣Slave端就會丟失掉已經回復Master ACK的事務events。

但當Slave再次啟動,如果沒有來得及從Master端同步丟失的事務Events,Master就Crash。這個時候,用戶訪問Slave就會發現數據丟失。

通過上面這個Case,MySQL semisync如果要保證任意時刻發生一台機器宕機都不丟失數據,需要同時設置sync_relay_log為1。對relay log的sync操作是在queue_event中,對每個event都要sync,所以sync_relay_log設置為1的時候,事務響應時間會受到影響,對於涉及數據比較多的事務延遲會增加很多。

MySQL三節點
在一主一從的主備semisync的數據一致性分析中放棄了高可用,當主備之間網絡抖動或者一台宕機的情況下停止提供服務。要做到高可用,很自然我們可以想到一主兩從,這樣解決某一網絡抖動或一台宕機時候的可用性問題。但是,前文敘述要保證數據一致性配置要求依然存在,即正常情況下的性能不會有改善。同時需要解決Master宕機時候,如何選取新主機的問題,如何避免多主的情形。

選取新主機時一定要讀取兩個從機,看哪一個從機有最新的日志,否則可能導致數據丟失。這樣的三節點方案就類似分布式Quorum機制,寫的時候需要保證寫成功三節點中的法定集合,確定新主的時候需要讀取法定集合。利用分布式一致性協議Paxos/Raft可以解決數據一致性問題,選主問題和多主問題,因此近些年,國內數據庫團隊大多實現了基於Paxos/Raft的三節點方案。MySQL官方后續也以插件形式引入了支持多主集群的Group Replication方案。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM