- 數據生態:MySQL復制技術與生產實踐
- 羅小波
- 5211字
- 2020-11-24 13:05:00
第5章 半同步復制
除了內置的異步復制,MySQL 5.7還支持通過插件方式實現半同步復制接口。相對于MySQL 5.5和MySQL 5.6中的半同步復制,通常我們將MySQL 5.7中的半同步復制稱為“增強半同步復制”,也稱為“無損復制”(MySQL 5.5和MySQL 5.6雖然也支持半同步復制,但不能保證“無損復制”,詳見5.4節“半同步復制的注意要點”)。本章將詳細介紹MySQL 5.7中的半同步復制。
5.1 半同步復制的原理
在MySQL中配置復制時,如果不額外加載其他任何與復制相關的插件,則默認為異步復制。主庫將事件寫入二進制日志,但不知道從庫是否接收成功,也不知道從庫什么時候重放二進制日志。如果主庫崩潰,則它已提交的事務可能尚未傳輸到任何從庫。在這種情況下,如果發生從主庫到從庫的故障轉移,可能導致主庫中還未來得及傳輸到從庫的那一部分事務丟失。
異步復制的示意圖如圖5-1所示(參考Oracle MySQL官方資料)。該圖描述了異步復制中主從庫數據同步的大致時間線,其中Master表示主庫,Slave1和Slave2表示主庫的兩個從庫。這里以自動提交的事務為例,因為MySQL適合運行一些“短、平、快”的事務,所以通常會啟用事務的自動提交,主庫發起事務提交,在execute(執行)階段執行完對數據的修改操作,然后在binlog(二進制日志)階段將修改數據所產生的二進制日志記錄寫入二進制日志文件中,在commit(提交)階段完成存儲引擎層的事務提交(事務的狀態修改為“提交”)。與此同時,主庫會通過Dump線程將二進制日志記錄發送給兩個從庫,兩個從庫收到后會寫入relay log(中繼日志)文件中。之后,兩個從庫各自讀取relay log文件中的內容進行apply(應用),即模擬事務在主庫中的提交方式來回放relay log。當這些事務在從庫提交時,也可能會寫入自己的binlog中(從庫啟用系統變量log_bin和log_slave_updates時才會寫入)。最后,兩個從庫在commit階段各自修改存儲引擎層中的事務狀態,結束事務。
由于主庫執行提交與發送二進制日志是異步的,也就是說,從庫是否成功接收二進制日志不影響主庫中的事務執行提交,因此可能會出現“主庫發生宕機,但主庫中已提交事務的二進制日志并沒有被任何從庫成功接收”的情況,即發生了數據丟失。

圖5-1
為了避免出現上述問題,MySQL對異步復制進行改進,引入了半同步復制。但半同步復制不是原生的內置功能模塊,要讓半同步復制功能正常工作,需要額外加載半同步復制功能模塊的插件。主庫端和從庫端需要安裝不同的插件庫文件,它們都包含在支持半同步復制的MySQL發行版中,不需要單獨下載。更多關于安裝與配置的信息可參考第15章“搭建半同步復制”。半同步復制處于正常工作狀態時,主庫提交的事務產生的二進制日志需要至少被一個從庫接收并寫入中繼日志,等返回的ACK消息被主庫成功接收之后,主庫才會確認事務已提交。正常情況下,主庫發生故障轉移時不會發生數據丟失。
半同步復制的示意圖如圖5-2所示(參考Oracle MySQL官方資料)。該圖描述了半同步復制中主從庫數據同步的大致時間線。從圖中我們可以看到,在binlog階段后commit階段前,主庫必須等待從庫在relay log階段之后回復的ACK消息。而從庫給主庫回復ACK消息之前,必須確保已經成功接收主庫的二進制日志記錄,并寫入中繼日志。這樣,當主庫發生故障時,主庫已提交的事務如果丟失,可以通過從庫的中繼日志恢復,避免在主庫發生故障時已提交的數據丟失[1]。

