架構系列---餓了麼MySQL異地多活的資料雙向複製

FeelTouch發表於2019-04-09

陳永庭,餓了麼框架工具部高階架構師,主要負責MySQL異地雙向資料複製,支撐餓了麼異地多活專案。曾就職於WebEx、Cisco、騰訊等公司。

今天我主要分享餓了麼多活的底層資料實施,會和大家介紹在整個多活的設計和實施過程中我們是怎麼處理異地資料同步的,而這個資料同步元件在我們公司內部稱之為DRC。

異地多活背景

在講DRC或者講資料複製之前,先跟大家回顧一下異地多活的背景。

去年我們在做多活調研的時候,整個公司所有的業務服務都是部署在北京機房,伺服器大概有四千多臺,災備的機器是在雲端,都是虛擬機器,大概有三千多臺。當時我們峰值的業務訂單數量已經接近了千萬級別,但是基本上北京機房(IDC)已經無法再擴容了,也就是說我們沒有空餘的機架,沒有辦法新增新的伺服器了,必須要再建一個新的機房,於是我們在上海建一個新的機房,上海機房要在今年的4月份才會投入使用,所以需要在上海機房建成之後,異地多活專案能具備在生產環境上進行灰度。

異地多活的底層資料同步實施

這是異地多活的底層資料同步實施的一個簡單的概要圖,大家可以看到,我們有兩個機房,一個是北京機房,一個是上海機房。在這個時候,我們期望目標是北方所有的使用者請求、使用者流量全部進入北京機房,南方所有的使用者請求、使用者流量進入上海機房。困難的地方是,這個使用者有可能今天在北方,明天在南方,因為他在出差,還有就是存在一些區域在我們劃分南北shard的時候,它是在邊界上面的,這種情況會加劇同一個使用者流量在南北機房來回漂移的發生。還有個情況,當我們某個機房出現故障,如核心交換機壞掉導致整個機房服務不可用,我們希望可以把這個機房的所有流量快速切到另外的資料中心去,從而提高整個餓了麼服務的高可用性。

以上所有的因素,都需要底層資料庫的資料之間是打通的。而今天我所要分享的DRC專案就是餓了麼異地MySQL資料庫雙向複製的元件服務,即上圖中紅色框標記的部分。

異地多活對底層資料的要求

我們在前期調研DRC實現的時候,主要總結了的三點,而在後續的設計和實施當中,基本上也是圍繞這三點來去解決問題。

第一個我們覺得是延遲要低,當時給自己定的目標是秒級的,我們希望在北京機房或上海機房寫入的資料,需要在1秒鐘之內同步到上海或者北京機房。整個延遲要小於1秒鐘。

第二個就是我們要確保資料的一致性,資料是不能丟也不能錯的,如果出現資料的不一致性,可能會給上層的業務服務、甚至給產品帶來災難性的問題。

第三個就是保證整個複製元件具備高吞吐處理能力,指的是它可以面對各種複雜的環境,比方說業務正在進行資料的批量操作、資料的維護、資料字典的變更情況,這些會產生瞬間大量的變更資料,DRC需要面對這種情況,需要具備高吞吐能力去扛住這些情況。

資料低延遲和一致性之間,我們認為主要從資料的併發複製這個策略上去解決,安全、可靠、高效的併發策略,才能保證資料是低延遲的複製,在大量資料需要複製時,DRC併發處理才能快速在短時間內解決。資料一致性,使用者的流量可能被路由到兩個機房的任何一個機房去,也就是說同樣一條記錄可能在兩個機房中被同時更改,所以DRC需要做資料衝突處理,最終保持資料一致性,也就是資料不能出錯。如果出現衝突且DRC自身無法自動處理衝突,我們還提供了一套資料衝突訂正平臺,會要求業務方一道來制定資料訂正規則。

高吞吐剛才已經介紹了,正常情況使用者流量是平穩的,DRC是能應對的,在1秒鐘之內將資料快速複製到對端機房。當DBA對資料庫資料進行資料歸檔、大表DDL等操作時,這些操作會在短時間內快速產生大量的變更資料需要我們複製,這些資料可能遠遠超出了DRC的最大處理能力,最終會導致DRC複製出現延遲,所以DRC與現有的DBA系統需要進行互動,提供一種彈性的資料歸檔機制,如當DRC出現大的複製延遲時,終止歸檔JOB,控制每輪歸檔的資料規模。如DRC識別屬於大表DDL產生的binlog events,過濾掉這些events,避免這些資料被傳輸到其他機房,佔用機房間頻寬資源。

以上是我們在實施異地多活的資料層雙向複製時對DRC專案提出的主要要求。

資料叢集規模(多活改造前)

這是我們在做多活之前的北京資料中心的資料規模,這個資料中心當時有超過250套MySQL的叢集,一千多臺MySQL的例項,Redis也超過四百個叢集。

DRC服務的目標物件就是這250套MySQL叢集,因為在正在建設的第二個資料中心裡未來也會有對應的250套MySQL叢集,我們需要把兩個機房業務對等的叢集進行資料打通。

