大廠如何使用binlog解決多機房同步mysql數據(一)?

享學課堂特邀作者:老顧
轉載請聲明出處,謝謝!!!

前言

小夥伴們是否經常聽說多機房部署,異地容災?什麼兩地3中心,三地5中心?是否好奇多機房部署,數據之間是如何共享的呢?

今天老顧就來嘗試着給大家解惑解惑,並詳細介紹一下數據同步的問題。

單一IDC

上圖的架構,是一個IDC機房中,部署了一主兩從mysql數據庫集羣,大多數據中小型互聯網公司採用的方案。

上面的方案存在一些問題:

1)不同地區的用戶體驗速度不同。一個IDC必然只能部署在一個地區,例如部署在北京,那麼北京的用戶訪問將會得到快速響應;但是對於上海的用戶,訪問延遲一般就會大一點

上海到北京的一個RTT可能有20ms左右。

2)容災問題。這裏容災不是單臺機器故障,而是指機房斷電,自然災害,或者光纖被挖斷等重大災害。一旦出現這種問題,將無法正常爲用戶提供訪問,甚至出現數據丟失的情況。

某年,支付寶杭州某數據中心的光纜就被挖斷過

多IDC

爲了解決這些問題,我們可以將服務部署到多個不同的IDC中,不同IDC之間的數據互相進行****同步。如下圖

通過這種方式,我們可以解決單機房遇到的問題:

1)用戶體驗。不同的用戶可以選擇離自己最近的機房進行訪問。

2)容災問題。當一個機房掛了之後,我們可以將這個機房用戶的流量調度到另外一個正常的機房,由於不同機房之間的數據是實時同步的,用戶流量調度過去後,也可以正常訪問數據。

故障發生那一刻的少部分數據可能會丟失

關於流量的調度問題,本文就不介紹,以後老顧會單獨介紹流量、灰度發佈的問題。本文主要介紹數據同步。

容災補充

  • 機房容災:上面的案例中,我們使用了2個IDC,但是2個IDC並不能具備機房容災能力。至少需要3個IDC,例如,一些基於多數派協議的一致性組件,如zookeeper,redis、etcd、consul等,需要得到大部分節點的同意。例如我們部署了3個節點,在只有2個機房的情況下, 必然是一個機房部署2個節點,一個機房部署一個節點。當部署了2個節點的機房掛了之後,只剩下一個節點,無法形成多數派。在3機房的情況下,每個機房部署一個節點,任意一個機房掛了,還剩2個節點,還是可以形成多數派。這也就是我們常說的"兩地三中心”
  • 城市級容災:在發生重大自然災害的情況下,可能整個城市的機房都無法訪問。爲了達到城市級容災的能力,使用的是"三地五中心"的方案。這種情況下,3個城市分別擁有2、2、1個機房。當整個城市發生災難時,其他兩個城市依然至少可以保證有3個機房依然是存活的,同樣可以形成多數派。

Mysql主從同步

小夥伴們應該知道mysql的主從架構的數據複製的基本原理。

通常一個mysql集羣有一主多從構成。用戶的數據都是寫入主庫Master,Master將數據寫入到本地二進制日誌binary log中。從庫Slave啓動一個IO線程(I/O Thread)從主從同步binlog,寫入到本地的relay log中,同時slave還會啓動一個SQL Thread,讀取本地的relay log,寫入到本地,從而實現數據同步

數據同步方案

根據上面的mysql主從數據複製方案,那我們是不是可以自己寫個組件,也讀取binlog日誌,解析出sql語句;然後同步到另一個mysql集羣呢?

這樣就可以實現了一個集羣的數據,同步到另一個集羣中。

那這個組件需要我們自己寫嗎?這個組件可以參考binlog的協議,只要有資深的網絡編程知識,是能夠實現的。

當然現在也不需要我們自己編寫,現在市面上有成熟開源的:

•阿里巴巴開源的canal

•美團開源的puma

•linkedin開源的databus

我們可以利用這些開源組件訂閱binlog日誌,解析到變化數據同步到目標庫中。整個過程可以分爲2步,第一步訂閱獲得變化的數據,第二步是把變化數據更新到其他目標庫

這邊所說的目標庫,不單單爲mysql集羣,也可以爲redis,es等

上圖我們通過訂閱binlog,完成比較有代表性的數據同步。

多機房Mysql同步

根據上面的知識,多機房的mysql的數據同步,可以也採用binlog方案

北京用戶的數據不斷寫入離自己最近的機房的DB,通過binlog訂閱組件訂閱這個庫binlog,然後下游的更新組件將binlog轉換成SQL,插入到目標庫。上海用戶類似,只不過方向相反,不再贅述。通過這種方式,我們可以實時的將兩個庫的數據同步到對端。

