Mysql半同步複製模式說明及配置示例 - 運維小結

散盡浮華發表於2019-01-06

 

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方案。

相關文章