聊聊分散式系統的數據一致性

聊聊分散式系統的數據一致性

來自專欄網易雲社區

本文來自網易雲社區。

進入公司以來,先後參與了分散式資料庫、分散式文件系統、NOS對象存儲雲服務等多個大型存儲系統的開發工作。保證各個數據副本間的一致性,是評價存儲系統優劣的核心指標,也是貫穿整個開發過程中的討論重點。以前都是碰到問題見招拆招,看一些分散式系統中偏理論的研究時也是覺得雲里霧裡,所以也想趁這次機會好好梳理一下,整理出的內容也希望能夠對大家以後的開發工作起到幫助。

數據多副本間的一致性

存儲系統是千差萬別的,可以拿來存放視頻這種動輒幾個G的大文件,也可以存放幾KB的KV鍵值對數據,還可能是MySQL這種關係型的資料庫。雖然上層數據結構千差萬別,在保證數據多副本一致性方面,都逃不出一個基本方式,狀態機複製(state machine replication)。參考有限狀態機的行為,只要是固定的一串輸入內容,輸出必然是相同的。因此問題就變成一定要保證所有副本伺服器的輸入內容必須一致,那所有副本回放完這串日誌後,內部存儲的數據也必然是相同的。

要搞定狀態機複製,需要干兩個事情。第一個是需要保證消息全序(total order),所有的消息都必須是有嚴格先後順序的,如果要達到這個目標那就必須保證一個時間內只有一個指揮官在發出指令,否則多個副本各說各話,完全不知道先做哪個後做哪個。這就涉及到存儲系統必須要乾的事情:「選主leader」。第二件事情,是要整一個比較牛逼的演算法出來,能夠保證無論發生什麼異常,各個副本獲得的日誌都是嚴格一致的。最容易想到的,我如果讓leader伺服器把命令向所有的副本都下發一遍,大家都返回ok了之後,這次請求才算完。這樣是否就算嚴格一致了?其實是不行的,在一些異常情況和leader伺服器切換時,會有很多問題。要解決這個問題,其實也不用多費腦筋了,業界已經有了共識:paxos演算法。基於這個演算法能夠保證任何情況下都能副本間一致,而且經過優化後可保證大部分情況下只要一輪通信就能達成一致,性能也能接受。一切都完美,對於一個一致性演算法真不能要求他更多了。Paxos有個小問題是不能保證消息全序,不過這個事情並不難搞。我們新版本分散式文件系統用的演算法PacificA、zookeeper用的ZAB,都解決了這個事情。這些都算是在某些更嚴格要求場景下的Paxos特例。

基於狀態機複製來做數據多副本的一致性,要面臨的一個困難是性能可能達不到要求。主要是兩方面,一個是底層的日誌複製協議吞吐量不夠高。這個在我們的分散式文件系統中就遇到過,因為日誌本身就包含了數據內容,而文件系統的數據量很大,即使做了多條日誌合併打包發送後,性能仍舊不好。要徹底解決這個事情,需要在leader伺服器上做pipeline,即不等副本返迴響應就立即發送下一批日誌。說起來簡單,但實現起來難度比較大,尤其是和各種異常場景混雜在一起後演算法的正確性需要仔細論證。我們的分散式文件系統設計時經過了一番激烈討論,最後還是放棄了。我說的意思是說工程上工作量較大,並不指理論上搞不定這個事情,google分散式資料庫的paxos演算法實現中就包含了pipeline機制。另一個問題是上層伺服器在回放日誌時,為了保證消息全序,必須使用單線程回放,導致回放性能達不到要求。這個事情在資料庫裡面特別明顯,只要線上寫壓力偏大,MySQL的從節點就會跟不上。MariaDB和淘寶都對此做了優化,但跟資料庫的一些自有特性糾纏在一起,要徹底搞定還是比較難。因此,基於狀態機複製的這個現實,在最上層做適當的分區,採用並行複製組來提升整體吞吐量是有必要的。

