分佈式數據中的坑(一)Master-Slave架構

前言

Master-slave 架構能夠說是最經常使用的架構,關係型數據庫諸如:mysql,postgreSql,oracle,Nosql諸如:MongoDb,消息隊列諸如:Kafka,RabbitMQ等都使用了這種架構,本文將先簡要介紹此種架構並介紹高可用Master-slave架構中的一些坑,以及應對之策。mysql

Master-Slave架構的原理

如上圖所示,Master-slave能夠說是最多見的架構:

  • 副本之一會被指定爲Leader,或者是主庫,寫入請求會發送給主庫,主庫會將數據寫入本身的本地
  • 每當主庫寫完後,會將變動的日誌發送從庫,從庫按照主庫更新的順序應用全部寫入
  • 客戶端能夠從任何庫讀取數據

同步複製和異步複製

如圖中所示,Follower 1是同步複製,Follower 2是異步複製。 同步複製的優勢是,從庫能夠保證與主庫一致的最新副本,若是主庫掛了,能夠從從庫找到一致的數據。缺點是,若是從庫掛了,沒有響應,那麼全部的寫入操做都將沒法處理,直到從庫恢復爲止。

因此一般狀況下,這種架構通常都是用徹底異步的方式進行同步,這種狀況下,若是主庫失效,那麼全部尚未被複制的數據就可能會被丟失;可是若是從庫失效,主庫依然能夠繼續處理寫入操做。web

主從複製的底層實現

基於語句

主庫會記錄每一個請求的sql,而且將這些sql:insert select update等發送至從庫執行,雖然聽上去沒什麼問題,可是:redis

  • 有一些非肯定性的函數會在每一個副本上產生不同的結果,好比now(),rand()這種
  • 若是有自增列或者語句有依賴數據庫現有的數據,那麼必須保證從庫執行的順序與主庫相同,不然會有不一樣的結果
  • 另外,一些存儲過程,觸發器的執行,若是有問題的話,他會影響到全部的從庫

這種複製方法如今基本不太使用了。算法

基於預寫日誌

以前說過,主庫在處理寫請求時,都會先寫WAL(Write Ahead Log),日誌的結構很是底層,包含了寫入時須要追加的數據序列:磁盤塊中哪些數據發生了更改。這就意味着它會比數據庫存儲結構緊密相關,甚至有可能數據庫只由於版本不一致而致使沒辦法複製。sql

對於運維來講,這點就很不友好了。若是複製協議對於版本不匹配的話,一般狀況下須要停機才能夠升級。數據庫

基於行的邏輯日誌

另外一種方法是使用另外一種日誌,只是這種日誌再也不於底層存儲耦合,好比Mysql的binlog。它通常是以行爲密度來描述寫入的操做:緩存

  • 對於新插入的行,日誌包含全部列的新值
  • 對於刪除的行,日誌包含惟一主鍵來標誌已刪除的行
  • 對於更新的行,日誌一樣包含惟必定位到這條記錄的信息,來記錄新數據

這種日誌普遍應用,它包含的信息與底層徹底解耦,甚至能夠基於它複製不一樣數據庫的數據。微信

Master-Slave架構如何實現高可用

增長設定新的從庫

若是咱們須要新增新的副本,如何保證新的從庫擁有與主庫徹底一致的數據呢?由於客戶端在不斷的向主庫寫入數據,最簡單的辦法,咱們能夠禁止主庫的寫入,而後使用日誌進行同步;但這會違背咱們高可用的原則。咱們通常使用以下方法:網絡

  • 大多數的數據庫都有一致性快照的功能,咱們能夠獲取它
  • 接着講快照複製到新的從庫節點
  • 從庫複製全部快照時間點以後的數據根據日誌(mysql中的binlog)追趕主庫

從庫掛了怎麼辦

首先每一個從庫的硬盤上確定也會記錄全部從主庫收到的數據庫的變動,若是從庫掛了,等他恢復的時候能夠從日誌中知道發生故障以前最後處理的一個事物,接着鏈接主庫,請求拉取全部以後它斷片以後數據變動,以後追遇上主庫便可架構

主庫掛了怎麼辦

主庫掛了須要fail over(故障轉移):須要將一個新的從庫提高爲主庫,而且從新配置應用客戶端,將全部寫操做發送到新的主庫。一般有以下幾個步驟:

一、確認主庫失效。現實生活中有不少緣由致使失效:崩潰、停電、網卡以及機器被修空調的師傅搬走等緣由。沒有萬無一失的方法,大部分系統採用簡單的超時來肯定。

二、選一個新的主庫。主庫的選舉一般是以擁有着主庫最新數據的那個從庫爲準,具體的算法能夠是paxos raft等共識算法。

三、從新配置路由,寫請求發送到新的主庫上;而且若是老領導回來了,須要避免「腦裂」的狀況,讓老領導下臺成從庫。

