MGR是MySQL官方推出的領先的服務高可用和數據高可靠方案,網易從2017年下半年開始對MGR進行了全面的性能和穩定性測試,發現並解決了數個問題,同時對MGR核心功能進行了優化增強,目前基於MGR的網易RDS金融版服務已經上線,為網易電商業務提供了跨機房的高可用方案。本次分享將為大家介紹網易對MGR的優化和增強,以及MGR在網易電商業務的使用和調優實踐


本文是在由IMG(Inside MySQL Group)社區主辦的第三屆MySQL技術嘉年華上所做的「網易MGR使用和優化實踐」PPT講稿。

大家好上午好,今天主要跟大家聊聊MySQL Group Replication,也就是MGR在網易杭研這邊的使用和優化情況。杭研這邊使用MGR的場景主要有這麼幾個,一種是用在業務系統上,一般是電商業務。另一種就是作為其他高可用或跨機房系統的元資料庫,這個很多業務場景都需要。

這次分享的內容包括本頁所述的3個部分。先是分析下MGR技術實現,簡單介紹MGR下的事務執行流程,重點分析事務排序和事務認證,這也是為第二部分做鋪墊;第二部分是本次分享的重點,將會分享我們在MGR使用時發現的一些不足以及我們的優化思路和效果;最後簡單說下網易杭研MGR的使用方案。

這個圖相信瞭解過MGR的同學都比較熟悉。應該說比較直觀得顯示了MGR與普通的MySQL複製模式的區別。主要是MGR模式下,事務完成引擎層prepare,寫Binlog之前會被MySQL的預埋鉤子HOOK before_commit()攔截,進入到MGR層,將事務封裝成消息通過Paxos一致性協議(consensus)進行全局排序後發送給MGR各個節點,在各節點上獨自進行認證(certifiy)。

認證通過後本地節點寫Binlog完成提交。其他節點寫relay-log,由註冊的複製通道group_replication_applier channel完成事務並行回放。

上頁中提到MGR模式下有個certify階段,就是事務認證或者叫衝突檢測的過程。這是因為MGR支持多主模式(multi-master),意味著允許各個節點都執行事務並在本地提交,這就存在潛在的問題,第一個問題是不同節點可能在同一時刻更新相同的記錄,第二個問題是交叉更新,比如節點A先更新了記錄1再更新記錄2,節點B先更新記錄2在更新記錄1,由於非本地事務是先寫到relay-log裡面,所以可能出現兩個事務分別更新第二條記錄時,第一條記錄的更新所對應的relay-log還沒有回放,也就是說讀取了第二條記錄的舊版本進行了更新操作。

這顯然是跟事務的ACID有衝突的。必須解決掉。那麼MGR就是通過certify階段解決。其實不僅僅是多主模式,在單主模式的新主切換過程中,也需要進行事務認證。

進行事務認證的核心是右圖所示的衝突檢測資料庫。其保存了記錄的當前版本信息(gtid_set欄位),即最後一次修改該記錄的事務提交後的gtid_executed。通過pk hash欄位來標識某條記錄。衝突檢測資料庫的sequence_number欄位並不用於事務認證,而是規定認證通過後的並行回放行為。

每個事務進行認證的時候都會攜帶執行該事務時節點的gtid_executed,也就是事務的版本snapshot_version,以及事務所修改的記錄信息writeset。通過這兩部分信息跟衝突檢測資料庫裏的信息進行比對,就能確定該事務應該提交還是回滾。

如果一個事務在各個節點都已經執行或回放了,那麼該事務的writeset信息就不需要繼續緩存在衝突檢測資料庫中。MGR會對其進行週期性清理。先廣播各自節點的gtid_executed信息,然後收集其他節點的gtid_executed信息取交集stable_gtid_set,再基於這個交集來清理無用的信息。