最後再來稍微討論一下基於狀態機複製機制實現系統的可用性問題。Paxos演算法本身是有容錯的,掛掉一個底層節點不會造成數據無法寫入。整套機制的單點在於用於消息排序的leader伺服器。Leader伺服器如果宕機,就需要重新執行選主演算法,中間會有一段時間不可用。因此,基於狀態機複製來實現的存儲系統,可用性註定是沒法做到非常高的。另外,如果考慮到跨機房多副本部署,機房間網路出現問題會出現網路分區的情況,此時存在單點的系統可用性會更低。其實大部分情況沒那麼苛刻,如果真要對這個問題求解,Amazon的dynamo系統可能算是一個解決方案。Dynamo為了提升可用性,放寬了對數據一致性的要求,消息完全不排序,而是通過一個向量時鐘(vector clock)的演算法追蹤消息的路徑進行智能排序。通過這個演算法,可能99.9%的請求最後都沒有問題,但確實會有極個別請求會出現無法判斷先後次序的衝突情況,此時只能把這個問題丟給苦逼程序猿來解決。想像一下,我們之前在資料庫查詢,每次都能返回一個確定的結果,如果哪天資料庫給你的答案是:「我也不清楚,你自己看著辦。。。」,你的腦袋是不是要爆炸了?對異常情況實現的複雜性決定了這個方案並不流行,起碼在國內真正用的人不多。當時Amazon的工程師為啥選了這個方案?可能是他們的老闆發了狠話:我這個系統可是每秒幾十萬上下的,你一年的工資只夠宕機2秒鐘,你自己看著辦吧!呃,對這個事情,有兩個結論。一個是如果系統經過了非常仔細考慮,要實現極高可用率的存儲系統,也是有可能實現的。前提是降低了數據一致性後,客戶端必須做容錯。第二個結論是這個事情幸虧只發生在了萬惡的資本主義。

分散式事務帶來的問題

在之前做分散式資料庫時,遇到了數據一致性的另一個問題。這個問題的起因是資料庫中有一個事務的概念,其中的原子性需要保證事務中多條記錄的修改或者同時成功,或者同時失敗。這個問題的解決主要是依靠兩階段提交演算法。演算法的核心邏輯比較簡單,由一個事務協調器發起兩輪操作命令,第一輪prepare命令詢問各個參與者是否同意本次操作,如果都同意則發起第二輪commit命令,在各個參與者伺服器上真正提交本次事務。

兩階段提交演算法能夠保證事務中多條記錄之間的一致性,在實現時被人詬病最多的是演算法的活性存在較大問題。線上運行時,事務參與者各伺服器出現宕機或者網路出現波動,都有可能導致某些資料庫上出現懸掛的中間狀態事務,即事務永遠處於prepare狀態中。而根據兩階段提交的語義,此時事務包含的所有數據行都會被鎖住。更悲劇的問題在於,我即使做一個腳本定期掃描各資料庫上的prepare懸掛事務,我也不敢做出任何後續處理,因為無論是把這些事務提交或者回滾,都有可能出現數據不一致。為了彌補這個缺憾,學術界提出了一個三階段提交,在commit之前再插入一個預提交的狀態,當處於預提交階段的事務如果在等待一段時間超時後仍然沒有收到後續指令,可以按照事先大家的約定來自行提交或回滾。整個事情的核心在於引入了超時,靈感可能來源於分散式領域的一個著名定理:FLP定理。意思是對於一個只是依靠消息通訊的非同步系統來說,如果出現了伺服器異常,無論依靠什麼精妙的演算法,都有可能導致本次請求的各參與方處於永久不一致的狀態。兩階段提交的問題也屬於非同步系統的範疇中。而定理有一個明白的前提就是大家不能依靠時間來做任何的超時假設。另一個更簡單粗暴的方法,我讓事務各參與者永遠不會失敗,事情不就結了嗎?具體做法就是把各伺服器全部用paxos協議來實現,而paxos本身是具有一定容錯性的。這個方案目前看起來還更靠譜一些, google已經把他做出來並大規模應用了。其實很多年前,資料庫和分散式領域的兩點陣圖靈獎得主Jim Gray和Leslie Lamport,就合作研究過這個方案並專門發表了論文。

其實,兩階段提交模型在理論上也還沒有達到完美的程度。回顧事務的ACID屬性,有一個要求是原子性,一個事務要麼回滾,要麼提交,在任何情況下都不允許出現一個事務所做的修改只有一部分可見的情況。而在兩階段提交中,如果在兩個節點分別執行commit操作的當中,用戶執行了read操作,完全有可能只能看到事務執行結果的一半。對此情況目前無能為力,只能自我安慰此次操作我們是保證最終一致性的。記得當初在開發分散式資料庫雲服務時,這個問題導致了一個更棘手的場景。當時想開發一個資料庫備份功能,用戶點擊備份後就把底層所有節點數據自動備份下來。由於上文所論述的無法獲取全局一致性視圖的問題,可能我們備份的數據只有一個事務的一半。這會導致更嚴重的問題,備份的數據一旦拿來做恢復,會導致那些殘缺的事務永遠處在部分更新的狀態中。在目前底層基於MySQL的架構下,這個問題已經很難解決了,我們來看一下google是如何做的吧。首先還是需要有一個地方做消息排序,每個消息分配一個遞增的全局日誌號。然後在底層存儲節點的每條數據中記錄該次更新的日誌版本號。當用戶來查詢時,首先獲取到當前最新的全局日誌版本號,然後在每個節點中根據該日誌號來進行查詢,就能保證返回給用戶的數據一定是一個全局一致的視圖。

