MySQL MGR成員管理與故障恢復實現
來自專欄資料庫內核
MySQL Group Replication(MGR)框架讓MySQL具備了自動主從切換和故障恢復能力,舉single primary(單主)模式為例,primary作為主節點對外提供讀寫服務,是唯一的可寫節點,其他節點均為secondary節點,可提供讀服務。在傳統的master-slave主從複製模式下,如果master發生了crash,MySQL DBA需要手動將slave升級為新master(比如關閉只讀開關等),舊的master重啟後需執行change master to進行複製關係重建,並執行start slave開啟複製。如果是semi-sync半同步複製,還需要進行半同步參數配置。但在MGR模式下MySQL能自動發現primary crash,通過選主產生新的primary節點對外提供讀寫服務。舊的primary節點重啟後,DBA只需要執行start group_replication即可將crash節點重新加入到Group中,在運維便利性和系統健壯性上有極大的提示。本文分為2個部分,先分析MGR的成員管理機制,講解crash的節點如何重新加入到Group中。然後再分析該節點加入Group後如何進行故障恢復使其自身狀態從Recovery變為ONLINE。
成員管理
視圖及視圖切換
MGR以組視圖(Group View,簡稱視圖)為基礎來進行成員管理。視圖指Group在一段時間內的成員狀態,如果在這段時間內沒有成員變化,也就是說沒有成員加入或退出,則這段連續的時間為一個視圖,如果發生了成員加入或退出變化,則視圖也就發生了變化,MGR使用視圖ID(View ID)來跟蹤視圖的變化並區分視圖的先後時間。在進一步介紹之前,我們先看一個圖:
Group當前的視圖ViewID為83973:2,該視圖中包括2個成員,分別為DB1和DB2;一段時間後DB3節點請求加入Group,成功加入後視圖切換為83973:3,包括3個成員,分別為DB1、DB2和DB3;之後DB2節點crash而退出了Group,視圖切換為83973:4;之後DB4加入Group,視圖切換為83973:5。從上面的圖例我們可以發現,ViewID由2部分組成,分別如下。
前綴部分:是在這個Group初始化時產生,為當時的時間戳,Group存活期間該值不會發生變化。所以,該欄位可用於區分2個視圖是否為同一個Group的不同時間點;
序號部分:Group初始化時,第一個視圖的序號從1開始,其成員只有1個,為進行初始化的節點。以後Group出現任何成員的加入或退出序號都需要增一。
上面所述的是同個Group的視圖的變化情況,這裡再補充說明下Group定義。一個組Group是在節點參數為group_replication_bootstrap_group為on的條件下執行start group_replication產生的,如果要加入現有的Group,節點需要確保group_replication_bootstrap_group為off。一個節點加入Group後,需要連到其他節點使自己的數據uptodate,如果該Group其他成員都已不在線,那麼就無法進行uptodate,在這種情況下,就需要設置group_replication_bootstrap_group為on來初始化新的Group。
Group的當前視圖可以通過performance_schema系統庫下的replication_group_member_stats表中查到,下圖表示該Group初始化時間為2018/2/7 14:54:13,已經發生了321次視圖切換:
node1>select * from replication_group_member_stats limit 1G
*************************** 1. row ***************************
CHANNEL_NAME: group_replication_applier
VIEW_ID: 15179864535059527:321
MEMBER_ID: 2e7b6e78-0bd3-11e8-9ba2-c81f66e48c6e
COUNT_TRANSACTIONS_IN_QUEUE: 0
COUNT_TRANSACTIONS_CHECKED: 382502
COUNT_CONFLICTS_DETECTED: 0
COUNT_TRANSACTIONS_ROWS_VALIDATING: 340084
TRANSACTIONS_COMMITTED_ALL_MEMBERS: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:1-117137653:117596712-117597349
LAST_CONFLICT_FREE_TRANSACTION: aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa:117273119
COUNT_TRANSACTIONS_REMOTE_IN_APPLIER_QUEUE: 25586
COUNT_TRANSACTIONS_REMOTE_APPLIED: 356916
COUNT_TRANSACTIONS_LOCAL_PROPOSED: 0
COUNT_TRANSACTIONS_LOCAL_ROLLBACK: 0
1 row in set (0.00 sec)
視圖切換實現
上面我們簡單一個節點加入Group後,需要連到其他節點使自己數據uptodate,那麼基於什麼來判斷自己的數據uptodate了呢。下面我們詳細介紹一個節點如何加入到Group中,用圖說話,如下:
一個節點請求加入Group時,其首先會根據配置的group_replication_group_seeds參數跟Group的種子成員建立TCP連接 (Gcs_xcom_control::do_join())。該種子成員會根據自己的group_replication_ip_whitelist(ip白名單)檢查是否允許新節點加入,MGR默認不限制新節點的ip。連接建立後,新節點發送請求申請加入組,如上圖左上所示;收到請求後,種子成員廣播視圖變化的消息給Group中的所有節點,包括申請加入的節點,如右上所示;各節點收到消息後開始做視圖切換。每個節點都會廣播一個狀態交換消息,每個交換消息包含了節點的當前狀態和信息,如圖左下所示。發送了交換消息後,各個節點開始接收其他節點廣播的消息,將其中的節點信息更新到本節點所維護的成員列表中。狀態交換消息像事務數據包一樣走相同的Paxos(Mencius)協議進行發送,當收到所有成員的狀態交換消息時,通信模塊將完整的新視圖封裝為視圖數據包(view_change_log_event,即vcle的前身,但沒有資料庫當前的快照版本信息),Group中的每個節點基於MGR的通信模塊將其返回給全局事務認證模塊的消息隊列(簡稱認證隊列)。至此,視圖切換即告結束。視圖切換過程中不會影響在線成員對外服務。
視圖數據包的作用
完成視圖切換隻是成員加入Group要做的第一步,只是說明該成員可以接收到Group中通過Paxos協議達成共識的消息,並不意味著可以將成員設置為ONLINE(上線)對外提供服務。原因是新成員還需要進行數據同步,建立起正確的數據版本(recovery_module->start_recovery)。之後才能執行Paxos協議消息,進而上線提供正常的用戶訪問服務。
在這過程中,視圖數據包發揮了關鍵性的作用。如上所述,視圖數據包通過Paxos放入消息隊列,那麼各節點依次處理消息隊列中的數據包時就能夠發現視圖數據包(Plugin_gcs_events_handler::on_view_changed),將視圖數據包封裝為view_change_log_event後寫入Relay log文件中,group_replication_applier通道的並行複製線程讀取Relay log中的vcle並將其寫入到Binlog文件中。這樣,Binlog文件中的事務就被vcle劃分為多個區間,每個區間都代表一個視圖。vcle中包括了ViewID和用於進行全局事務認證模塊進行事務認證的衝突檢測資料庫。基於vcle,我們可以把節點上線前的過程劃分為2個階段,分為前一個視圖數據恢復和本視圖緩存事務執行。第一個階段又可以細分為本地恢復和全局恢復。下面分析這三個子過程的具體實現。
故障恢復實現
在開始介紹前,需簡單介紹下MGR所涉及的數據複製及其通道(channel),MGR正常運行時,即Group中各節點均在線,節點間通過Paxos協議傳輸數據,異地事務經過認證後寫入group_replication_applier Relay log中,由group_replication_applier通道的複製線程負責回放。當有節點加入Group時,需要用到另一個複製通道group_replication_recovery,它是一個傳統的Master-Slave非同步複製通道。
MGR故障恢復代碼位於MGR代碼目錄(rapidplugingroup_replicationsrc)的http://Recovery.cc文件下,節點的故障恢復交由單獨的恢複線程(launch_handler_thread -> recovery_thread_handle)執行。
本地恢復階段
第一步:進行故障恢復初始化,包括故障恢複線程初始化,Group成員信息初始化等。
第二步:啟動group_replication_applier複製通道。對於故障恢復場景,待加入的成員不是新節點,也就是說其曾經加入過該節點,其group_replication_applier通道的Relay log可能還有一些Binlog未回放。所以需要先將這部分Relay log回放掉。由於有後面所述對的第三步,非Group初始化場景下,其實可以跳過回放本地恢復的環節,直接從其他節點複製所需的數據,但引入本地恢復的好處在於可提高恢復性能,無需在拉取這部分Binlog,提高恢復過程健壯性,減小所需Binlog被purge的風險。
確認對應通道的複製線程空閑後,阻塞(suspend)認證隊列的處理線程,即允許新的Paxos消息入隊但不進行處理。這樣,本視圖的Paxos消息會在認證隊列中堆積起來。
若該節點是Group的第一個節點,即Group初始化場景,則故障恢復結束,可將此節點置為在線狀態。
全局恢復階段
第三步:完成本地Relay log回放後,進入故障恢復第三步,即State transfer是通過group_replication_recovery複製通道從Group其他在線節點拉取本節點欠缺的非本視圖的數據。與傳統的Master-Slave複製配置不同,MGR中僅需為其配置賬號和密碼即可,配置方式形如:CHANGE MASTER TO MASTER_USER=rpl_user, MASTER_PASSWORD=rpl_pass FOR CHANNEL group_replication_recovery。顯然,該通道是基於GTID以MASTER_AUTO_POSITION的方式進行。恢複線程隨機選擇一個在線節點(donor),調用http://rpl_slave.cc中的request_dump函數建立與donor的複製關係,該函數攜帶了恢復節點的gtid_executed信息。donor端會逆序遍歷其Binlog文件,通過判斷Binlog文件起始的Previous-GTIDs來找到第一個不屬於gtid_executed的事務,從該事務開始進行數據複製。
MGR實現了全局恢復階段的容錯能力,能夠處理非同步複製過程中出現的絕大部分情況:
recovery_aborted:表示主動退出故障恢復過程。
on_failover:表示donor節點離開了Group,故障恢複線程選擇另一個在線節點作為donor,連上後繼續進行非同步複製。該場景下,從退出的donor節點複製而來未回放的Relay log不會purge,而是完成回放後再繼續從當前donor進行複製。
donor_channel_applier_error:表示本節點回放從donor節點複製過來的事務時出錯,故障恢複線程也會選擇另一個節點作為donor繼續進行數據複製,但與on_failover不同的是,該場景下,未回放的Relay log被purge,從出錯事務位置開始從新donor節點重新拉取Binlog進行嘗試。
donor_transfer_finished:這是正常的情況,表示全局恢復已經完成了所需數據複製。可進入到緩存事務執行階段。那麼如何獲知已經複製完所需數據呢,這就用到了我們前述的vcle,複製vcle後,會判斷其攜帶的ViewID,如果其與節點當前的ViewID同等,就表示非同步複製可以停止了。在開始下一步處理前,還故障恢複線程還會基於vcle初始化認證所需的衝突檢查資料庫。
除了上述情況外,還可能出現所選的donor無法連接(比如配置了IP白名單)或所需的Binlog已經被purge的情況。對於該情況,故障恢複線程會選擇其他donor進行重試,可通過參數group_replication_recovery_reconnect_interval和group_replication_recovery_retry_count對重試行為進行控制,第一個參數表示重試的間隔時間,默認為1分鐘,第二個參數為重試的次數, 默認為10次。
緩存事務執行
第四步:先喚醒在第二步被阻塞的認證隊列處理線程,讓本節點逐步跟上Group中的其他節點,進而將其設置為在線(ONLINE)。那麼,應該在什麼時候允許節點上線呢,MGR引入參數group_replication_recovery_complete_at進行控制,可選TRANSACTIONS_CERTIFIED或TRANSACTIONS_APPLIED。分別表示認證隊列為空或回放完所有已認證的事務時。雖然判斷條件很明確,但實際場景下代碼實現卻較為困難,因為負載可能是持續的,狀態是動態變化,不會存在靜態的事務均已認證或均已回放的狀態。
對於單主模式,在開始處理認證隊列之前,需要先將衝突檢測標誌位開啟,這樣在緩存的隊列請求時就會將事務攜帶的快照版本跟衝突檢測資料庫版本進行對比,決定事務是回滾還是提交。此時開啟衝突檢測的原因是緩存的事務有部分是在primary節點回放之前視圖產生的Relay log過程時執行的,也就是說,事務執行的時候,其基於的數據並不是最新的數據,所以可能會被回滾,如果未開啟衝突檢測就會導致數據不一致。這裡需要說明一點,就是無論是否開啟衝突檢測,經過Paxos發送的消息都會進入認證隊列,然後依次進行處理。代碼實現上,衝突檢測只是認證的一個環節,除此之外,還需要為事務確定gtid,並確保相同的事務在各個節點產生的gtid是相同的。
成員上線與出錯處理
第五步:在達到了group_replication_recovery_complete_at所確定的條件後,發送Recovery_message::RECOVERY_END_MESSAGE消息,通知Group中的各個節點,將該節點設置為在線狀態。
第六步:在故障恢復全過程中,若遇到了無法繼續的情況或錯誤,比如recovery_aborted被置位或恢復所需的數據在其他節點均已被Purge或等待上線時回放出錯等,則先將該節點置為ERROR狀態,確認該節點複製相關線程退出了,節點向Group發送leave信息退出Group,而不是置為ERROR狀態候仍留在Group中。這是跟MongoDB Replica Set等其他多節點資料庫系統不一樣的地方。
第七步:不管是否出錯,均需要重置故障恢復相關的參數,並銷毀故障恢複線程。
MGR故障恢復實現的不足
一、隨機選擇donor節點:早期的MGR版本按照節點設置group_replication_group_seeds參數中的種子節點順序來選擇donor節點,若各節點的參數配置一致,則存在同一個節點被反覆選擇為donor節點,會導致該節點負載過大而出現複製延遲等問題,不利Group中各節點負載的均衡。當前的MGR版本已將其調整為隨機選擇donor,有了很大進步,但仍可以進一步優化,就是存在多個可選的節點情況下,單主模式先排除primary節點,多主模式下排除本地負載壓力最大的節點;
二、本視圖事務緩存機制:MGR在節點進行全局恢復時,將本視圖產生的消息緩存在認證隊列中,若節點落後較多,需較長時間完成全局恢復,若Group的負載較高,會在認證隊列中堆積大量的消息,這些消息位於內存中,容易出現因為內存不足而OOM。可考慮的優化方案是基於一定的策略將緩存的消息臨時寫到系統表中,將壓力轉移到磁碟上。
三、Binlog被purge處理:相比Percona基於Galera封裝的Percona XtraDB Cluster(PXC),MongoDB Replica Set(ReplSet)方案,MGR目前還未提供節點全量恢復方案。即可往Group中加入一個全新的節點,由MGR來負責從其他節點拷貝全量數據(State Snapshot Transfer,SST),並通過增量複製(Incremental State transfers,IST)直至將節點置為上線狀態。PXC是個解決方案套件,採用xtrabackup來實現SST,ReplSet是進程,採用邏輯拷貝的方式實現Initial Sync。MGR可參考ReplSet來實現。
四、成員類型單一:MGR目前僅提供Primary和Secondary兩種成員類型,不支持Arbiter,Hidden等類型,也不支持像MongoDB和X-Cluster等其他資料庫系統設置豐富的成員屬性。
參考資料:
1、 http://mysqlhighavailability.com/distributed-recovery-behind-the-scenes/
2、https://mysqlhighavailability.com/improvements-and-changes-to-group-replication-recovery/
3、MySQL 5.7.20、MySQL 8.0.4源碼
4、MySQL運維內參 27章MySQL Group Replication
5、https://www.percona.com/blog/2015/08/05/pxc-incremental-state-transfers-in-detail/
6、https://www.percona.com/doc/percona-xtradb-cluster/5.5/manual/state_snapshot_transfer.html
7、https://docs.mongodb.com/manual/core/replica-set-sync/#initial-sync
推薦閱讀:
※如何理解Innodb 的文件物理結構?
※mysql中sql使用場景及應用技巧
※MySQL兩種引擎的區別
※系統優化怎麼做-資料庫優化
※親手為你建立sql面試題案例數據
TAG:MySQL |