圖5-2
當主庫的某個會話提交事務發生阻塞時(在等待從庫的ACK消息),它不會返回給任何信息給執行事務的會話。當阻塞結束時(已經收到從庫的ACK消息或者等待ACK消息超時),主庫將事務的提交狀態返回給會話,并繼續執行其他事務。此時,事務在主庫中已經完成提交,而且確保至少有一個從庫確認收到這個事務的事件日志。主庫可以使用系統變量rpl_semi_sync_master_wait_for_slave_count(默認值為1)來設置需要收到多少個從庫發回的ACK消息,才能執行存儲引擎層的事務提交。
當一個修改非事務引擎表的語句發生回滾時,二進制日志仍然會被記錄下來,同時主庫發起操作的會話也會被阻塞,因為對非事務表的修改實際上是無法回滾的,對非事務表的修改必須發送到從庫,而且需要確保有至少有一個從庫接收了事件日志。
自動提交的每個語句,都會隱式開啟一個事務。在半同步復制中,每個自動提交的語句都會被阻塞,直到主庫至少接收一個從庫的ACK消息,阻塞才解除。
與異步復制相比,半同步復制提供了更好的數據完整性,因為當發起事務提交的會話收到提交成功返回的信息時,數據至少已經存在于兩個位置(主庫和返回ACK消息的從庫),在主庫未收到從庫的ACK消息之前,主庫中發起事務提交的事務處于阻塞狀態(事務未提交,處于等待ACK消息的狀態)。
在繁忙的Server上,半同步復制確實會對性能產生一些影響,由于需要等待從庫成功接收事件日志,所以在主庫中的事務提交速度變慢,但這是對提高數據完整性的一種權衡。等待的時長至少為主庫將提交事務的二進制日志發送到從庫,并等待從庫確認收到日志的TCP/IP協議通信包(ACK消息)的整個往返時間。這意味著半同步復制最適合在高速網絡中使用,不適合在低速網絡中使用。
系統變量rpl_semi_sync_master_wait_point控制在半同步復制中主庫返回事務提交狀態信息給提交事務的客戶端之前,等待從庫的ACK消息的點位。其有效值如下:
? AFTER_SYNC(默認值):主庫將每個事務寫入其二進制日志和從庫,并將二進制日志同步到磁盤。主庫在把二進制日志同步到磁盤之后等待從庫確認接收事務。收到從庫的ACK消息后,主庫將在存儲引擎層提交事務,并將提交結果返回給發起事務提交的客戶端,然后客戶端可以繼續做其他事情。
? AFTER_COMMIT:主庫將每個事務寫入其二進制日志和從庫,同步二進制日志到磁盤,并繼續在存儲引擎層執行事務的提交。提交事務后,主庫等待接收事務的從庫確認。收到從庫的ACK消息后,主庫將提交結果返回給客戶端,然后客戶端可以繼續做其他事情。
系統變量rpl_semi_sync_master_wait_point的不同值的特征如下:
? AFTER_SYNC:主庫未收到從庫的ACK消息之前,發起事務提交的會話不會收到事務的提交結果,存儲引擎層也不會執行提交。當主庫收到從庫的ACK消息之后,主庫在存儲引擎層執行該事務的提交,之后返回事務的提交結果給發起事務提交的客戶端。因此,所有客戶端在主庫上看到的數據都相同,如果主庫發生故障,則在主庫上提交的所有事務都已至少被復制到一個從庫中(從庫會確保接收的事務日志在中繼日志中已落盤)。主庫發生故障轉移之后數據是無損的。
? AFTER_COMMIT:主庫未收到從庫的ACK消息之前,發起事務提交的會話未收到事務的提交結果,但存儲引擎層會先執行提交。所以,在事務執行提交之后、未收到從庫的ACK消息之前,主庫中的其他客戶端可以查看到這個事務(只是發起事務提交的會話未收到事務提交的結果信息,但是由于在存儲引擎層中該事務已經提交,對于其他會話來說,該事務其實就是正常提交了)。如果在這種情況下主庫發生故障,主庫中已經提交的事務(可能是最后一個事務)的二進制日志有可能還沒來得及被從庫成功接收,那么主庫發生故障轉移之后,原來在主庫中能夠查詢到的事務,在業務切換到從庫之后可能在從庫中無法查詢到(發生數據丟失)。
“半同步復制”中的“半”是什么意思?
? 使用異步復制時,主庫將事件寫入其二進制日志,而從庫在準備就緒之后請求這些日志,并將其寫入自身的中繼日志。但該過程無法保證任何事件日志都能被從庫成功接收并寫入中繼日志。
? 使用完全同步復制,當主庫提交事務時,主庫必須等待所有從庫中的事務重放完(提交完)之后才能提交,這樣會導致在主庫中的寫事務存在大量的延遲提交,即大量事務被阻塞。
? 半同步復制介于異步復制和完全同步復制之間。主庫等待至少收到一個從庫的ACK消息即可,不需要等到所有從庫都收到二進制日志,并且主庫只需要等到從庫成功接收,不需要等待從庫完成重放,即事務提交。
對數據零丟失和數據一致性有一定要求的場景,可以用半同步復制替代異步復制:
? 當一個從庫使用半同步復制連接到主庫時,從庫會告知主庫自己是否具有半同步復制的能力,以便主庫決定是否需要使用半同步復制方式發送二進制日志給從庫。
? 如果主庫啟用了半同步復制,并且至少有一個保持半同步復制連接的從庫,則在主庫上執行事務提交時線程會等待,直到收到至少一個半同步從庫確認收到事務日志時返回的ACK消息(或者主庫在等待ACK消息時超時)。
? 從庫需要確保將收到的主庫二進制日志事件寫入其中繼日志并刷新到磁盤之后,才確認收到主庫的事務事件(即此時才會發送ACK消息給主庫)。
? 如果主庫在超時時間內沒有收到任何從庫的ACK消息,則主庫將切換為異步復制,直到至少有一個從庫恢復與主庫的半同步復制連接,主庫才重新切換為半同步復制。
? 要正確使用半同步復制,需要主從庫兩端都啟用半同步復制,如果在主庫上禁用了半同步復制,或者在從庫上禁用了半同步復制,則主庫將使用異步復制中的方式傳輸二進制日志。
提示:半同步復制的搭建步驟詳見第15章“搭建半同步復制”。更多信息可參考高鵬的“復制”專欄,登錄簡書網站搜索“第15節:MySQL層事務提交流程簡析”。
5.2 半同步復制的管理接口
半同步復制的管理接口指的是下面所述的插件、系統變量和狀態變量。兩個插件實現半同步功能,主庫和從庫各自安裝一個插件,主庫安裝semisync_master.so插件,從庫安裝semisync_slave.so插件。
系統變量控制插件的行為,如下所述:
? rpl_semi_sync_master_enabled:控制是否在主庫上啟用半同步復制。要啟用或禁用插件,將此變量設置為1或0即可,默認值為0(表示禁用插件)。
? rpl_semi_sync_master_timeout:一個以毫秒為單位的值,用于控制主庫在超時并切換到異步復制之前,等待從庫返回ACK消息的時間,默認值為10000(10秒)。
? rpl_semi_sync_slave_enabled:與rpl_semi_sync_master_enabled類似,但它控制從庫端插件semisync_slave.so的啟用或禁用。
狀態變量可以監控半同步復制的狀態,如下所述:
? rpl_semi_sync_master_clients:顯示半同步從庫的數量。
? rpl_semi_sync_master_status:顯示當前主庫中半同步復制插件是否處于啟用狀態。如果已啟用,則該值為1;如果未啟用,或者由于等待ACK消息超時(主庫已退回到異步復制),則該值為0。
? rpl_semi_sync_master_no_tx:從庫未成功確認(異步復制模式)事務的提交數。
? rpl_semi_sync_master_yes_tx:從庫成功確認(半同步復制模式)事務的提交數。
? rpl_semi_sync_slave_status:顯示當前從庫中的半同步復制插件是否處于啟用狀態。如果插件已啟用且從庫I/O線程正在運行,則此值為1,否則為0。
提示:只有當主庫和從庫中使用INSTALL PLUGIN語句安裝了相應的主庫插件或從庫插件時,半同步復制相關的系統變量和狀態變量在主/從庫中才可用。
5.3 半同步復制的監控
半同步復制插件提供了幾個系統變量和狀態變量,通過檢查這些變量,可以確定插件的配置是否正確、操作是否處于半同步復制或者異步復制狀態,關鍵的系統變量和狀態變量可參考5.2節“半同步復制的管理接口”。

