電腦科學家在研究分散式系統理論時使用三種模型型別:
- 同步模型
- 半同步模型
- 非同步模型
同步模型意味著系統內傳送的每條資訊都有一個已知的通訊上限(傳送和接收資訊之間的最大延遲)以及節點或代理之間的處理速度。這意味著你可以確定在一段時間後,一條資訊被錯過了。這種模式適用於極少數情況,如硬體訊號,主要是分散式系統證明的初學者模式。
非同步模式意味著沒有上限。代理和節點無限期地處理和延遲事情是合法的。你永遠無法假設,一條你在過去 15 年中都沒有看到的 "丟失 "資訊不會恰好在明天送達。另一個節點也可能陷入一個持續 500 個世紀的 GC 迴圈,這很好。
在非同步模型上證明某樣東西是有效的,就意味著它在所有其他型別上也是有效的。這是證明的專家模式,在大多數情況下比現實世界的實現更難。
半同步模型是真實世界的欺騙模式。通訊機制和節點的上限無處不在,但它們往往是可配置的,也是未指定的。這就使得協議設計者可以說:"你知道嗎,我們要在其中插入一條 ping 訊息,如果你錯過了太多的 ping 訊息,我們就認為你死了"。
故障模式
故障發生和檢測的方式對於許多演算法都很重要。以下是最常用的:
- 故障停止
- 崩潰故障
- 遺漏故障
- 效能故障
- 拜占庭式的失敗
首先,"故障停止 "意味著如果節點出現問題,每個人都能知道並檢測到它,並能從穩定的儲存中恢復狀態。這在理論和協議上都是簡單的模式,但在實踐中卻很難實現(在某些情況下甚至是不可能的)
崩潰故障意味著,如果節點或代理出現問題,它就會崩潰,然後再也不會回來。你要麼永遠正確,要麼永遠遲到。這在理論上比故障停止更容易設計(但操作起來非常麻煩,因為冗餘是遊戲的名字,永遠都是)。
遺漏故障意味著你必須給出符合協議的正確結果,否則就永遠無法回答。
效能故障則假定,在傳送資訊內容方面遵守協議的同時,也有可能延遲傳送結果。
拜占庭故障意味著任何事情都可能出錯(包括有人故意用壞軟體冒充好軟體來破壞協議)。有一類特殊的可檢測認證的拜占庭故障,它至少會限制你不能偽造來自其他節點的其他資訊,但這只是可選項。拜占庭模式是最糟糕的。
在預設情況下,大多數分散式系統理論都假定不存在腐敗和故意破壞的壞人或代理,而由區塊鏈和某些形式的包管理來解決混亂的故障。
大多數現代論文和文章都試圖堅持使用崩潰或故障停止故障,因為它們往往是實用的。
共識
這是分散式系統的核心問題之一:系統中的所有節點或代理如何就一個值達成一致?它之所以如此重要,是因為如果能就一個值達成一致,就能做很多事情。
選擇一個非常有用的值,最常見的例子就是選出一個執行決策的領導者的名字,這樣你就不用再建立更多的共識了,因為共識太痛苦了。
關於什麼是共識,存在各種說法,包括每個人都完全同意?(強)還是隻是多數?(t-resilient),以及在各種同步或失敗模型中提出同樣的問題。
需要注意的是,雖然像 Paxos 這樣的經典協議會使用領導者來確保一致性,並在保持一致的同時加快執行速度,但很多系統都會放棄這些要求。
FLP 結果
代表費舍爾-林奇-帕特森(Fischer-Lynch-Patterson),他們是 1985 年一篇論文的作者,該論文指出,在純粹的非同步模型中,只要有可能出現任何形式的故障,哪怕只是延遲,所有參與者都同意某個值的正確共識是無法解決的(儘管在同步模型中是可以解決的)。
這篇論文是該領域最有影響力的論文之一,因為它引發了其他學者的大量工作,以確定分散式系統中到底發生了什麼。
細節閱讀
- original paper
- blog post review (archive)
故障檢測
FLP 的研究結果表明,故障檢測對於事物的執行具有超級關鍵的作用,在此之後,許多電腦科學人員開始研究故障檢測的確切含義。
這項工作很難,而且往往沒有我們希望的那麼令人印象深刻。故障檢測器有強檢測器和弱檢測器之分。前者意味著所有有故障的程序最終都會被所有無故障的程序識別出來,而後者則意味著只有一些無故障的程序才能發現有故障的程序。
然後是準確度:
- 沒有崩潰的程序不會被懷疑崩潰
- 有可能根本就沒有懷疑過非故障程序
- 你可能會因為混亂而感到困惑,但在某些時候,非故障程序不再被懷疑為壞程序
- 在某些時候,至少有一個非故障程序不會被懷疑
你可能會意識到,一個強大且完全準確的檢測器(可以說是完美的)意味著你會得到一個共識,而既然在一個完全非同步的系統模型中,共識是不可能真正實現的,那麼你能可靠檢測到的東西就有了硬性的限制。
這往往就是半同步系統模型的意義所在:如果將大於 T 的延遲視為故障,那麼就可以開始進行充分的故障檢測。
See this slide deck for a decent intro
CAP 定理
CAP 定理在很長一段時間內都只是一個猜想,但在 2000 年代初得到了證明,從而產生了許多最終一致的資料庫。
分散式系統有三個特性:
- 一致性:每次向系統寫入並從系統中讀取時,都會得到寫入的值或更新值。
- 可用性:每次請求都會得到響應(包括讀取和寫入)
- 分割槽容忍性:網路可能會丟失資訊
從理論上講,你可以得到一個既可用又一致的系統,但這隻適用於完美網路上的同步模型。這種情況並不存在,因此在實際應用中,P 總是存在的。
CAP 定理的基本原理是,在給定 P 的情況下,你必須選擇 A(繼續接受寫入並可能損壞資料)或 C(停止接受寫入以儲存資料,並當機)。
改進
CAP 在實際應用中比較嚴格。網路中並非所有分割槽都等同,也並非所有一致性級別都相同。
為 CAP 定理增加靈活性的兩種最常見方法是產量/收穫模型和 PACELC。
產量/收成模型(Yield/Harvest)本質上是說,你可以用不同的方式來看待系統:產量Yield是指你滿足請求的能力(如正常執行時間),收成Harvest是指你實際能返回的所有潛在資料的比例。搜尋引擎就是一個常見的例子,當搜尋引擎忽略某些搜尋結果以更快地做出響應時,就會透過減少收穫量來提高產量並更頻繁地做出響應。
PACELC 還認為,最終一致性資料庫過於嚴格。在網路分割槽的情況下,您必須在可用性和一致性之間做出選擇,但在其他情況下--當系統正常執行時--您必須在延遲和一致性之間做出選擇。我們的想法是,你可以決定為了可用性而降低一致性(但只有在真正需要時才這樣做),也可以決定始終放棄一致性,因為你必須快速執行。
需要注意的是,你不可能超越 CAP 定理(只要你尊重證明該定理的模型),任何聲稱能做到這一點的人通常都是蛇蠍心腸的推銷員。
Resources
- CAP visual proof
- You can't sacrifice partition tolerance
- PACELC
多年來,關於 CAP 定理和各種討論的重述不計其數;儘管許多人一直試圖提出這些結果非常可靠,無關緊要的論點,但這些結果在數學上已經得到了證明。
資訊傳遞定義
資訊可以按不同順序傳送零次或多次。下面介紹一些術語來定義它們:
- 單播是指資訊只傳送給一個實體
- 任播(anycast)是指向任何有效實體傳送資訊
- 廣播是指將資訊傳送給所有有效實體
- 原子廣播(atomic broadcast)或總順序廣播(total order broadcast)是指系統中的所有非故障行為體都以相同的順序接收相同的資訊,無論該順序是什麼
- 流言(gossip)是指在對等體之間轉發資訊,希望最終每個人都能收到所有資訊的協議系列。
- 至少傳遞一次是指每條資訊將被傳遞一次或多次;監聽者希望看到所有資訊,但可能不止一次
- 最多傳送一次是指每個傳送者只傳送一次資訊。監聽者有可能永遠看不到。
- 完全一次傳送是指每條資訊保證只被傳送和看到一次。這是一個很好的理論目標,但在實際系統中是不可能實現的。最終只能透過其他方法來模擬(例如,將原子廣播與特定標誌和排序保證結合起來)
關於順序:
- 總排序是指所有資訊只有一種嚴格的排序和比較方式,就像 3 總是大於 2 一樣。
- 部分排序意味著某些資訊可以與某些資訊進行比較,但不一定是所有資訊。例如,我可以決定,對金鑰 k1 的所有更新可以相互之間有一個總順序,但獨立於對金鑰 k2 的更新。因此,在所有金鑰的所有更新之間存在部分順序,因為 k1 的更新與 k2 的更新沒有任何資訊關聯。
- 因果順序指的是,所有依賴於其他資訊的資訊都是在這些資訊之後收到的(你不可能在瞭解一個使用者之前就知道他的頭像)。它是部分順序的一種特殊型別。
沒有 "最佳 "排序,每種排序都提供了不同的可能性,並伴隨著不同的成本、最佳化和相關故障模式。
冪等性
冪等性非常重要,需要有自己的條目。閒置意味著,當資訊被多次檢視、重發或重放時,它們對系統的影響不會與只傳送一次時有什麼不同。
常見的策略是讓每條資訊都能引用以前看過的資訊,這樣就可以定義一個排序來防止重播舊資訊,設定唯一的 ID(如事務 ID)和一個儲存空間來防止重播事務,等等。
Idempotence is not a medical condition
狀態機複製
這是一個理論模型,根據該模型,如果給定相同的狀態序列並對其進行相同的操作(不考慮各種非確定性),所有狀態機最終都會得到相同的結果。
這個模型對於大多數可靠的系統都至關重要,因為這些系統都會嘗試以相同的順序向所有子系統重放所有事件,從而確保所有地方的資料集都是可預測的。
這通常是透過選擇一個領導者來實現的;所有的寫入都透過領導者完成,所有的跟隨者都會得到系統的一致複製狀態,使他們最終成為領導者,或將自己的狀態扇形擴充套件到其他參與者。
基於狀態的複製
基於狀態的複製在概念上可能比狀態機複製更簡單,其理念是,如果只複製狀態,最終就能得到狀態!
問題是,要做到快速高效非常困難。如果你的狀態有幾兆兆位元組那麼大,你可不想每次操作都要重新傳送。常見的方法包括對資料進行拆分、雜湊和分塊,以檢測變化並只傳送變化的部分(想想 rsync);用梅克爾樹merkle trees 來檢測變化;或者對原始碼打補丁。
實用事項
這裡有許多關於各種系統設計元素的資源值得挖掘。
系統設計中的端到端論證
分散式系統的系統設計基礎實踐方面:
- 傳送出去的資訊不一定會被另一方收到
- 另一方收到的資訊不一定是另一方真正讀取的資訊
- 另一方讀取的資訊不一定是另一方已經執行的資訊
結論是,如果你希望任何資訊都是可靠的,你就需要端到端確認,通常由應用層編寫。
- Overview by the morning paper
- Actual paper
- Wikipedia page
這些想法是 TCP 協議設計的基礎,但作者也指出,僅僅停留在協議層面是不夠的,還必須有應用層的參與。
分散式計算的謬誤
這些謬誤是
- 網路是可靠的
- 延遲為零
- 頻寬是無限的
- 網路是安全的
- 拓撲結構不會改變
- 只有一個管理員
- 傳輸成本為零
- 網路是同質的
完整解釋: the paper.
常見的實際故障模式
在實踐中,當你從電腦科學轉向工程學時,你會發現故障型別更加多樣化,但也可以對映到任何理論模型。
本節非正式地列出了系統中常見的問題源。有關其他常見情況,請參閱 CAP 定理清單:the CAP theorem checklist
網路分裂
有些節點可以互相通話,但有些節點卻無法與其他節點聯絡。一個常見的例子是,基於美國的網路可以在內部正常通訊,基於歐盟的網路也可以,但兩者都無法互相通話
非對稱網路分裂
節點組之間的通訊是不對稱的。例如,假設美國網路可以向歐盟網路傳送資訊,但歐盟網路卻無法作出回應。
這種情況在使用 TCP 時比較少見(儘管以前也發生過),而在使用 UDP 時則可能很常見。
腦裂
許多系統處理故障的方法是保持多數系統繼續執行。當網路分裂的雙方都認為自己是領導者,並開始做出相互衝突的決定時,就會出現分裂腦。
超時
超時尤其棘手,因為它們是非確定性的。它們只能從一端觀察到,而且你永遠不知道最終被解釋為失敗的超時是否真的是失敗,或者只是由於網路、硬體或 GC 暫停造成的延遲。
有時,如果資訊已被看到,重傳就不安全了(即它不是冪等的),而超時基本上使人無法知道重傳是否安全:資訊是否已被執行、丟棄,還是仍在傳輸中或在某個緩衝區中?
排序導致的報文丟失
一般情況下,使用 TCP 和碰撞往往意味著很少有報文在系統間丟失,但經常出現的情況包括
- 節點當機(或軟體崩潰)幾秒鐘,在此期間錯過了一條不會重複的資訊
- 在不同節點之間臨時接收更新。例如,服務 A 在匯流排(無論是 Kafka 還是 RMQ)上釋出的訊息最終可能被服務 B 讀取、轉換或執行並重新發布,而服務 C 有可能先於 A 讀取 B 的更新,從而造成因果關係問題。
時鐘漂移
並非所有系統上的所有時鐘都能正確同步(即使使用 NTP),而且同步速度也會不同。
使用時間戳對事件進行排序幾乎肯定會產生錯誤,如果時間戳來自多臺計算機,錯誤就會更多。
客戶端是系統的一部分
一個非常常見的誤區是忘記了參與分散式系統的客戶端也是系統的一部分。如果客戶端無法理解其接收到的事件或資料,伺服器端的一致性就不一定有多大價值。
對於資料庫客戶端來說,這一點尤其隱蔽,因為它們在進行非瞬時事務處理時會超時,而且無法知道是否可以再次嘗試。
從多個備份恢復
單個備份很容易處理。多重備份會遇到一個問題,即一致切割(高階檢視)和分散式快照,這意味著並非所有備份都是在同一時間進行的,這會帶來不一致性,可被視為資料損壞。
好在沒有很好的解決辦法,每個人都會遭受同樣的痛苦。
一致性模型
有幾十種不同級別的一致性,維基百科、彼得-貝利斯(Peter Bailis)的相關論文或凱爾-金斯伯裡(Kyle Kingsbury)的相關文章都對它們進行了概述
線性化是指每個操作都是原子性的,不會受到其他操作的影響,就好像所有操作都是一次執行一個一樣。順序是已知的、確定的,在某個寫入開始後開始的讀取將能看到該資料。
- 序列化意味著,雖然所有操作看起來都是原子操作,但並不保證這些操作會以何種順序進行。這意味著某些操作可能在另一個操作之後開始,但在另一個操作之前完成,只要隔離維護得當,這並不是問題。
- 順序一致性是指,即使操作可能是不按順序進行的,它們看起來也是按順序進行的。
- 因果一致性是指,只有在邏輯上相互依賴的操作才需要彼此排序
- 已提交讀取的一致性是指任何已提交的操作都可在系統中繼續讀取
- 可重複讀取是指在一個事務中,多次讀取相同的值總會得到相同的結果
- 讀寫一致性是指您完成的任何寫入操作都必須能被隨後的同一客戶端讀取
- 最終一致性(Eventual Consistency)是一種特殊的一致性測量方法,它表示系統可以不一致,只要最終能再次保持一致。因果一致性就是最終一致性的一個例子。
- 強最終一致性與最終一致性類似,但要求併發更新之間不能發生衝突。這通常是 CRDT 的特點。
需要注意的是,雖然這些定義具有明確的語義,學術界也傾向於尊重這些定義,但在業界各個專案或供應商的文件中,這些定義並沒有被統一採用或得到尊重。
資料庫事務作用域
預設情況下,大多數人都認為資料庫事務是可線性化的,但它們往往不是,因為預設情況下線性化太慢了。
每個資料庫可能有不同的語義,因此以下連結可能涵蓋了最主要的語義。
- PostgreSQL
- MySQL (depends on the storage engine used)
- Transact-SQL (most of Microsoft's products)
- Oracle
請注意,雖然 PostgreSQL 文件可能是介紹該主題的最清晰、最易懂的文件,但不同的供應商會對相同的標準事務作用域賦予不同的含義。
邏輯時鐘
這些資料結構可以在資訊或狀態轉換之間建立總排序或部分排序。
最常見的有:
- Lamport 時間戳,這只是一個計數器。它們可以在不被發現的情況下悄悄地粉碎衝突資料
- 向量時鐘,每個節點包含一個計數器,在看到的每條訊息上遞增。他們可以檢測資料和操作中的衝突。
- 版本向量類似於向量時鐘,但僅更改狀態變化的計數器,而不是更改所有看到的事件
- 點分版本向量是奇特的版本向量,允許跟蹤客戶端與伺服器通訊時感知到的衝突。
- 間隔樹時鐘嘗試透過需要更少的空間來儲存特定於節點的資訊並允許一種內建垃圾收集來解決其他時鐘型別的問題。它還擁有有史以來最好的論文之一。
CRDT
CRDT 本質上是限制可以執行的操作的資料結構,以便它們永遠不會發生衝突,無論它們以何種順序完成或發生的併發程度如何。
可以將其視為關於某人如何編寫永遠不會出錯但只留下數學知識的分散式 Redis 的規範。
這仍然是一個活躍的研究領域,無數的論文和變體不斷湧現。
- 大原完整論文
- 一個不錯的介紹
- 另一個介紹
- 在實踐中同步 CRDT 的挑戰
其他有趣的材料
- 凱爾·金斯伯裡 (Kyle Kingsbury) 的資料庫評論
- 這份材料清單很棒
- 領導者選舉演算法
- Raft(簡單協議介紹)
- Paxos 變得簡單
- Paxos(兼職議會)
- Paxos made Live(谷歌在使 paxos 工作方面的經驗報告)
- Paxos 變體
- ZAB(動物園管理員演算法)
- 迪納摩論文
將所有這些觀點結合在一起的聖經是Martin Kleppmann 的《設計資料密集型應用程式》 。但請注意,我認識的每個絕對喜歡這本書的人都是透過閱讀大量論文而在分散式系統方面擁有良好基礎的人,並且非常感謝將所有這些都放在一個地方。我見過的大多數人在讀書俱樂部中閱讀這本書,目的是更好地瞭解分散式系統,但有時仍然發現它具有挑戰性和令人困惑,並且受益於周圍有人可以向他們提出問題以彌合一些差距。它仍然是我能想象到的所有東西都集中在一個地方的最清晰的來源。