事務在MGR中進行認證前,會先進行全局排序,就是前述的consensus。在這個階段就需要把事務認證所需的所有信息都收集起來,封裝好後通過Paxos協議進行廣播。主要有3個部分的內容,第一部分是Transaction_context_log_event,它攜帶了事務認證所需信息,包括是否為本地事務,事務的快照版本,執行線程是否為worker線程,事務的gtid是否已確定(MGR作為其他MySQL實例的slave)、事務writeset等等。需要注意的是該log_event僅用於認證,不會寫入binlog文件中。接下來2個部分是我們比較熟悉的部分,在認證通過後會寫入Binlog文件中,首先是gtid_log_event,規定了事務的gtid,以及在回放時的組提交行為。接著就是真正的事務內容。

在後續的認證階段,這三部分會被一一處理。

封裝好後就會發給Paxos。為了支持各節點可寫的多寫模式,MGR底層採用了Paxos的變種mencius(也就是孟子,當然真正實現有點差別),每個Paxos節點或者說MySQL節點都是公平的,基於round robin的方式分配到對應的消息輪次。比如節點0分別佔據0,3,6。。。槽位,節點1佔據1,4,7。。。槽位,節點2佔據2,5,8。。等槽位。

這樣的話,各節點上執行的事務過了Paxos後就全局有序了。舉個例子。比如MGR有3個節點,同一時間分別執行了T1,T2,T3這3個事務,在Mencius中,T2所在MySQL對應節點0,那麼就佔了槽位0,T1的MySQL對應節點1,佔據槽位1。依次。最終排定了T1,T2和T3的全局順序。

但如果3個節點的事務提交頻率是不一樣的,那麼會出現什麼情況呢? 那些沒有事務提交或提交較少的節點對應的消息輪次/槽位會被noop/null所取代。表示該輪次沒有需要達成majority的數據,填空即可。但怎麼知道對應消息輪次為空呢,這個實現起來比較麻煩。其他節點會嘗試通過READ操作來讀取,輪次所屬的節點發現本輪次為空,那麼發送noop,但如果是因為事務消息過大,導致達成majority很慢,其他節點多次通過READ操作無法獲取到,又或者是由於網路延遲較高/網路分區,導致多次READ無法成功,那麼其他節點會嘗試廣播prepare noop來協商該輪次為空值。這是一個潛在的問題。

這頁的圖是事務認證的實現方案。基於pipeline機制,一共有3個pipeline,分別是event_cataloger,certification_handler,applier_handler。事務消息中的3部分log event依次進入pipeline。其中最核心的是certification_handler處理gtid_log_event這個環節,決定了事務是否認證通過,以及通過後如果分配gtid,如何確定非本地節點事務的並行回放行為等。下面展開來說下這一步如何進行認證。

假設T2事務的快照版本是1-100,更新的記錄是1,查看衝突檢測資料庫中1對應的版本是1-50,也就是說T2在執行的時候,節點已經執行或回放了之前所有對記錄1的修改操作。因此T2得已認證通過。認證通過後需要為T2分別gtid,這裡我們先確定分配的是201,他會被更新到衝突檢測資料庫1的gtid_set上。那麼201是怎麼確定的呢。需要看下圖。

MGR會為每個節點預留一段連續的gtid區間,Available gtid_set表示給當前各個節點預留的可用gtid。比如當前為Server2預留的是201到300,由於T2屬於Server2,所以分配目前可用的最小gtid,也就是201。這樣就保證了多寫模式下,給不同節點事務分配的gtid是不一樣的。

確定了gtid後,對於非本地事務,需要寫到relay-log後回放。那麼在寫入relay-log文件前,需要確定並行回放行為,即last_committed和sequence_number。

再次以T2為例,首先說sequence_number,這是並行複製的邏輯時鐘,根據事務提交先後次序遞增。待分配的sequence_number由全局變數parallel_applier_sequence_number保存,那麼T2的sequence_number即為387。parallel_applier_sequence_number遞增為388。