當主庫提交事務等待從庫的ACK消息超時之后,半同步復制將回退為異步復制,或者從庫重新連接上主庫之后切換為半同步復制時,MySQL Server會相應地設置狀態變量rpl_semi_sync_master_status的值。當主庫從半同步復制自動回退到異步復制時(非人為修改rpl_semi_sync_master_enabled = 0導致),此時半同步復制實際上不可操作的,但系統變量rpl_semi_sync_master_enabled在主庫中的值仍然為1,即這個值一旦配置好,除非人為修改,始終為1。可以監控狀態變量rpl_semi_sync_master_status,當它為ON時,就表示半同步復制插件為啟用狀態,為OFF就表示為禁用狀態。
要在主庫中查看其連接了多少個半同步從庫,可以通過檢查狀態變量rpl_semi_sync_ master_clients值進行確認。
狀態變量rpl_semi_sync_master_yes_tx和rpl_semi_sync_master_no_tx表示通過半同步復制方式已成功或未成功提交的事務數。
在從庫端,狀態變量rpl_semi_sync_slave_status表示半同步復制插件的當前狀態。
5.4 半同步復制的注意要點
早期的半同步復制有個缺陷。在正常的半同步復制流程中,當客戶端對主庫發起一個事務提交之后,主庫發送二進制日志給從庫,從庫收到二進制日志并返回ACK消息,然后主庫返回事務提交成功的消息給發起提交的客戶端。這里對于發起事務提交的客戶端看起來沒有任何問題,但實際上在早期的半同步復制中,主庫在等待ACK消息的InnoDB存儲引擎內部已經提交事務,只是阻塞了返回給發起事務提交的客戶端的消息而已。此時,如果有其他會話對該事務修改的數據進行查詢,將會查詢到最新數據,參見圖5-3(該圖來自my-replication-life blogspot)。

