第五章備份-part3,designing Data-Intensive Applications 中文翻譯摘要

weixin_34279579發表於2018-02-27

多Leader備份(Multi-Leader Replication)

這章當目前位置我們都在討論單Leader的備份架構,這個方案用的很普遍,但也有些問題。因為只有一個Leader,所有寫請求都要通過通過這個Leader,一旦客戶端和Leader連線斷了,就不能寫了。

一個最自然的解決方法是多個節點都可以接受寫請求,備份方式是一樣的,每個節點處理一個寫請求後把更新傳送給其他節點。我們管他叫多Leader配置。(multi-leader configuration)。每個節點都是Leader,有都是其他Leader的Follower。

多Leader備份的應用場景

如果你只有單個資料中心,那就不要用了,因為收益遠比付出的代價小。但是在某些場景下這麼搞是有意義的。

多資料中心

如果你的服務是多地部署的,那Leader只能在某一地的機房,這樣所有的寫請求都要發到這個機房去。跨機房跨地域的請求會很慢。在多Leader的配置下,你可以每個機房設定一個Leader,在機房內部,還是運用前面講到的Leader和Follower的機制做備份。機房之間,2個Leader之間彼此把自己的更新傳送給對方。方法如Figure 5-6


10388879-356b8a961eb0a957.png

那麼我們來對比下在多資料中心場景下,單Leader和多Leader的優劣

  • 效能
    在單Leader下,每個寫請求都必須翻山越嶺發到某一個資料中心的Leader去,這個會有很大的耗時,而且這違背了最初設立多個資料中心的目的。在多Leader下,每次寫請求可以傳送給本地的Leader,然後在非同步的同步給其他的資料中心。從使用者側看來,效能更好。
  • 資料中心容錯性
    單Leader配置下,一旦Leader所在的資料中心掛了,失敗重啟(failover)機制會在另一個資料中心再選一個Leader。在多Leader場景下,其他的資料中心可以繼續工作,等到異常的資料中心恢復後,再讓這個中心備份重新同步
  • 網路異常容錯性
    跨資料中心的網路往往走公網,這就要比內部網路可靠性差很多了。單Leader 情況下,網路問題十分重要,因為一旦網路不好,這個系統就跪了。但是多Leader情況下對網路的容錯性就好很多。因為客戶端可以先發個本地的Leader然後非同步的在Leader之間同步。

有些資料庫預設支援多Leader,但是往往都是外部實現的。例如MySQL的Tungsten Replicator, PostgreSQL 的BDR,Oracle的GoldenGate。

雖說多Leader的配置很多資料庫都作為一個額外特徵,但是他和很多其他的資料庫特徵在一起可能會有很奇怪的問題。比如自增鍵, 觸發器,完整性約束可能都是個問題。所以一般來說多Leader很多時候都是個危險的東西,能不用就不用。

離線操作

如果你的應用需要即使斷網也能正常工作,那多Leader就很好用了。比如你有一個日曆的app, 無論身處何地,網路如何,都要能夠正常檢視你的會議(讀請求),加入新的會議(寫請求)。這就要求當你做任何更新的時候,在裝置下次連上網路的時候,需要把這些更新傳送給伺服器和其他的裝置。

這種情況下,每個裝置有一個自己的本地資料庫作為Leader,同時還有一個非同步的多Leader同步流程把你的資料正確備份到所有裝置去。備份節點的落後可能有幾個小時甚至幾天。

從架構角度講,這跟多個資料中心間進行多Leader備份沒什麼區別。每個裝置是一個資料中心,網路連線非常不可靠。在日曆同步的豐富歷史中,多Leader備份機制是表現不錯的一個。

協同編輯

實時協同編輯應用允許多個人同時編輯一個文件。比如Etherpad和Google Docs。我們一般不把這件事情當做一個資料庫同步的問題,但是當出現離線編輯的時候,就跟上面的問題比較像了。當一個使用者修改文件時,首先把更新傳送給本地的備份,然後非同步的把更新傳送給server和其他使用者。