接下來確定last_committed,我們在前面在介紹過,並行回放時的組提交行為依賴於衝突檢測資料庫,每條記錄除了保存了對應的事務版本外,還保持了最後一個事務的sequence_number。這裡是120,那麼T2這個事務依賴於120這個事務,必須等待120這個事務回放完之後才能回放,也就是說T2的last_committed初始化為120。最後,將記錄1對應的sequence_number更新為387。

這樣,MGR在事務處理過程中需做的事情基本上處理完了。Relay-log中的事務交由worker線程回放即可。

接下來分享下MGR在考拉業務場景上使用時遇到的問題,由於時間有限,只重點說明影響最大的2個案例,分別是事務認證模塊、Paxos模塊的不足和優化。還有很多問題的定位和優化就只能簡單列舉下。

我們前面提到,MGR會每隔60s廣播本節點的gtid_executed信息,各節點取交集後清理衝突檢測資料庫信息。但在網易電商場景下,這樣的機制會有明顯的性能波動,如下圖所示,非常規律的每分鐘一次性能波動,延遲升高,tps降低。我們分析發現,這個跟衝突檢測資料庫清理有關係。因為上面累計了太多的writeset,而執行清理的時候需要加鎖遍歷整個衝突檢測資料庫,這就跟事務的認證流程的加鎖起了衝突,導致事務性能下降,延遲提升。另外,在某些較極端的情況下,比如流控參數設置較大,或每個事務writeset較多,或gtid_set較大時。writeset清理不及時也容易導致衝突檢測資料庫佔據過多內存空間,導致mysqld OOM。

基於此,我們做了些優化。比如將gtid_executed廣播週期變為動態可調的參數,默認設置為20s;將事務產生的writeset個數減半;將writeset個數納入MGR流控模塊管理。這樣可以有效降低衝突檢測資料庫中的writeset個數。

但在節點間複製延遲較大的情況下,衝突檢測資料庫仍可能比較大。針對這個,我們在單主模式下做了進一步優化,就是writeset清理不依賴於gtid_executed交集。而是採用writeset個數達到閾值直接清掉。因為單主模式不會有事務衝突,衝突檢測資料庫並不是發揮事務認證作用,而僅僅用於決定事務的並行回放行為。這個優化還報錯將gtid_set欄位置空,來進一步減少內存消耗。

右圖是優化前後的性能對比。可以發現,性能波動大大改善。平均性能得到提升。而且內存也得到了有效控制。

除了事務認證模塊,也對MGR底層的Paxos模塊進行了優化改進。這主要是為瞭解決大量數據導入場景下的問題,包括Primary節點OOM,或者有個Secondary節點由於無法獲取paxos消息導致mysqld自動退出問題。

如右圖所示,使用NDC往MGR導入數據,設置為10個並發,每個事務為200條記錄組成的batch,事務大小在30MB左右。導入過程中,內存急劇增漲。

我們分析發現原因一是paxos cache過大,佔據太多內存;二是paxos消息發送和接受環節未考慮高網路延遲情況。

這個2個方面原因展開來說:

首先是paxos cache,目前社區版的cache大小閾值1G,且不可調(8.0最新版本已經可調),但其實無法將cache大小嚴格控制在1G左右。主要是因為達到大小閾值後,cache對象是否能被清理需要看其他節點是否還需要該cache對象,另外達到cache大小閾值後,新的cache對象還會從空閑隊列分配而不是通過LRU替換當前cache對象。

第二個原因是paxos協議實現導致的內存增漲,主要是由於大事務在網路中低效率傳輸導致網路擁塞。由於網路擁塞,未參與majority的節點嘗試獲取消息時無法及時獲取對應消息輪次節點返回的消息,就會不斷進行重試,但其實此時對應輪次節點正在回復消息數據,只是因為事務較大,網路較差,導致傳輸速度太慢。如果不斷重試,會不斷惡化這個情況。節點在回復消息數據時會拷貝一份消息數據。也就是30MB,那麼如果每個消息都重複多次,顯然會迅速消耗大量內存。