failOver一樣會有不少問題:

  • 若是是異步複製,那麼難以免會有數據的丟失,這些數據寫入的丟失每每會被直接忽略
  • 寫入的丟失還可能會有潛在問題,由於數據庫的數據可能會與外部存儲(redis)想結合,進行業務上的控制或者緩存。Github曾經有這個故障,一個過期的從庫被提高爲主庫,表採用自增ID列,由於丟失了數據,新主庫的計數器落後於老主庫的計數器,因此新主庫分配了一些已經被老主庫分配掉了的ID做爲主鍵,而這些主鍵又在redis中當緩存使用,致使了隱私數據的泄露。
  • 腦裂的問題:老領導恢復過來發現本身仍是領導,那麼這個集羣中就有兩個leader,兩個leader的話會產生一系列的衝突,例以下:

這個問題咱們下一節解決它

  • 主庫失效的超時時間應該如何配置?太長的話,意味着更多的數據可能被丟失,過短的話有可能出現沒必要要的故障轉移(好比只是單純的系統負載比較高或者網絡有延遲)

複製延遲的問題

大部分web應用都是讀多寫少,因此咱們一般採用多副本異步複製的架構,基於異步架構,可能會致使數據庫從庫和主庫的明顯不一致,可是通過一段時間後,他們最終會是一致的。

正常狀況下,複製延遲大概是幾分之一秒,可是在極端的狀況下(網絡延遲,機器高負荷)延遲可能達到幾秒甚至幾分鐘。

因此這是咱們在實際中會遇到的真實問題,瞭解這些問題以後能夠幫助咱們更好的設計業務。問題體如今下面幾個方面:

讀己之寫

如上圖所示,用戶剛提交的數據就查不到了。 咱們須要保證讀寫一致性。

讀寫一致性的意思就是保證用戶能夠讀到本身的寫,可是不保證其它用戶也能夠及時的讀到;其它用戶可能須要延遲才能夠讀取到。保證讀寫一致性的話,有下面幾個方向:

  • 基於業務,從主庫讀。舉個例子,微信的我的資料一般只能由你本身編輯,那麼咱們就能夠在業務上控制:從主庫讀取本身的檔案,從從庫讀取其餘人的檔案
  • 若是業務上的資料仍是由大多數人編輯呢?這種狀況咱們能夠追蹤末次更新的時間,從末次更新時間一分鐘內的讀取都從主庫讀。
  • 客戶端在每次查詢時帶上一個時間戳(最好是邏輯時鐘或者是同步的系統時鐘,時鐘裏面也有一些坑後續會講到),從庫收到請求發現本身的數據還不夠新的話,將請求redirect給主庫或者其餘從庫查詢

還有一種狀況,好比用戶有電腦和app端進行查詢,電腦更新了信息如何保證手機上能夠及時的查看到,如何保證讀寫一致性呢?有多臺設備的話你沒法記錄末次更新的時間戳(由於手機不可能知道電腦末次操做的時間),不一樣設備的時間自己也是不可靠的,這種狀況,能夠根據userID進行散列,保證同一個用戶永遠會落到一個Datacenter上的主庫。

單調讀

單調讀的意思是時光倒流:

用戶2345前一秒還能夠查詢出結果,後一秒數據就沒有了。單調讀是這種異常的保證機制,咱們須要保證同一個用戶的查詢請求老是被落到同一個follower上,好比說能夠根據userid取模,散列到固定的機器上。

一致前綴度

如上圖所示,全部問的問題存儲在分區1,回答的答案存儲在分區2,Poons問了一個問題,Cake回答,明明是先問問題再回答,可是從觀察者的角度來看,時間順序卻已經錯亂了。

實際上數據庫的操做能夠分紅因果操做和併發操做兩種類型,併發操做能夠理解爲A發起set A操做,B發起set B操做,這兩個操做是併發的沒有前後因果關係的,數據庫對於這種操做只須要確認發生的順序就能夠肯定最終的值;對於因果操做:A發起insert 666,B再發起update 666,這兩個操做是有依賴關係的,A成功了B才能夠成功。若是數據庫先接受到B的請求,那麼久發生衝突了。 這樣的操做叫作因果操做。(下一章會詳細討論)

回到咱們的問題,若是要解決這類問題,首先咱們須要一個算法分辨出那些操做是併發的,那些操做是因果的;對於這種問問題再回答的典型的因果類型的操做,咱們應該儘可能讓他們分配到同一個partition以內,確保有因果關係的寫入到寫到同一個分區。

總結

本文討論了典型的master-salve架構中常見的問題和解決的辦法,某些概念尚未進行深刻研究,後續介紹多主、無主架構時再說明。

根據CAP原則,最終一致性是無可避免的,可是咱們能夠作一些基於業務的特殊處理,在保證高可用的同時,儘可能去保證數據的一致性。

相關文章
相關標籤/搜索