如果你要保證永遠沒有編輯衝突,那應用就需要在一個使用者編輯前加鎖。當一個使用者想要修改文件時,他必須等前一個人提交他們的修改並且釋放鎖。這種協同模型就跟單Leader備份加事務的方式很像了。但是為了更快,你可能希望每次更新更新的內容很小也不要鎖。但這就會帶來多Leader備份的各種問題,比如衝突解決。

應對寫入衝突

多Leader架構最大的問題就是寫入衝突,所以我們需要一個重提解決機制。舉個例子,假設一個網頁被兩個人同時編輯,使用者1把標題從A改成B,使用者2把標題從A改成C,兩個使用者的成功把自己的更新提交到了本地的Leader。但是當更新非同步的在節點中同步時,這就有衝突了。這個問題在單Leader的資料庫中是不存在的。


10388879-9f534c7b3465de7a.png

同步方案 vs 非同步衝突檢測

在單Leader時,在第一個提成功提交之前,第二個請求會一直阻塞住,或者直接失敗了。但是在多Leader時,兩個寫入都會成功,這種衝突會在未來的某個時間點非同步備份時被發現。這個時候讓使用者來解決衝突已經太晚了。從道理上來說,你可以線上的檢測衝突,也就是說只有在成功把更新資料傳送到所有其他的節點後才告訴使用者更新成功了。但是這就是去了多Leader的優勢,你沒有辦法再允許每個備份節點獨立的接受寫請求了。如果要同步的衝突檢測,還不如就搞一個單Leader備份框架呢。

避免衝突

最簡單的處理衝突的方法就是避免衝突。如果應用能夠保證對一條資料的所有寫都發給同一個Leader,就不會有衝突了。因為大部分多Leader備份的實現對於衝突的處理能力都很弱,避免衝突往往是一個推薦的方法。

舉個例子,如果一個使用者修改他自己的資料,那你可以將特定使用者的請求永遠發給相同的資料中心,用這個資料中心的Leader進行讀寫。不同的使用者有不同的資料中心(比如基於他們的地理位置選最近的資料中心),從單個使用者的角度來看,這就變成了一個單Leader的架構。

當時有時候你可能會需要修改使用者對應的資料中心,可能因為某一個資料中掛了,也可能是因為使用者地理位置變了,你要把他移到當前離他最近的資料中心。這種情況下,又會有衝突出來,因為在某段時間內,同一個使用者會寫兩個Leader。

一致性收斂

單Leader的架構下,寫請求是有順序的,多個更新同時發生時,最後一個寫確定了這個欄位的最終值。但是多Leader下,寫入就沒有一個確定順序了。在Figure 5-7中,leader 1的標題先改成B,後又變成C,Leader 2卻是先是C,後是B。這兩個順序沒有誰比誰更對的問題。

如果每個備份節點只根據他收到請求的順序更新自己的資料,那最終就會使一個不一致狀態。leader 1的最終結果是C, leader 2的最終結果是B,這是不能接受的。我們要求所有備份最終的結果必須是一樣的,這也就要求資料庫必須用一種收斂的方法來處理衝突。這就有很多方法了

  • 給每個寫一個唯一id(時間戳,隨機數,UUID,key/value的hash值),選id最大的寫請求作為贏家,把其他的都丟掉。如果用時間戳作為id,就加作最後寫生效last write wins, LWW).LWW 我們這章最後還會講。
  • 給每個備份節點一個唯一id,衝突時優先選用id大的節點的資料,但這會造成資料丟失。
  • 合併值, 比如把他們按照字母序合併,Figure 5-7的結果就會變成B/C
  • 把衝突記成一種特殊的資料格式然後交由應用後期處理。

自定義衝突解決邏輯