多活下MySQL的用途分類

我們按照業務的用途,給它劃分了多種DB服務型別。為什麼要總結這個呢?因為有一些型別,我們是不需要複製的,所以要甄別出來,首先第一個多活DB,我們認為它的服務需要做多活的。

比方說支付、訂單、下單,一個機房掛了,使用者流量切到另外新的機房,這些業務服務在新的機房是工作的。我們把這些多活服務依賴的DB稱為多活DB,我們優先讓業務把DB改造成多活DB,DRC對多活DB進行資料雙向複製,保障資料一致性。多活DB的優勢剛才已經講了,如果機房出現故障、核心交換機出問題,整個機房垮了,運維人員登不進機房機器,那麼我們可以在雲端就把使用者流量切到其它的機房。有些業務對資料有強一致性要求,後面我會講到其實DRC是沒有辦法做到資料的強一致性要求的,它是有資料衝突發生的,需要引入資料訂正措施。

業務如果對資料有強一致性要求,比方說使用者註冊,要求使用者登入名全域性唯一(DB欄位上可能加了唯一約束),兩個機房可能會在同一時間接收了相同使用者登入名的註冊請求,這種情況下,DRC是無法自身解決掉這個衝突,而且業務方對這個結果也是無法接受的,這種DB我們會把它歸納到GlobalDB裡面,它的特性是什麼呢?

它的特性是單機房可寫,多機房可讀,因為你要保證資料的強一致性的話,必須讓所有機房的請求處理結果,最終寫到固定的一個機房中。這種DB的上層業務服務,在機房掛掉之後是有損的。比方說機房掛了,使用者註冊功能可能就不能使用了。

最後一個非多活DB,它是很少的,主要集中於一些後端的管理平臺,這種專案本身基本上不是多活的,所以這種DB我們不動它,還是採用原生的主備方式。

DRC總體架構設計

這是DRC複製元件的總體架構設計。我們有一個元件叫Replicator,它會從MySQL叢集的Master上把binlog日誌記錄抽取出來,解析binlog記錄並轉換成我們自定義的資料,存放到一個超大的event buffer裡面,event buffer支援TB級別的容量。

在目標機房裡我們會部署一個Applier服務,這個服務啟一個TCP長連線到Replicator服務,Replicator會不斷的推送資料到Applier,Applier通過JDBC最終把資料寫入到目標資料庫。我們會通過一個Console控制節點來進行配置管理、部署管理以及進行各個元件的HA協調工作。

DRC Replicator Server

 

這是DRC Replicator Server元件比較細的結構描述,主要是包含了一個MetaDB模組,MetaDB主要用來解決歷史的Binlog的解析問題。

我們成功解析Binlog記錄之後,會把它轉換成我們自己定義的一種資料結構,這種結構相對於原生的結構,Size更小,MySQL binlog event的定義在size角度上考慮事實上已經很極致了,但是可以結合我們自己的特性,我們會把不需要的event全部過濾掉(如table_map_event),把可以忽略的資料全部忽略掉。我們比對的結果是需要複製的event資料只有原始資料size的70%。

DRC Applier Server

 

往目標的MySQL叢集複製寫的時候,由DRC Applier Server負責,它會建一個長連線到Replicator上去,Replicator PUSH資料給Applier。Applier把資料拿到之後做事務的還原,最後通過JDBC把事務重新寫到目標DB裡面,寫的過程當中,我們應用了併發的策略。

併發策略在提供複製吞吐能力,降低複製延遲起到決定的作用,還有冪等也是非常重要的,後面有很多運維操作,還有一些Failover回退操作,會導致發生資料被重複處理的情況,冪等操作保障重複處理資料不會發生問題。

DRC防止迴圈複製

在做複製的時候,大家肯定會碰到解決迴圈複製的問題。我們在考慮這個問題的時候,查了很多資料,也問了很多一些做過類似專案的前輩,當時我們認為有兩大類辦法,第一大類辦法一開始否決了,因為我們對MySQL的核心原碼不熟悉,而且時間上也來不及,雖然我們知道通過MySQL的核內解決迴路複製是最佳的、最優的。

靠DRC自身解決這個問題,也有兩種辦法,一種辦法是我們在Apply資料到目標DB的時候把binlog關閉掉,另外一種辦法就是寫目標DB的時候在事物中額外增加checkpoint表的資料,用於記錄源DB的server_id。

後來我們比較了一下,第一個辦法是比較簡單,實現容易,但是因為Binlog記錄沒有產生,導致不支援級聯複製,也對後續的運維帶來麻煩。所以我們最後選擇的是第二個辦法,通過把事務往目標DB複製的時候,在事務中hack一條checkpoint的資料來標識事務產生的原始server,DRC在解析MySQL binlog記錄時就能正確分辨出資料的真正來源。

DRC資料一致性保障