圖5-3
該缺陷可能導致除發起事務提交的客戶端會話之外,其他的客戶端會話在碰到主庫故障轉移時發生幻讀,參見圖5-4(該圖來自my-replication-life blogspot)。User1發起一個INSERT操作,寫入一行數據,正在等待寫入成功并返回,此時User2就可以在主庫上查詢到User1插入的數據了。當主庫發生故障,寫業務切換到從庫,而從庫又沒有收到User1寫入的數據時,User1會收到事務寫入失敗的消息,但對于User2來說,之前在主庫上能查到的數據,切換到從庫之后卻查不到了,就好像發生幻讀一樣。

圖5-4
從MySQL 5.7開始,Oracle MySQL官方對半同步復制進行了增強,從字面上來看,增強半同步本質上就是對早期半同步復制中的缺陷進行了修補,而其原理與后者并無差別。那么,增強半同步復制在早期半同步復制的基礎上做了什么修改呢?
如圖5-5所示(該圖片來自my-replication-life blogspot),從左側底部方框標記的地方可以看到,Engine Commit步驟下沉到了最后,也就是說,在增強半同步復制中,一個事務在存儲引擎內部提交之前,必須先收到從庫的ACK消息,否則不進行事務最后的提交。這樣一來,其他客戶端會話在查詢數據時,所看到的數據就能夠和發起事務提交的客戶端會話保持一致,從而解決了主庫故障轉移之后可能出現的幻讀問題。

圖5-5
[1]注:半同步復制從原理上講,是可以避免數據丟失的,但在實際中還需要看從庫落盤相關的系統變量設置、磁盤設備相關的落盤參數設置,以及數據庫的生命管理周期設計是否合理等。
- Oracle WebLogic Server 12c:First Look
- Intel Galileo Essentials
- C語言程序設計(第3版)
- 自己動手實現Lua:虛擬機、編譯器和標準庫
- Python從入門到精通(精粹版)
- Access 2010數據庫基礎與應用項目式教程(第3版)
- Lua程序設計(第4版)
- 小程序,巧運營:微信小程序運營招式大全
- Python機器學習:手把手教你掌握150個精彩案例(微課視頻版)
- Python貝葉斯分析(第2版)
- Asynchronous Android Programming(Second Edition)
- 軟件品質之完美管理:實戰經典
- C語言程序設計
- Java高手是怎樣煉成的:原理、方法與實踐
- UI動效設計從入門到精通