分佈式系統概要

分佈式系統是一個龐大的議題,每一個子領域都有大量的研究。學習分佈式系統知識,若是不分主次地隨看隨學,效果不會好。本文介紹了分佈式系統的主要概念,適合做爲分佈式系統的入門指南。算法

我一直在學習有關分佈式系統的知識,學習時間不算短了。老實說,只要你開始鑽研分佈式系統,知識點好像學不完似的,一個接一個。分佈式系統領域的文獻太多了,包括許多大學發表的論文,還有不少書籍可選。像我這樣的絕對新手,很難決定應該閱讀哪些論文或者購買哪些書籍。數據庫

同時,我還發現了幾個博客做者,他們在博客中推薦這篇或者那篇論文,聲稱這是分佈式系統工程師(先別糾結這個詞的含義)應該必知必會的。因而,個人閱讀列表愈來愈長:FLP、Zab、時間、分佈式系統中的時鐘和事件順序, Viewstamped Replication、Paxos、Chubby ,等等。問題是,不少時候他們沒有說明爲何要閱讀這篇或者那篇論文。爲了知足好奇心,不分主次地學習全部知識,這種想法我挺喜歡。不過,咱們仍是應該肯定不一樣內容的閱讀優先級,畢竟,天天只有 24 小時呀。緩存

除了有大量的論文和研究資料,分佈式系統領域的書也不少。我買了挺多本。我翻看其中的章節,發現有些書的書名起得頗有吸引力,貌似是我感興趣的內容,實則否則,或者說,這些書的內容並無涉及我想解決的問題。安全

在寫這篇博客的同時,我仍然在持續學習分佈式系統知識,因此請讀者有點耐心,明白本文不免會存在錯誤。已經寫好的內容,之後我會盡力作相應的擴充。服務器

在這裏,我想告訴你們,我已經在好幾個會議上講過這篇博客的內容,演講稿:What We Talk About When We Talk About Distributed Systems網絡