上面的方案面對binlog更新不頻繁的場景,應該問題不大;但是如果更新很頻繁,那麼binlog日誌量會很大,處理更新數據的組件很有可能會頂不住,那如何處理?

優化同步方案

爲了解決binlog量過大,更新數據組件處理不過來,可以在此方案中加入MQ進行削峯,如下圖:

同步方案的問題

我們看到上面的架構,主要是針對增量數據的同步;但一開始項目上線的時候,全量數據怎麼處理呢?這個一般的處理策略是DBA先dump一份源庫完整的數據快照;目標庫導入快照即可。

下面我們看看增量數據同步,仔細的小夥伴們應該會看到北京IDC和上海IDC之間的數據是雙向的,因爲北京用戶的數據是更新到北京DB的,上海用戶的數據是更新到上海DB的,所以業務上面也是必須是雙向的

整個數據同步的過程會出現幾個問題:

如何解決重複插入?

考慮以下情況下,源庫中的一條記錄沒有唯一索引。對於這個記錄的binlog,通過更新組件將binlog轉換成sql插入目標庫時,拋出了異常,此時我們並不知道知道是否插入成功了,則需要進行重試。如果之前已經是插入目標庫成功,只是目標庫響應時網絡超時(socket timeout)了,導致的異常,這個時候重試插入,就會存在多條記錄,造成數據不一致。

因此,通常,在數據同步時,通常會限制記錄必須有要有主鍵或者唯一索引

對於DDL語句如何處理?

如果數據庫表中已經有大量數據,例如千萬級別、或者上億,這個時候對於這個表的DDL變更,將會變得非常慢,可能會需要幾分鐘甚至更長時間,而DDL操作是會鎖表的,這必然會對業務造成極大的影響

因此,同步組件通常會對DDL語句進行過濾,不進行同步。DBA在不同的數據庫集羣上,通過一些在線DDL工具進行表結構變更。

如何解決唯一索引衝突?

由於兩邊的庫都存在數據插入,如果都使用了同一個唯一索引,那麼在同步到對端時,將會產生唯一索引衝突。對於這種情況,通常建議是使用一個全局唯一的分佈式ID生成器來生成唯一索引,保證不會產生衝突。關於如何生成全局唯一分佈式ID,可以看老顧之前的文章。

另外,如果真的產生衝突了,同步組件應該將衝突的記錄保存下來,以便之後的問題排查。

如何解決數據迴環問題?

此問題是數據同步經常出現的,也是必須需要解決的。最重要的問題。我們針對INSERT、UPDATE、DELETE三個操作來分別進行說明:

INSERT操作

假設在A庫插入數據,A庫產生binlog,之後同步到B庫,B庫同樣也會產生binlog。由於是雙向同步,這條記錄,又會被重新同步回A庫。由於A庫應存在這條記錄了,產生衝突。

UPDATE操作

先考慮針對A庫某條記錄R只有一次更新的情況,將R更新成R1,之後R1這個binlog會被同步到B庫,B庫又將R1同步回A庫。對於這種情況下,A庫將不會產生binlog。因爲A庫記錄當前是R1,B庫同步回來的還是R1,意味着值沒有變。

在一個更新操作並沒有改變某條記錄值的情況下,mysql是不會產生binlog,相當於同步終止。下圖演示了當更新的值沒有變時,mysql實際上不會做任何操作:

上圖演示了,數據中原本有一條記錄(1,"tianshouzhi”),之後執行一個update語句,將id=1的記錄的name值再次更新爲”tianshouzhi”,意味着值並沒有變更。這個時候,我們看到mysql 返回的影響的記錄函數爲0,也就是說,並不會真的產生更新操作

小夥伴們是不是以爲,update操作不會有迴環問題了;事實上並不是,我們看一些場景:

考慮A庫的記錄R被連續更新了2次,第一次更新成R1,第二次被更新成R2;這兩條記錄變更信息都被同步到B庫,B也產生了R1和R2由於B的數據也在往A同步,B的R1會被先同步到A,而A現在的值是R2,由於值不一樣,將會被更新成R1,併產生新的binlog;此時B的R2再同步回A,發現A的值是R1,又更新成R2,也產生binlog。由於B同步回A的操作,讓A又產生了新的binlog,A又要同步到B,如此反覆,陷入無限循環中

這個後果將會進入死循環。

DELETE操作

同樣存在先後順序問題。例如先插入一條記錄,再刪除。B在A刪除後,又將插入的數據同步回A,接着再將A的刪除操作也同步回A,每次都會產生binlog,陷入無限迴環

總結

今天老顧介紹了基本的多機房同步mysql的方案,以及同步方案遇到的一些問題,以及一些解決方案;但還****遺留了數據迴環問題,老顧將在下一篇文章中介紹解決方案。謝謝!!!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章