這是Paxos層優化後的內存使用情況,可以看到,已經變得非常平穩。而且其實性能並沒有什麼損耗。

除了上面這些大點外,我們也從運維角度進行了優化,比如將8.0版本的一些特性遷移到5.7上。還有就是對故障恢復和選主等環節進行優化,比如選擇gtid_executed最新的節點作為Primary節點。節點加入集羣時優先從其他secondary節點獲取數據。等等。時間關係,不展開介紹。

這是我們上報的MGR模塊,或者跟MGR相關的一些官方bug。有MGR選主和故障恢復方向的,也有跟複製或者XA事務相關的。其中有些非常嚴重的問題,比如數據不一致問題,複製中斷問題,只讀開關開啟情況下還能提交事務等等。這些問題的發現和修復,為我們業務能夠使用MGR打下了基礎。目前上面的絕大多數問題MySQL官方都已經在新版本上修復了。

第三部分簡單說下MGR使用方案。包括MGR如何與網易雲RDS服務配合實現金融級高可用實例,如何實現考拉的同城跨機房,如何與DDB配合來減低硬體成本等。

首先是MGR在網易雲RDS上的使用。網易雲RDS支撐了網易杭研95%以上的業務,是網易內部使用MySQL的主要平臺。相比自建MySQL,網易雲RDS有非常多的優勢。目前網易雲RDS已經基於MGR推出3節點的金融版實例。相比主從複製架構的RDS高可用實例,基於MGR的RDS金融版實例,在數據可用性和可靠性上又有了進一步提升。引入MGR後,RDS Agent和管控的邏輯需要針對性得進行適配,這裡主要介紹MGR在故障時的路由切換和故障恢復。

MGR會自己選主,但無法將MySQL訪問路由從舊主切換到新主,這個是需要RDS做的。RDS可以通過監測主節點心跳來發現節點宕機等行為。然後通過各個節點上報的心跳信息來感知view視圖變更和新選出來的主是哪個節點。等新主的認證隊列和回放隊列均為空,relay-log回放完之後,將網路路由切換到新主上。

對於故障的節點,會嘗試重啟,如果無法重啟,那麼RDS需要用xtrabackup基於MGR其他節點做個全量物理備份,重新加入MGR。如果重啟成功,重新加入MGR後節點會首先回放本地applier通道上的relay-log,然後連上其他節點使用recovery通道獲取binlog並回放,如果此時其他節點binlog已經被purge,那麼也需要進行全量重建。

接下來介紹下MGR在考拉跨機房上的使用,目前的部署模式是同城跨機房基於MGR,跨城的多機房使用非同步複製進行。

為了提高同城2機房模式下數據的可用性和可靠性。我們將Primary節點放在1個機房,2個Secondary節點放在另一個機房。在2機房架構下,只有將Primary和Secondary隔開才能確保任意一個機房掛掉,數據不會丟失。因為如果Primary跟任意一個Secondary在一個機房的話,就可能在機房內部對一個事務達成majority,另一個機房的secondary可能還未收到數據。這種情況下,Primary所在機房宕機時就可能導致數據丟失。

還有,在跨機房場景下,不可能通過切換ip來調整業務的路由。所以設計了基於ETCD的DNS切換方案,一個機房故障的情況下,另一個機房還能跟第三個機房實現majority,因此還能夠修改DNS地址,實現業務訪問的跨機房高可用。

目前某電商業務的測試環境均已經升級為基於MGR的RDS金融版實例。線上也有小規模使用,目前正在逐步將線上RDS高可用實例替換為基於MGR的RDS金融版實例。

由於時間關係,PPT中所述的內容並不是每塊都講得很詳細,大家有興趣的話,可以關注我們在知乎上的專欄-資料庫內核,來獲取進一步得資料。

推薦閱讀:

相關文章