其實最合適解決衝突的方法依賴於應用,多Leader備份架構很多都允許你自定義衝突解決程式碼。這段程式碼可能是在寫入或者讀取時執行。

  • 寫入執行
    當資料庫系統在備份日誌中檢測到衝突,他會呼叫衝突處理器。這種處理器一般無法提示使用者,因為他是在後臺程式中執行的,所以必須速度很快。Bucardo就是這麼搞的。
  • 讀取執行
    當發現衝突時,所有衝突的現場都被保留下來。當這條資料下次被讀取的時候,這條資料的多個版本都會返回給應用。應用把這個衝突資料提示給使用者,然後由使用者手動解決衝突,再把最終結果寫到資料庫中。CouchDB 就是這麼搞的。

有一點注意,衝突解決往往是針對一條資料,而不是一個事務。所以雖然你的一個事務可能一次性包含多個寫請求,但是他們還是在衝突解決中分開處理的,也就說可能會不一致。

什麼是衝突?

有時候衝突其實很顯然,就好像Figure 5-7一樣,兩個人同時修改同一條資料的同一個欄位,毫無疑問這是衝突。

但是其他的衝突就很難檢測了。比如想象你有一個會議室,這個會議室在任何時刻都只能有一個人預訂。在這種場景下,當一個會議室在某個時間點有超過1個人預訂時,這就是一個衝突了。(但是他和Figure 5-7的區別是他不再是同一個欄位的問題,而是一個時間段的問題,就好像2個人一個人預約7-8,一個人預約9-10就不衝突,但是一個人預約7-10,一個人預約9-11就衝突了。)即使應用層在預訂之前檢查會議室在這個時間段是否可用,也不一定能避免衝突,因為請求可能發給多個Leader導致衝突。

多Leader備份拓撲圖

備份拓撲(replication topology)是指更新在節點間傳播的路徑,如果你像Figure 5-7一樣只有2個節點,那就很簡單了。但是如果你有多個節點,就可能有多鍾拓撲關係,例如Figure 5-8

10388879-d4b3859121877bd9.png

最常見的拓撲關係是全相連,如(c)。但是其他的拓撲結構也有人用,比如MySQL預設是有環裝拓撲,如(a), 每個節點從上個節點接收更新請求,把收到的更新加上自己的更新發給下一個節點。(b)中的星型結構也很流行,一個節點負責總控分發,其他節點只跟總控通訊。

在環型和星型拓撲中。一個寫請求要傳遞多個節點才能實現所有節點都備份完成。因此一個節點要把他收到的內容進行轉發。為了防止無限迴圈,每個節點都會有一個唯一的編號,當一個節點處理完一條備份日誌後,會給這個日誌當上這個節點的id。當再次收到這條日誌發現已經打上了自己的id後,節點直接把這條日誌丟掉。

環型和星型的問題是一旦有一個節點掛了,那整個系統的訊息備份都會受影響。這就要求系統必須在一個節點掛掉後重新配置通訊的節點,去掉失效節點,但是這個工作往往依賴於人工介入。這點就是全連線的拓撲結構的優勢所在,他有著更好的容錯性,因為一個節點掛了,其他節點還是能從別的通路中獲取到所有的更新訊息。

不要以為全連線就沒有問題,在網路環境中,有些鏈路會比其他的更快,這可能會導致有些更新被莫名其妙的覆蓋了。就像Figure 5-9一樣。客戶端A 通過Leader1插入了一條資料, 客戶端B通過Leader3更新這條資料,但是Leader 2收到的日誌順序就跟實際不一樣了,這就是前面講到的邏輯前後順序的問題。這種情況下,簡單的用時間戳都不能解決問題,因為時間戳也不一定能保證時序的嚴格一直,這個第8章講。


10388879-6d1a1f179a2372e8.png

為了解決這個問題,用到了一個叫版本列表(version vectors)的技術,這個也後面再講了。但是殘酷的事實是衝突解決在很多多Leader備份的系統中實現的都很差。如果你要用一個而類似這樣的系統,一定是小心這些問題,仔細讀文件。充分測試,確保他的實際工作結果跟你預想的一樣。

相關文章