新形勢下的新挑戰

前面說的基本上就是分散式領域數據一致性的幾個主要問題。看起來都很繞人,不過所幸每個事情基本上都有了結論並且有大量現成的第三方編程庫可以用,程序猿和攻城獅們都覺得好幸福。悲劇的是這個世界好像永遠都不如人所願,看看google的分散式資料庫演進就知道了。先是搞了bigtable,接著搞了megastore,最後又搞了spanner。估計去採訪Jeff Dean,他也會滿腹牢騷:尼瑪難道真是我天生愛折騰嗎,也還不是被逼的。Bigtable做完後分散式資料庫的基本樣子就定下來了,Megastore屬於google在修鍊神功過程中走火入魔的產品,Spanner被業界認為是第一個全球資料庫。啥叫全球資料庫?意思是世界上任何一個人發了一條消息,我就能立馬看到並保證數據的嚴格一致。舉個例子,這個事情如果換在我們這邊該怎麼做呢?第一步就是在杭州中心機房建一個資料庫,把這個產品先做出來。後來有客服反饋說,非洲人民抱怨發消息太慢,需要被關懷。好吧,那就在香港拉一條專線,把所有的國外請求都代理過來,降低網路上的時延。恩,確實有改進,但是每次伺服器請求還是在百毫秒以上,用戶還是覺得用起來不爽。呃,到此為止咱們應該就找不到更好的辦法了。面臨一個兩難,如果要提高用戶體驗,就需要在非洲部署一套資料庫,但如果某個操作需要同時讀寫兩個庫中的數據,根據前面的討論,分散式事務的強一致性還需要有一個地方集中分配全局更新日誌號。Spanner的想法非常的創新,利用了真實世界的時間戳來充當全局日誌號。Google在全球每個機房中同時部署了GPS+原子鐘,保證全球各地的時鐘誤差能夠控制在一個合理範圍內(ε,小於5ms)。當每個paxos複製組更新數據時,只要嚴格控制前後兩個事務的更新時間小於2ε,就意味著事務相互之間無重疊,時間戳是單調遞增的,此時時間戳就能完全替代日誌號來做事務版本控制。當產品需要做跨地區的兩階段提交時,確實需要聯繫兩個paxos複製組,暫時卡住數據更新並協調一個時間戳作為全局日誌號。但跨機房的兩階段提交畢竟是一個很少概率發生的事情,基於時間戳來設計事務並發控制機制,確實能夠使得絕大多數的更新操作能夠在非常短時間內完成。

Spanner的設計,核心思想就是依靠物理時鐘來代替全局日誌號,這個想法看似簡單,在此之前在架構師眼裡這事情完全是不可想像的。三十年前Leslie Lamport有一篇專門論述時間和消息順序的文章,被認為是分散式領域最重要的一片論文,就專門指出了無法依靠全局時間的這個原則。確實在現實世界裡,絕對時間是不存在的,即使費了九牛二虎力氣把兩台機器的時間校準到分毫不差,根據相對論,只要機房管理員哪天不小心把一台機器移了一下位置,時間又不一樣了。因此之後這麼長時間裡,在數據存儲這種要求強一致的系統中,再沒有人拿時間戳來設計系統。Google這種完全反經典的做法,如果沒有強大的工程能力做後盾也是白搭。如果單純依靠時間校準NTP協議,是無法在全球範圍內把時間誤差縮短到5ms之內的,世界上也只有少數幾個公司能夠在所有機房裡安裝gps天線。自去年起,就開始有一個風潮,國內的頂尖互聯網公司開始走向國外。形勢逼人,要在全球範圍內提供優秀的用戶體驗,可能會是接下來擺在一線互聯網公司面前的一個難題。NOS已經關注到這個問題並開始尋求解決方案,而作為更底層組件的資料庫,這方面的要求更高。打破經典不走尋常路,好像一直是國人的一個長項,說不定哪天咱們國人也能設計出比肩spanner的優秀全球資料庫,也未可知?

本文來自網易雲社區,經作者邱似峰授權發布。

原文地址:聊聊分散式系統的數據一致性 -社區博客-網易雲

更多網易研發、產品、運營經驗分享請訪問網易雲社區。

推薦閱讀:

一行代碼,保障分散式事務一致性—GTS:微服務架構下分散式事務解決方案
FastDFS分散式文件系統安裝與使用
分散式系統測試的應用方法——場景注入測試
ZBS:SmartX 分散式塊存儲 -- 元數據篇
論文筆記:[DSN 2002] Scalable Weakly-consistent Infection-style process group Membership protocol

TAG:Hadoop | 分散式計算 | 分散式系統 |