我在斯德哥爾摩 Erlang 用戶大會上的演講視頻:What We Talk About When We Talk About Distributed Systems(https://youtu.be/yC6b0709HCw)併發

主要概念app

肯定分佈式系統算法的分類,主要依據是搞清楚算法的各類屬性。例如,定時模型、進程間通訊類型和失效模型等等。異步

本文涉及的主要概念包括:分佈式

  • 定時模型(Timing Model)

  • 進程間通訊(Interprocess Communication)

  • 失效模式(Failure Modes)

  • 失效檢測器(Failure Detectors)

  • 領導人選舉(Leader Election)

  • 共識(Consensus)

  • 法定人數(Quorums)

  • 分佈式系統中的時間

  • 快速瀏覽 FLP

  • 結束語

  • 參考文獻

定時模型

定時模型有三種:同步模型,異步模型和部分同步模型。

同步模型的使用最簡單。全部組件在所謂的同步輪次(synchronous
round)中同時執行算法步驟。在同步模型中,消息送達的耗時是已知的,每一個進程的速度也是肯定的,即每一個進程執行一個算法步驟的耗時是肯定的。同步模型的主要問題是它不能很好地反映真實世界,與分佈式系統的差異就更大了。在分佈式系統中,咱們向另一個進程發送消息,確定但願本身行大運,由於只有這樣,才能保證消息必定發送到目標進程。好在咱們能夠利用同步模型得到理論結果,再把結果轉換到其餘模型。例如,在同步模型中時間是有保證的,若是一個問題在同步模型中都解決不了,那麼在其餘模型中,沒有了時間的保證(例如,完美失效檢測器就是這樣一個模型),就更不可能解決這個問題了。

異步模型的使用相對複雜。在異步模型中,每一個組件自行決定算法步驟的執行順序,每一步的耗時也沒有保證。雖然異步模型的描述很簡單,也更接近真實世界,可是它仍然算不上正確地反映了真實世界。例如,在異步模型中,一個進程響應請求的時間有多是無限長,可是在真實的項目中,通常會爲請求設置一個超時限制,若是在此期間沒有收到響應,將會停止請求而且報告異常。異步模型帶來的一個難點是如何確認一個進程的活躍度條件(liveness condition)。在最著名的不可能性結果當中,其中一個就是有關異步模型的:「只要有一個進程可能會失效就不可能達成共識」(Impossibility of Consensus with one Faulty Process),也就是說,不可能區分下列兩種狀況:(1)進程失效了;(2)進程沒有失效,可是它須要耗費無限長的時間去響應一條消息。

在部分同步模型中,每一個組件都瞭解一些關於定時的信息,它們要麼使用近似同步的時鐘,要麼大體瞭解消息送達的耗時或者每一個進程執行一個算法步驟的耗時。

Nancy Lynch 寫的書《分佈式算法》,就是按照上述三種定時模型組織內容的。

進程間通訊

這部分討論分佈式系統的不一樣進程之間如何交換信息,包括兩類:

  • 消息傳遞模型:進程間相互發送消息;

  • 共享內存模型:不一樣的進程讀寫共享的變量,實現數據的共享。

記住一點,咱們能夠作到:使用一種消息傳遞算法,構建一個分佈式共享的內存對象。可讀可寫的寄存器就是這樣一種實現,在不少分佈式系統書籍中均可以見到這個例子。有些做者還使用隊列和堆棧來描述一致性屬性,例如,線性化(linearizabilty)。你們不要弄混「共享內存」的兩種含義:第一種是指不一樣的進程讀寫同一個共享的變量,這是實現數據共享的一種方法;第二種是指在消息傳遞之上構建的共享內存抽象,剛剛已經講過。

在理解基於消息傳遞模型的算法時,還必須弄清楚進程之間是哪種連接(能夠理解爲進程之間的信道)。不一樣種類的連接抽象爲算法提供的保證也不相同。例如,完美連接可以保證消息的可靠送達,不會重複發送消息;它保證消息會且只會一次送達。顯然,現實世界並不是如此。當算法設計者在設計儘可能接近真實的模型時,他們會使用其餘類型的連接抽象。請牢記,即便完美連接並不是那麼真實,它仍然有用。例如,若是咱們能證實,即便連接是完美的咱們也沒法解決某個問題,那麼全部的相關問題也將是不可解的。在討論連接時,研究者一般會假定消息順序是「先入先出」的, Zab 就是一個例子。

失效模式

我之前寫過一篇文章「分佈式系統中的失效模式」,不過仍然值得在這裏說道說道。進程的失效模式是分佈式系統模型的一個屬性,它是對進程失效種類的假設。崩潰-結束(crash-stop)失效模式的假設是:進程一直是正確的,直到它發生崩潰;崩潰以後,這個進程不會被恢復。還有崩潰-恢復(crash-recovery)失效模式,進程崩潰後會被恢復。有些算法還會把進程恢復到崩潰以前的狀態。要作到這一點,恢復後的進程要麼從持久存儲中讀取之前的數據,要麼與同一羣組的其餘進程通訊,獲取所需的數據。須要指出的是,在有些羣組成員算法中,一個崩潰以後再恢復的進程並不等同於沒崩潰以前的原始進程。兩者是否等同,取決於這是一個動態羣組仍是一個固定羣組。

還有一種失效模式叫作忽略失效模式(omission failure mode),它的假設是進程不能接收或者發送消息。忽略有兩種:進程接收不到消息,或者進程發送不了消息。爲何要了解這個呢?想一想這種情景,實現分佈式緩存的一組進程,若是其中一個進程沒法應答其餘進程的請求,只要它能收到其餘進程的請求,那麼這個進程的狀態就是最新的,它仍然能夠響應來自客戶的讀請求。

一種更復雜的失效模式叫作拜占庭失效模式或者任意失效模式(Byzantine or arbitrary failures mode):進程有可能向同伴發送錯誤的信息;進程多是冒充的;應答給其餘進程的數據是正確的,可是篡改了本地數據庫的內容,等等。

在設計一個分佈式系統時,必須考慮清楚要應對的進程失效類別。Birman (參見《可靠的分佈式系統指南》)認爲,通常來講咱們不須要應對拜占庭失效。他引用了在 Yahoo! 完成的工做,得出一個結論:崩潰失效比拜占庭失效更爲常見。

失效檢測器

有了定時模型和進程失效模式的假設,咱們就能夠構建報告系統狀態的抽象——失效檢測器,即檢測某個進程是否已經崩潰,或者懷疑這個進程已經崩潰。完美失效檢測器從不虛報進程失效。假定系統是崩潰-結束失效模式加上同步定時模型,咱們只需使用超時就能實現一個完美失效檢測器:要求進程按期向檢測器報到,報到消息確定能送達給失效檢測器(同步模型可以保證這一點);若是報到消息沒有如期送達,咱們能夠判定該進程已經崩潰。

在實際的系統中,不能假定消息送達的耗時,也不能保證進程執行每一個步驟的耗時。這時候,咱們能夠構建一個失效檢測器 _p_ ,若是進程 _q_ 在超時限制 _N_ 毫秒內沒有回答,那麼檢測器會報告進程 _q_ 可能已經崩潰。若是後來 _q_ 又開始應答了,那麼 _p_ 將把 _q_ 從懷疑已經崩潰的名單中剔除。由於檢測器不知道它與 _q_ 之間的實際網絡延遲,又不想再把 _q_ 添加到懷疑名單中(事實已經證實 _q_ 沒有崩潰),因此它會增大 _N_ 的取值,保證 _q_有足夠的報到時間。若是在某個時間點 _q_ 真的崩潰了,檢測器首先把它列入懷疑名單,並將一直保留在那裏(由於 _q_ 永遠不會再報到了)。要了解這個算法,請閱讀《可靠且安全的分佈式程序設計指南》一書的「最終完美失效檢測器」部分,講得更好。

失效檢測器一般有兩個屬性:完備性和精準性。最終完美失效檢測器具備:

  • 強完備性:到最後,每個崩潰的進程都會被每個正常運行的進程永久地懷疑;

  • 最終強精準性:最終,沒有任何正常運行的進程會被其餘正常的進程所懷疑。

失效檢測器是異步模型中解決共識問題的關鍵。在 FLP 論文中,提出了不少著名的不可能性結果。這篇論文指出,在異步的分佈式系統中,若是進程有可能失效,那麼就不可能達成共識。要達成共識,就必須爲系統引入一個可以規避上述問題的失效檢測器。

領導人選舉

與失效檢測相反的一個問題是,如何斷定一個進程沒有崩潰,它所以可以正確地工做。網絡中其餘進程會信賴這個進程,把它看成可以協調分佈式行動的領導人。像 Raft 或者 Zab 這樣的協議就依賴領導人進程來協調行動。

協議中有一個領導人,意味着不一樣節點的地位變得不對稱,非領導人節點將成爲跟隨者。領導人節點所以成爲不少操做的瓶頸。選擇什麼協議,取決於咱們要解決什麼樣的問題。有時,咱們並不想使用須要領導人選舉的協議。記住,大部分經過某種共識得到一致性的協議,都包含一個領導人進程和一組跟隨者進程。這樣的例子包括Paxos 、 Zab 或者 Raft 。

共識

共識(consensus 或 agreement)問題是由 Pease , Shostak 和 Lamport 在論文「在存在失效的狀況下達成一致」首先提出來的。他們是這麼描述的:

容錯系統一般要求提供一種手段,使得獨立的處理器或者進程可以達成某種精確的相互一致。例如,一個冗餘系統的多個處理器可能須要按期同步它們的內部時鐘。或者每一個處理從某個時變的輸入傳感器讀取的數值都有稍微不一樣,它們須要肯定一個統一的值。

因此,共識是指獨立的進程之間達成一致。針對某一個問題,這些進程提議了一些值,像它們的傳感器的當前取值,而後根據這些值就共同的行動達成一致。例如,一輛轎車包含多個提供剎車溫度水平信息的傳感器。這些傳感器的讀數可能不一樣,由於它們的精度等不同。可是汽車的防抱死制動系統須要它們達成一致,這樣才能肯定須要施加多少壓力給剎車。這就是一個在咱們平常生活中解決的共識問題。《容錯的實時系統》這本書解釋了在汽車行業的分佈式系統中遇到的共識及其餘問題。

實現某種形式共識的進程,它的做用是對外顯露一個API ,包括提議和決定功能。在共識的開始階段,進程提議一個值,而後根據系統中提議的全部值,決定一個最終的值。共識算法必須知足下列屬性:終止(Termination)、合法性(Validity)、誠實(Integrity)和一致性(Agreement)。例如,對於常規共識,

  • 終止:每個正確的進程最終都會決定一個值;

  • 合法性:若是某個進程最終決定取值是 _v_ ,那麼這個 _v_ 必然是由某個進程提議的;

  • 誠實:沒有進程會決定兩次;

  • 一致性:兩個正確的進程,它們的決定不會不一樣。

更多細節,請參考上面提到的原始論文。還有一些很棒的參考書:

  • 《可靠且安全的分佈式程序設計導論》第 5 章

  • 《同步消息傳遞系統中的容錯一致》

  • 《容錯、異步分佈式系統中的通訊與一致抽象》

法定人數

法定人數(Quorum)是設計容錯分佈式系統的工具,它是能表徵系統特徵的進程的交集,其中某些進程有可能失效。

舉個例子,假設某個算法遵循崩潰失效模式,由 _N_ 個進程組成。只要大多數進程成功地應用了某個操做(例如,寫入數據庫),就能夠說已經有了進程的法定人數。只要只有少數進程崩潰,即不超過 _N/2-1_ 個進程崩潰,那麼大多數進程仍然知曉應用到系統的最後操做。例如, Raft
在提交日誌到系統時就用到了由大多數進程構成的法定人數。領導人向跟隨者發出日誌複製請求,只要有一半的服務器有應答,領導人當即把這一日誌項應用到它的狀態機。領導人加上一半服務器,已經構成了大多數。這樣作的好處是, Raft 沒必要等待全部的服務器都應答日誌複製的 RPC 請求以後纔開始複製操做。

再舉一個例子,限定每次只能有一個進程能夠訪問共享的資源。保衛資源的進程組成了集合 _S_ 。當進程 _p_ 要訪問資源時,它首先要向 _S_ 中的大多數進程徵求許可,大多數進程受權它能夠訪問資源。如今,系統中另一個進程 _q_ 也要訪問這個資源,可是它永遠得到不了大多數保衛進程的許可和受權。只有當進程 _p_ 釋放資源後,進程 _q_ 纔有可能訪問這個資源。更多細節,見論文《法定人數系統的負載、容量和可用性》。

法定人數並不老是指進程的大多數。有時,爲了保證操做的成功,須要更多的進程造成法定人數,例如,由 _N_ 個可能出現拜占庭失效的進程構成的進程組。此時,若是 _f_ 表示可容忍的最多失效進程數,那麼法定人數將是大於 _(N + f) / 2_ 。參見 《可靠且安全的分佈式程序設計導論》。

若是你對這個話題感興趣,有一本專門講分佈式系統法定人數的書:

《法定人數系統及其在存儲和共識中的應用》

分佈式系統中的時間

理解時間及其後果,是分佈式系統最大的問題之一。在平常生活中,咱們都習慣了事件一個接一個發生,按照完美定義的「在……以前發生」(happend before)的順序。若是是一系列分佈式進程,它們交換消息和訪問資源都是併發的。咱們該如何判斷事件的前後發生順序?要回答此類問題,不一樣的進程必須共享一個同步時鐘,還要準確地知道經過網絡交換信息的耗時、 CPU 調度任務的耗時等等。顯然,在現實系統中不可能作到這一點。

有一篇討論上述問題的影響深遠的論文,標題是「時間,時鐘和分佈式系統中的事件順序」。這篇論文引入了邏輯時鐘的概念。邏輯時鐘是爲系統中每一個事件分配一個數字的方法。這些數字與實際的時間無關,而是與分佈式系統節點對事件的處理有關。

邏輯時鐘算法有不少種,像向量時鐘和區間樹時鐘。

我推薦一篇討論分佈式系統時間的文章, Justin Sheehy 寫的「沒有如今」,頗有意思的討論。

我認爲,時間及其在分佈式系統中引發的問題,是須要理解的關鍵概念。咱們必須摒棄同時性(simultaneity)的想法。同時性的想法源自「絕對知識」的老觀念,咱們之前認爲絕對知識是能夠得到的。物理定律告訴咱們,即便是光也須要時間才能從一個地方達到另一個地方,這樣,當光到達咱們的眼睛時,咱們的大腦會處理光傳遞的信息。這是一種舊的世界觀。Umberto Eco在《發明敵人》這本書的「絕對和相對」一章中討論了上述想法。

快速瀏覽 FLP

最後,咱們快速瀏覽只要有一個進程有可能失效,就沒法達成分佈式共識這篇論文,把剛纔學到的有關分佈式系統的各類概念關聯起來。

在摘要的開頭,做者寫道:

共識問題涉及的是由一組進程組成的異步系統,其中一些進程是不可靠的。

在異步系統中,對處理速度或者消息送達時間不作任何假設,其中有些進程可能會崩潰。

這種說法有一個問題。在一般的技術術語中,異步多是指處理請求的方式。以 RPC 爲例,進程 _p_ 向進程 _q_ 發送一個異步請求;在進程 _q_ 處理請求的同時, _p_ 會繼續作別的事情——也就是說, _p_ 不會爲了等待應答而阻塞本身的運行。你看,一樣是叫異步,在這裏的含義與在分佈式系統文獻中的含義徹底不一樣。不瞭解這一點,你很難徹底理解 FLP 論文的第一句話。

接下來,做者寫道:

本文提出一個使人驚奇的結果:系統中哪怕是隻有一個進程可能會失效,就徹底不可能存在異步共識協議。咱們假設系統中不存在拜占庭失效,消息系統也是可靠的——全部消息都會被正確地一次送達。

因而可知,這篇論文討論的系統只存在崩潰-中止(或者說失效-中止)的失效模式,(除了不存在拜占庭模式),也不存在忽略失效模式,由於消息系統是可靠的。

最後,做者還添加下面一條約束:

不假定進程可以檢測出另一個進程是否死亡。也就是說,一個進程沒法區分另一個進程的兩種狀態:死亡(徹底中止運行)或者運行得很慢。

這就是說,本文討論的系統中不存在失效檢測器。

總結一下, FLP 不可能性結果適用於具備下列特徵的異步系統:崩潰-中止失效模式;可靠的消息系統;不存在失效檢測器。不瞭解各類分佈式系統模型的理論,咱們就可能漏掉不少細節,咱們的理解甚至與做者的原意相去甚遠。

想要更詳細地瞭解 FLP 不可能性,請看博客文章: FLP 簡要梳理。

Marcos Aguilera 寫了一篇有意思的論文《共識研究中的那些坑:誤解與問題》,文中討論了 FLP 做爲分佈式系統的一個不可能性結果,它的含義到底是什麼(劇透警告:這裏說的不可能性與停機問題中的不可能性不是一回事兒)。

結束語

如你所知,分佈式系統的學習須要時間投入。分佈式系統是一個很是龐大的議題,每一個子領域都已經有很是多的研究。與此同時,分佈式系統的實現和驗證又是很是複雜的,有不少容易犯錯的細微之處,處理很差的話,咱們實現的分佈式系統就會在乎想不到的狀況下沒法正常地工做。

若是不能正確地理解分佈式系統的底層理論,下列問題甚至更多問題就會發生:選擇錯誤的法定人數,結果致使新的複製算法丟失關鍵數據;選擇很是保守的法定人數,致使應用程序運行變慢,從而知足不了向用戶承諾的服務約定;咱們要解決的問題原本根本不必使用共識,用最終一致性就行,(可是咱們卻選擇使用共識);錯誤地假設系統的定時模型;使用了不符合底層系統屬性的失效檢測器;咱們想優化像 Raft這樣的算法,因而去掉了貌似不相關的一個小步驟,結果卻破壞了算法的安全性保證。

好,我明白了,我不想從新發明分佈式系統輪子,可是面對如此衆多的問題和文獻,我該從哪裏着手呢?在本文開頭,我指出隨機地閱讀論文沒有什麼用處。就像上面提到的 FLP 論文,不瞭解各類定時模型,你就沒辦法理解論文的第一句話。所以,我推薦下列入門書籍:

《分佈式算法》,Nancy Lynch 著。這本書就好像是分佈式系統的聖經,內容涵蓋上面說起的各類模型,包括每種模型涉及的算法。

《可靠且安全的分佈式程序設計指南》,Christian Cachin 等人著。這本書不光導論寫得很是棒,還涵蓋了不少種共識算法。這本書還有個優勢,處處可看法釋算法的僞代碼。

相關文章
相關標籤/搜索