首先,因為資料庫是多活的,我們必須從資料中心層面儘可能把資料衝突發生的概率降到最低,避免衝突,怎麼避免呢?就是合理的流量切分,你可以按照使用者的維度,按照地域的維度,對流量進行拆分。剛才我們講的,北方使用者的所有資料在北京機房,這些北方使用者的下單、支付等的所有運算元據都是在北方機房產生的,所以使用者在同一個機房中發生的資料變更操作絕對是安全的。我們最怕的是同一個資料同時或者是在相近的時間裡同時在兩個機房被修改,我們怕的是這個問題,因為這種情況就會引發資料衝突。所以我們通過合理的流量切分,保證絕大部分時候資料是不會衝突的。

第二個我們認為你要保障資料一致性,首先你要確保資料不丟,一旦發生可能資料丟失的情況,我們會做一個比較保險的策略,就是把資料複製的時間位置回退,即使重複處理資料,也避免丟資料的可能,但是這個時候會帶來資料重複處理的問題,所以資料的冪等操作特別重要。

這些都是我們避免資料發生衝突的方法,那衝突實際上是不可避免的,衝突發生後,我們怎麼解決?最終採用的辦法是在資料庫表上隱含地加一個時間欄位(資料最後更新時間),這個欄位對業務是透明的,主要用來輔助DRC複製,一旦資料發生衝突,DRC複製元件可以通過這個時間來判斷兩個機房或者三個機房中的哪條資料是最後被更新的,最新優先的原則,誰最後的修改時間是最新的,就以它為準。

DRC資料複製低延遲保障

剛才我們講的是資料的一致性,還有一個點非常重要,就是資料複製的低延遲保障。我們現在延遲包括使用者高峰時間也是小於1秒的,只有在凌晨之後,各種歸檔、批量資料處理、DDL變更等操作會導致DRC延遲出現毛刺和抖動。如果你的延遲很高的話,第一在做流量切換時,因為運維優先保障產品服務的可用性,在不得以的情況會不考慮你的複製延遲,不會等資料複製追平之後再切流量,所以你的資料衝突的概率就變的很大。

為了保證複製低延遲,我們認為主要策略、或者你在實施時主要的做法還是併發,因為你只有用高效的安全的併發複製策略,服務才有足夠的吞吐處理能力,而不至於你的複製通道因為遇到“海量”資料而導致資料積壓,從而加劇了複製延遲的產生。

我們一開始採用的基於表級別的併發,但是表級別的併發在很多情況下,併發策略沒辦法被有效的利用,比方說有的業務線的資料庫可能90%的資料集中在一張表或者是幾個表裡面,而大部分表資料量很小,那基於表的併發策略就併發不起來了。我們現在跑的是基於行級別的併發,這種併發它更能容忍和適應很多場景。

DRC & MySQL Master切換

這個是DRC複製元件與MySQL叢集的關係關聯圖,一旦MySQL叢集裡面的Master發生了主備切換,原來的Master掛了,DRC怎麼處理?目前的解決方案是DBA系統的MHA工具會通知DRC控制中心,DRC的控制中心會找到對應的複製鏈路,然後把複製鏈路從老的Master切到新的Master,但是關鍵點是MHA在通知之前先把老的Master設定為不可寫,阻斷DRC可能往老的Master繼續寫資料。

DRC線上執行狀況(規模)

這個是我們DRC上線之後的執行狀況。現在大概有有將近400多條複製鏈路。這個複製鏈路是指單向的鏈路。我們提供的訊息訂閱大概有17個業務方接入,每天產生超過1億條的訊息。

DRC線上執行狀況(效能)

這是DRC線上執行的一個效能監控快照,我們可以看到,它是上午11點多到12點多的一個小時的效能,你會發現其實有一個DB是有毛刺的,有一個複製鏈路有毛刺,複製延遲最高達到4s,但是大部分的複製鏈路的延遲大概也是在1秒或1秒以下。

Q&A

Q1:你好,想問一下餓了麼是怎麼避免各個機房中的PK衝突的?

A1:主鍵自增的步長在各個機房中是固定相同的,但是每個機房的增長offset是不同的,所以不會出現PK衝突。

 

Q2:DRC複製會不會對目標資料庫造成效能影響?

A2:有影響。因為DRC會佔用目標DB的IOPS。DRC Apply本身就是目標DB的上層服務。

 

Q3:DRC Applier採用JDBC去寫目標DB,除了這個辦法還有其它途徑嗎?

A3:目前我們分析binlog還原事務,然後通過JDBC把事務寫到目標DB。我們曾經模擬過MySQL的binlog server,讓目標DB啟動一個Replication連線到我們偽造的binlog server上,我們的binlog server會把binlog記錄發給目標DB,這個辦法會存在很多問題,我們就放棄了這個辦法。

 

Q4:有監測資料一致性的工具嗎?

A4:這個是有的。DBA團隊開發了一套checksum工具來實時監測資料一致性。

 

Q5:餓了麼做異地雙活主要的原因就是剛剛提到的單個機房是無法擴容嗎?

A5:是的。

 

Q6:雙向同步後期的運維成本高嗎?

A6:對DBA的運維會造成影響,DBA的歸檔job、DDL釋出等操作都需要考慮DRC的雙向同步因素。

相關文章