徹底厘清真實(shí)世界中的分布式系統(tǒng)
“求知之路漫長(zhǎng)喲,不知何處是盡頭。我們一路求索,終于有跡可循。這為我們帶來了希望,驅(qū)散了恐懼。”
(譯者注:「Down the Rabbit Hole」是一句俗語(yǔ),源自小說《愛麗絲漫游仙境》(Alice's Adventures in Wonderland),比喻對(duì)未知的探索。)
分布式系統(tǒng)領(lǐng)域的文獻(xiàn)雖然多,我作為一名實(shí)踐者,卻發(fā)現(xiàn)如果你不是科班出身,就不知道怎樣入門或者如何綜合這么多的知識(shí)。這篇文章提供了一個(gè)不是那么學(xué)術(shù)的思路,幫助你理解分布式系統(tǒng)的各種設(shè)計(jì)思想。也就是說,本文沒提出什么新的設(shè)計(jì)思想,而是構(gòu)建了一個(gè)框架,人們可以按照這個(gè)框架去研究一些有影響的設(shè)計(jì)思想。文中列出的參考文獻(xiàn),是研究分布式系統(tǒng)的絕佳起點(diǎn)。特別是,我們將審視幾個(gè)形式化結(jié)果,以及不那么形式化的一些設(shè)計(jì)原則,這是我們討論分布式系統(tǒng)設(shè)計(jì)的基礎(chǔ)。
“這是你最后的機(jī)會(huì),一旦選定就沒得回頭了。要是分布式系統(tǒng)領(lǐng)域也有紅藥丸/藍(lán)藥丸就好了。分布式系統(tǒng)是如此復(fù)雜,我們徹底搞清楚它吧。”
(譯者注:這一段的開頭引用《黑客帝國(guó)》(The Matrix)中 Neo 的話。選擇紅藥丸,回到現(xiàn)實(shí)世界,有可能是痛苦的;選擇藍(lán)藥丸,繼續(xù)生活在幻想的幸福當(dāng)中。這里的含義似乎是你是選擇理解真實(shí)的分布式系統(tǒng)(過程可能很痛苦),還是繼續(xù)保持無知的幸福呢?)
指導(dǎo)原則
為了厘清分布式系統(tǒng)的設(shè)計(jì),很重要的一點(diǎn)是明確立論的指導(dǎo)原則或者說是定理,其中最基礎(chǔ)的一個(gè)可能是「兩將軍問題」,首先由 Akkoyunlu 等人在論文「網(wǎng)絡(luò)通信設(shè)計(jì)中的一些約束和權(quán)衡」中提出,而在 1975 年版和 1978 年版的《數(shù)據(jù)庫(kù)操作系統(tǒng)注記》中, Jim Gray 對(duì)兩將軍問題的討論,使得這個(gè)問題開始被人們所熟知。兩將軍問題表明:通過不可靠網(wǎng)絡(luò)通信的兩個(gè)進(jìn)程不可能達(dá)成一致的決定。兩將軍問題非常接近于必須保證下列條件成立的二元共識(shí)問題(“攻擊”或者“不攻擊”):
- 終止(Termination):所有正確的進(jìn)程都會(huì)決定某個(gè)取值(活性/liveness);
- 合法性(Validity):所有正確的進(jìn)程,如果它決定的取值是 v ,那么這個(gè) v 必然是由某個(gè)正確的進(jìn)程提議的(非平凡/non-triviality);
- 誠(chéng)實(shí)性(Integrity):所有正確的進(jìn)程,最多只決定一個(gè)取值 v ,并且這個(gè) v 是正確的取值(安全性/safety);
- 一致性(Agreement):所有正確的進(jìn)程決定的取值是相同的(安全性/safety)。
很顯然,任何有用的分布式算法都涉及活性和安全性屬性。如果再考慮到網(wǎng)絡(luò)是異步的、存在崩潰失效,問題就更復(fù)雜了:
- 異步:消息有可能被無限延遲,但是最終會(huì)被投遞;
- 崩潰失效:進(jìn)程有可能無限停機(jī)。
對(duì)上述情境的思考,引導(dǎo)我們?nèi)チ私鈸?jù)稱是最重要的分布式系統(tǒng)理論結(jié)果之一: FLP 不可能性,由 Fischer, Lynch 和 Patterson 在 1985 年論文「只要存在一個(gè)可能失效的進(jìn)程就不可能達(dá)成共識(shí)」中首次提出。這個(gè)結(jié)果表明兩將軍問題是不可能解決的。在崩潰-失效模型中,如果進(jìn)程完成工作且給出響應(yīng)的耗時(shí)沒有上限,我們就不可能區(qū)分下面兩種情況:進(jìn)程已經(jīng)崩潰或者只是響應(yīng)的耗時(shí)比較長(zhǎng)。 FLP 結(jié)果還表明,在異步環(huán)境中,只要至少有一個(gè)進(jìn)程有可能失效,就不存在能夠確定解決共識(shí)問題的算法。也就是說,存在崩潰-失效的異步環(huán)境中,不可能存在完美的失效檢測(cè)器。
(譯者注:把 failure 翻譯為「失效」,意味著系統(tǒng)已經(jīng)完全不能工作;把 fault 翻譯為「故障」,意思是系統(tǒng)組件有問題,不能按照原先設(shè)計(jì)目的正常地工作。)
討論故障容忍(fault-tolerant)系統(tǒng)時(shí),很重要的一點(diǎn)是把拜占庭故障(實(shí)質(zhì)上就是任意的故障)考慮在內(nèi)。此類故障包括但不限于:試圖破壞系統(tǒng)的攻擊。例如,一次安全攻擊可能會(huì)生成或者偽造消息。拜占庭將軍問題是兩將軍問題的泛化版,它描述的就是拜占庭故障。拜占庭故障容忍是指檢測(cè)出或者屏蔽掉大量的拜占庭故障,保護(hù)系統(tǒng)免受威脅。
我們?yōu)槭裁慈绱酥匾暪沧R(shí)?因?yàn)樗墙鉀Q分布式系統(tǒng)設(shè)計(jì)中很多重要問題的關(guān)鍵。領(lǐng)導(dǎo)人選舉(leader election)要實(shí)現(xiàn)共識(shí),這樣才能動(dòng)態(tài)選出一個(gè)協(xié)調(diào)者,避免單點(diǎn)失效。分布式數(shù)據(jù)庫(kù)要實(shí)現(xiàn)共識(shí),這樣才能保證不同節(jié)點(diǎn)的數(shù)據(jù)是一致的。消息隊(duì)列要實(shí)現(xiàn)共識(shí),這樣才能支持消息投遞事務(wù)或保證消息投遞的順序。分布式初始化(init)系統(tǒng)要實(shí)現(xiàn)共識(shí),這樣才能協(xié)調(diào)不同的進(jìn)程。共識(shí)根本就是分布式程序設(shè)計(jì)的一個(gè)重要問題。
人們一次又一次的證明,無論是局域網(wǎng)還是廣域網(wǎng),它們經(jīng)常是不可靠的,總體上也是異步的。這給分布式系統(tǒng)的設(shè)計(jì)帶來真切而巨大的挑戰(zhàn)。
“這些不可能性結(jié)果不單單有學(xué)術(shù)意義,受此啟發(fā),大量分布式系統(tǒng)及設(shè)計(jì)開始涌現(xiàn),這些系統(tǒng)在網(wǎng)絡(luò)失效時(shí)提供了不同的保證。”
L. Peter Deutsch 寫的「有關(guān)分布式計(jì)算的幾個(gè)謬論」是研究分布式系統(tǒng)理論的絕佳起點(diǎn)。文中列舉了很多新手會(huì)誤以為真的假設(shè),其中第一條就是“網(wǎng)絡(luò)是可靠的”。這些實(shí)際上不成立的假設(shè)包括:
- 網(wǎng)絡(luò)是可靠的。
- 延遲為零。
- 帶寬是無限的。
- 網(wǎng)絡(luò)是安全的。
- 拓?fù)洳粫?huì)改變。
- 肯定有一個(gè)管理員。
- 傳輸?shù)拇鷥r(jià)為零。
- 網(wǎng)絡(luò)是同質(zhì)的。
最近, CAP 定理被認(rèn)真審視,人們爭(zhēng)論這個(gè)定理的作用是否被夸大了。盡管如此, CAP 定理仍然是一個(gè)有用的工具,它能幫助我們建立分布式系統(tǒng)的基本權(quán)衡因素,認(rèn)清廠商玩的花招。 Gilbert 和 Lynch 合寫的「對(duì) CAP 定理的看法」 明確了易出故障(fault-prone)系統(tǒng)固有的安全性(safety)與活性(liveness)之間的權(quán)衡,而 Fox 和 Brewer 合寫的「完備度、完成概率和可擴(kuò)展的容忍系統(tǒng)」從更實(shí)用角度描述了 CAP 定理的特征。我將一直毫不含糊地說, CAP 定理在分布式系統(tǒng)領(lǐng)域的地位非常重要,對(duì)分布式系統(tǒng)設(shè)計(jì)者和實(shí)踐者來說,它具有重大的意義。
重燃希望
根據(jù)前面這些理論結(jié)果,很多分布式算法,包括實(shí)現(xiàn)線性化操作、序列化事務(wù)和領(lǐng)導(dǎo)人選舉的算法,都沒什么用。果真如此嗎?當(dāng)然不是。只要精心設(shè)計(jì),分布式系統(tǒng)不用靠撞大運(yùn)就能保持正確性。
首先需要指出, FLP 定理并沒有說共識(shí)是無法達(dá)成的,而是說在有限時(shí)間內(nèi)不一定能達(dá)成。其次, FLP 定理討論的是不受控制的系統(tǒng)。在同步系統(tǒng)中,進(jìn)程間消息投遞的耗時(shí)有一個(gè)上限;在異步系統(tǒng)中則沒有固定的限制。實(shí)際的系統(tǒng)一般表現(xiàn)為部分同步(partial synchrony), Dwork 和 Lynch 在「部分同步系統(tǒng)的共識(shí)」一文中描述了部分同步的兩個(gè)模型。在第一個(gè)模型中,上限是固定的但是預(yù)先不知道;在第二個(gè)模型中,上限是已知的,但只是從某個(gè)未知的時(shí)間點(diǎn) T 開始才保證這個(gè)上限成立。Dwork 和 Lynch 針對(duì)這兩種模型(搭配不同的故障模型),分別給出相應(yīng)的能夠容忍故障的共識(shí)協(xié)議。
Chandra 和 Toueg 在「可靠分布式系統(tǒng)中的不可靠失效檢測(cè)器」介紹了不可靠失效檢測(cè)器的概念。每一個(gè)進(jìn)程都有一個(gè)本地、外部的失效檢測(cè)器,這個(gè)檢測(cè)器有可能出錯(cuò)。失效檢測(cè)器監(jiān)控系統(tǒng)中的部分進(jìn)程,維護(hù)一個(gè)它懷疑已經(jīng)崩潰進(jìn)程的列表。檢測(cè)失效的方法很簡(jiǎn)單:檢測(cè)器定期向某個(gè)進(jìn)程發(fā)送打招呼消息,如果超過某個(gè)耗時(shí)上限(2×消息來回的最大可能耗時(shí)),仍然沒有收到該進(jìn)程的響應(yīng),就把它列入懷疑名單。檢測(cè)器有可能犯錯(cuò),把正確的進(jìn)程列入懷疑名單中。不過,如果檢測(cè)器在后續(xù)時(shí)段又收到進(jìn)程的響應(yīng),會(huì)自動(dòng)糾錯(cuò),把這個(gè)進(jìn)程從懷疑名單去掉。在一個(gè)條件稍微放松的系統(tǒng)模型中,只要有失效檢測(cè)器,即使它是不可靠的,也能解決共識(shí)問題。
共識(shí)保證了不同的進(jìn)程就某個(gè)取值達(dá)成一致,而原子化廣播(atomic broadcast)保證了每一個(gè)進(jìn)程按照相同的順序投遞同一個(gè)消息。在上面那篇論文中,作者證明了共識(shí)和原子化廣播彼此是等價(jià)的。因此, FLP 等不可能性結(jié)果同樣適用于原子化廣播。有些協(xié)調(diào)服務(wù),如 Apache ZooKeeper ,就用到原子化廣播。
在《可靠且安全的分布式程序設(shè)計(jì)導(dǎo)論》一書中,Cachin, Guerraoui 和 Rodrigues 指出很多實(shí)踐系統(tǒng)可以被認(rèn)為是部分同步的:
分布式系統(tǒng)通常表現(xiàn)為一個(gè)同步系統(tǒng)。更準(zhǔn)確地說,我們所知的大部分系統(tǒng),在大部分時(shí)間內(nèi),投遞消息的耗時(shí)有一個(gè)上限。當(dāng)然,在有的時(shí)段,系統(tǒng)又是異步的。例如,網(wǎng)絡(luò)過載,或者某個(gè)進(jìn)程因?yàn)閮?nèi)存不夠而運(yùn)行得緩慢。更典型的例子,進(jìn)程收發(fā)消息的緩沖區(qū)有可能發(fā)生溢出,導(dǎo)致消息丟失,此時(shí)投遞消息的耗時(shí)肯定超過通常的上限。消息重傳有助于保證通信鏈接的可靠性,同時(shí)又引入不可預(yù)測(cè)的延遲。從這個(gè)意義上,實(shí)際的系統(tǒng)是部分同步的。
我們注意到,部分同步只是說最終保證消息投遞的耗時(shí)有一個(gè)固定的限制,但最終是指什么時(shí)候,沒有明確指出。類似地,我們稱這樣的系統(tǒng)是最終同步的。這里的最終同步,并不是說過了一段時(shí)間后系統(tǒng)就永遠(yuǎn)是同步的,也不是說系統(tǒng)開始是異步的,一段時(shí)間之后變成同步的。相反,最終同步是指系統(tǒng)有時(shí)是異步的,此時(shí)消息投遞的耗時(shí)有可能是無限長(zhǎng),但是也存在系統(tǒng)同步的時(shí)段,足夠一個(gè)算法做有用的工作或者運(yùn)行完。關(guān)鍵是要記住,異步系統(tǒng)不提供任何定時(shí)保證。
最后,在「分布式共識(shí)所需的最少同步」一文中, Dolev, Dwork 和 Stockmeyer 描述了一種分布式共識(shí)協(xié)議叫做 t-復(fù)原(t-resilient),它能在最多 t 個(gè)進(jìn)程失效時(shí)保證系統(tǒng)仍然正常地工作。本文給出幾個(gè)關(guān)鍵的系統(tǒng)參數(shù)和同步條件,描述不同的參數(shù)和條件對(duì)算法的影響。可以證明,在某些模型中共識(shí)是可達(dá)的,在另外一些模型中則不行。
依靠法定多數(shù)(quorum),能夠?qū)崿F(xiàn)容忍故障的共識(shí)。直覺上,如果大多數(shù)進(jìn)程能就每一個(gè)決定達(dá)成一致,即使出現(xiàn)故障,也至少有一個(gè)進(jìn)程了解完整的歷史。
在某些系統(tǒng)模型中,不可能達(dá)成確定性共識(shí),許多有用的算法也因此無法實(shí)現(xiàn)。但是,大部分實(shí)際系統(tǒng)對(duì)應(yīng)的模型能夠規(guī)避這一點(diǎn)。不管怎樣,這都顯示出分布式系統(tǒng)固有的復(fù)雜性,以及解決特定問題所需的嚴(yán)格性。
從理論轉(zhuǎn)向?qū)嵺`
上述理論有什么實(shí)踐意義呢?對(duì)于初學(xué)者而言,這意味著分布式系統(tǒng)沒有表面看起來那么簡(jiǎn)單。不認(rèn)識(shí)到這一點(diǎn),人們就會(huì)在文檔中不確切地描述權(quán)衡因素,還有很多因?yàn)檎J(rèn)識(shí)不足而導(dǎo)致數(shù)據(jù)丟失和違反安全性的例子。我們需要重新考慮分布式系統(tǒng)的設(shè)計(jì)方式,把焦點(diǎn)從系統(tǒng)屬性及保證轉(zhuǎn)向行業(yè)規(guī)則和應(yīng)用的不變量。
我最鐘意的一篇論文是 Saltzer, Reed 和 Clark 寫的「系統(tǒng)設(shè)計(jì)中的端到端原則」。這篇論文很好讀,它提出了一個(gè)非常有說服力的設(shè)計(jì)原則,幫助人們搞清楚究竟應(yīng)該在分布式系統(tǒng)的哪一層實(shí)現(xiàn)所需的功能。端到端原則是說在系統(tǒng)的底層實(shí)現(xiàn)功能有可能是多余的,或者與付出的代價(jià)相比,這樣做的用處不大。很多時(shí)候,外部保證比內(nèi)部保證更有意義,也就是說應(yīng)該在應(yīng)用層提供保證,而不是依靠子系統(tǒng)、中間件或者系統(tǒng)的底層提供保證。
我們以“設(shè)計(jì)周全的文件傳輸”為例說明端到端原則。某個(gè)文件保存在計(jì)算機(jī) A 的硬盤的文件系統(tǒng)中, A 通過通信網(wǎng)絡(luò)與計(jì)算機(jī) B 相連?,F(xiàn)在要求把這個(gè)文件從計(jì)算機(jī) A 無損地傳輸?shù)接?jì)算機(jī) B ,在此過程中有可能出現(xiàn)各種失效。換言之,這是一個(gè)文件傳輸應(yīng)用程序,依賴底層存儲(chǔ)和網(wǎng)絡(luò)的抽象。開發(fā)者考慮到下列問題有可能發(fā)生:
- 文件剛寫到計(jì)算機(jī) A 的磁盤時(shí),數(shù)據(jù)是正確的。如果現(xiàn)在讀這個(gè)文件,有可能因?yàn)榇疟P存儲(chǔ)系統(tǒng)的硬件故障而讀到錯(cuò)誤的數(shù)據(jù)。
- 無論是在計(jì)算機(jī) A 還是 B 上,文件系統(tǒng)、文件傳輸程序或者數(shù)據(jù)通信系統(tǒng)在緩沖和復(fù)制文件數(shù)據(jù)時(shí)都可能出錯(cuò)。
- 計(jì)算機(jī) A 或 B 的處理器或者內(nèi)存在緩沖和復(fù)制時(shí)有可能暫時(shí)出錯(cuò)。
- 通信系統(tǒng)有可能丟掉或者改變網(wǎng)絡(luò)包數(shù)據(jù)、丟包或者多次投遞同一個(gè)網(wǎng)絡(luò)包。
- 任何一個(gè)主機(jī)都有可能在文件傳輸過程中(已經(jīng)完成了未知比例的數(shù)據(jù)傳輸)崩潰。
這些本質(zhì)上都屬于拜占庭問題。如果我們逐個(gè)考慮這些威脅,很顯然,即使我們?cè)诘讓訉?shí)現(xiàn)了問題處理程序,高層的應(yīng)用仍然必須檢查問題是否存在。例如,通信系統(tǒng)依靠校驗(yàn)和、重試和網(wǎng)絡(luò)包排序提供可靠的數(shù)據(jù)傳輸。這只是消除了上述第 4 個(gè)威脅。為了克服其余的威脅,文件傳輸應(yīng)用程序仍然需要端到端校驗(yàn)和重試機(jī)制。
在底層構(gòu)建可靠性,代價(jià)太大。不光需要不少的投入,這么做也純屬多余。實(shí)際上,這雖然減少應(yīng)用層重試的頻率,卻在底層了增加不必要的負(fù)擔(dān),最終降低系統(tǒng)的性能。應(yīng)該只靠端到端校驗(yàn)和重試保證正確性,底層的實(shí)現(xiàn)對(duì)此沒什么幫助。通信系統(tǒng)的可靠性和正確性并非那么很重要,在通信層保證復(fù)原性并不能減少應(yīng)用層的負(fù)擔(dān)。實(shí)際上,僅僅依靠底層不可能保證正確性,因?yàn)橄? 2 個(gè)威脅要求編寫正確的程序,但是并非所有的程序都是由文件傳輸應(yīng)用開發(fā)者自己編寫的。
根本上,在底層實(shí)現(xiàn)功能會(huì)引發(fā)兩個(gè)問題。首先,底層不清楚應(yīng)用的需求和語(yǔ)義,這就意味著在底層實(shí)現(xiàn)的功能往往是不充分的,在應(yīng)用層仍然需要實(shí)現(xiàn)類似的功能,這就造成邏輯的重復(fù),如前面例子所示。其次,其他依賴底層的應(yīng)用,即使不需要這些功能,也得承擔(dān)相應(yīng)的代價(jià)。
Saltzer, Reed 和 Clark 把端到端原則視為系統(tǒng)設(shè)計(jì)的“奧坎姆剃刀”原則,他們認(rèn)為,端到端原則有助于指導(dǎo)設(shè)計(jì)系統(tǒng)的層次組織和確定功能在哪一層實(shí)現(xiàn)。
“因?yàn)榻?jīng)常是先確定通信子系統(tǒng)之后,才知道要運(yùn)行的上層應(yīng)用,所以設(shè)計(jì)者必須頂住誘惑,不要試圖為用戶提供超出需要的功能。了解端到端原則,有助于增強(qiáng)抵抗力。”
需要特別指出的是,端到端原則不是萬能藥。它是一個(gè)指導(dǎo)原則,幫助設(shè)計(jì)者從端到端角度思考解決方案,確認(rèn)應(yīng)用的需求,考慮失效的模式。最后,它提供了一種理念:把功能往系統(tǒng)上層移,靠近用到這項(xiàng)功能的應(yīng)用程序。當(dāng)然,凡事都有例外。有時(shí)為了性能優(yōu)化,選擇在底層實(shí)現(xiàn)功能??傊说蕉嗽瓌t主張底層應(yīng)當(dāng)避免承擔(dān)任何超出需要的責(zé)任。在 Google Bigtable 論文的“教訓(xùn)”部分有類似的論述:
“我們學(xué)習(xí)到的另外一個(gè)教訓(xùn)是,在搞清楚新特性將被如何使用之前,不要添加這個(gè)新特性。例如,剛開始時(shí),我們計(jì)劃提供支持通用事務(wù)的 API 。由于我們沒有馬上使用這些 API ,就沒有實(shí)現(xiàn)它們?,F(xiàn)在,我們有很多運(yùn)行在 Bigtable 上的實(shí)際應(yīng)用,我們能夠檢驗(yàn)這些應(yīng)用的真實(shí)需求,結(jié)果發(fā)現(xiàn)大部分應(yīng)用只需要單行事務(wù)。其他需要分布式事務(wù)的使用情景,最重要的一個(gè)是用分布式事務(wù)維護(hù)二級(jí)索引,我們計(jì)劃添加特別的機(jī)制滿足這一需求。這種新機(jī)制的通用性比不上分布式事務(wù),但是更有效率(尤其是執(zhí)行橫跨幾百行的更新操作時(shí)),也更適合我們采用的跨數(shù)據(jù)中心樂觀復(fù)制的模式。”
接下來的討論中,我們把端到端原則視為一個(gè)常識(shí)。
到底由誰來保證
一般來說,我們要靠健壯的算法、事務(wù)管理器和協(xié)調(diào)服務(wù)來維護(hù)一致性和應(yīng)用的正確性。這會(huì)引發(fā)兩個(gè)問題:這些服務(wù)經(jīng)常是不可靠的;還經(jīng)常成為嚴(yán)重的系統(tǒng)性能瓶頸。
分布式協(xié)調(diào)算法很難做到萬無一失。即使是像兩階段提交這樣有效的協(xié)議,也容易受崩潰和網(wǎng)絡(luò)分區(qū)的影響而無法正常工作。更能容忍故障的協(xié)議,像 Paxos 和 Raft ,它們的擴(kuò)展性不佳,只能運(yùn)行在比較小的集群內(nèi),也不能跨越廣域網(wǎng)。像 ZooKeeper 這樣的共識(shí)系統(tǒng)決定了整個(gè)系統(tǒng)的可用性,一旦它宕機(jī)了,你的麻煩就大了。出于性能的考慮,法定多數(shù)通常設(shè)得較小,這種情況并不少見。
于是乎,協(xié)調(diào)系統(tǒng)作為一種基礎(chǔ)設(shè)施,變得既復(fù)雜又脆弱。這太諷刺了,因?yàn)楸緛硎窍肜脜f(xié)調(diào)系統(tǒng)降低整個(gè)系統(tǒng)的脆弱性。另外一方面,消息中間件很大程度上是依靠協(xié)調(diào)為開發(fā)者提供下列有關(guān)消息投遞的保證:有且只有一次、順序、事務(wù)等等。
從傳輸協(xié)議到企業(yè)消息代理,對(duì)投遞保證的依賴都屬于分布式系統(tǒng)設(shè)計(jì)中的反模式。很難正確地處理投遞的語(yǔ)義。尤其是對(duì)分布式消息投遞而言,你想要的往往不是你需要的。重要的是審視其中涉及的權(quán)衡因素,了解這些因素如何影響系統(tǒng)的設(shè)計(jì)(和用戶體驗(yàn)!),權(quán)衡這些因素以便做出更好的設(shè)計(jì)決定。
由于各種失效模式的存在,提供強(qiáng)保證變得很難。實(shí)際上,根據(jù)前面我們討論的兩將軍問題和 FLP 不可能性結(jié)果,有些保證,像有且只有一次的投遞,甚至是不可能提供的。如果你想提供有且只有一次投遞、有序投遞的保證,往往屬于超出需要的過度設(shè)計(jì)和實(shí)現(xiàn)。系統(tǒng)變得難以部署和維護(hù)、脆弱和運(yùn)行慢。提供保證的服務(wù),如果能完美地運(yùn)行,開發(fā)者的開發(fā)工作肯定變得更輕松?,F(xiàn)實(shí)情況是這些服務(wù)很多時(shí)候不能完美地運(yùn)行。你會(huì)在凌晨一點(diǎn)收到警報(bào),不得不查找問題的源頭: 從監(jiān)控服務(wù)看,RabbitMQ 明明一切正常,為什么整個(gè)系統(tǒng)卻接連出現(xiàn)問題?
如果部署在生產(chǎn)環(huán)境的系統(tǒng)依賴此類保證,那你遲早會(huì)遇到一次(往往不止一次)上述的麻煩。最終,所謂的保證就不存在了,因此導(dǎo)致的后果可大可小。這種設(shè)計(jì)系統(tǒng)的方式不光危險(xiǎn),也不可取,尤其是當(dāng)你運(yùn)維一個(gè)大規(guī)模系統(tǒng),特別看重系統(tǒng)的吞吐或者需要提供關(guān)鍵的服務(wù)等級(jí)約定時(shí)。
分布式事務(wù)顯然會(huì)影響性能。協(xié)調(diào)的代價(jià)是昂貴的,因?yàn)檫M(jìn)程不能單獨(dú)繼續(xù)運(yùn)行,這會(huì)限制系統(tǒng)的吞吐、可用性和擴(kuò)展性。 Peter Bailis 有一個(gè)非常棒的演講「沉默是金:避免協(xié)調(diào)的系統(tǒng)設(shè)計(jì)」,他詳細(xì)討論了協(xié)調(diào)的代價(jià)以及如何避免協(xié)調(diào)。他提到一個(gè)特別的例子,其中分布式事務(wù)會(huì)導(dǎo)致系統(tǒng)的吞吐下降 400 倍。
如果不需要協(xié)調(diào),系統(tǒng)可以無限橫向擴(kuò)展,從而極大提高系統(tǒng)的吞吐和可用性。但是有時(shí)協(xié)調(diào)是不可避免的。在《數(shù)據(jù)庫(kù)系統(tǒng)中的協(xié)調(diào)避免》一書中, Bailis 等人回答了一個(gè)關(guān)鍵問題:為了保證正確性,在哪種情況下協(xié)調(diào)是不可避免的?他們提出一個(gè)屬性叫不變量交匯點(diǎn)(invariant confluence, I-confluence),它是安全、無協(xié)調(diào)、可用及收斂的執(zhí)行的充分必要條件。 I-confluence 的本質(zhì)是在應(yīng)用層定義和保持不變性,因?yàn)槲覀冊(cè)谶@里可以用應(yīng)用的語(yǔ)義而不是底層數(shù)據(jù)庫(kù)操作來定義正確性。
不知道應(yīng)用程序的正確性定義(例如, I-confluence 用到的那些不變性),在讀寫模型中,能夠保證的最佳正確性是序列化。
給定事務(wù)集,以及統(tǒng)一分散狀態(tài)的合并函數(shù),就可以判定 I-confluence 是否成立。如果成立,就意味著存在一種保證不變性的無協(xié)調(diào)執(zhí)行策略。如果不成立,意味著這樣的策略不存在,協(xié)調(diào)就是必需的。由此可見, I-confluence 能夠幫我們識(shí)別出何時(shí)需要協(xié)調(diào),何時(shí)不需要。由于是在應(yīng)用層定義和保持不變性,就不會(huì)存在超出需要的設(shè)計(jì)。
回想一下,分布式計(jì)算的同步性(synchrony)只是對(duì)時(shí)間做的假設(shè),所以同步(synchronization)從根本上是兩個(gè)或兩個(gè)以上進(jìn)程隨著時(shí)間推移進(jìn)行的協(xié)調(diào)。我們知道,無需協(xié)調(diào)的系統(tǒng)能提供最優(yōu)的性能和可用性,因?yàn)槊總€(gè)進(jìn)程完全獨(dú)立運(yùn)行。然而,根據(jù) I-confluence 理論,這樣的分布式系統(tǒng)沒什么用或者說是不可能的。 Christopher Meiklejohn 在 Strange Loop 大會(huì)做的演講「分布式、最終一致的計(jì)算」中,用汽車打比方來解釋協(xié)調(diào)。駕駛汽車需要摩擦力,但是只能有非常少量的摩擦點(diǎn)。太多的摩擦點(diǎn)會(huì)出問題或者降低效率。如果把物理時(shí)間看做摩擦力,完全消除它是不可能的,因?yàn)檫@是問題的本質(zhì)屬性。但是我們可以盡量減少它在系統(tǒng)中的使用。通常,可以選用邏輯時(shí)間取代物理時(shí)間,例如,使用 Lamport 時(shí)鐘或者其他沖突消除技術(shù)。有關(guān)這一思路的經(jīng)典介紹,是 Lamport 寫的書《分布式系統(tǒng)的時(shí)間、時(shí)鐘和事件順序》。
系統(tǒng)在執(zhí)行延遲敏感操作時(shí)通常會(huì)完全放棄協(xié)調(diào)。這是非常自然的權(quán)衡選擇,只不過要在文檔中清楚地指出這一點(diǎn)。不幸的是,現(xiàn)實(shí)往往并非如此,這很不應(yīng)該。 I-confluence 提供了一個(gè)有用的協(xié)調(diào)避免框架,我們還能從中學(xué)到更多:重新審視我們現(xiàn)在設(shè)計(jì)分布式系統(tǒng)的方式,看起來這些方式與端到端原則有些背道而馳。
在底層實(shí)現(xiàn)功能,意味著一開始我們就要付出代價(jià)——序列化事務(wù)、線性化讀寫和協(xié)調(diào)。這好像違反了端到端原則。應(yīng)用程序并不關(guān)心原子性、隔離級(jí)別或者線性化,它關(guān)心的是兩個(gè)用戶共享同一個(gè) ID 或者兩個(gè)訂單預(yù)定了同一個(gè)房間或者銀行賬戶有負(fù)結(jié)余,數(shù)據(jù)庫(kù)是不知道這些的。有時(shí),諸如此類的規(guī)則甚至不需要任何代價(jià)昂貴的協(xié)調(diào)。
如果把應(yīng)用規(guī)則和約束編碼成基礎(chǔ)設(shè)施層理解的語(yǔ)言,這會(huì)引發(fā)幾個(gè)問題。首先,必須把應(yīng)用語(yǔ)義無縫地轉(zhuǎn)換成底層操作。以消息傳送為例,應(yīng)用程序并不關(guān)心投遞送達(dá)的保證,它關(guān)心的是這個(gè)消息要干什么。其次,我們不能使用很多通用的解決方案,有時(shí)甚至要專門處理特別的情況。這種處理的實(shí)際擴(kuò)展性如何是未知的。第三,降低了性能,這本來是可以避免的(I-confluence 已經(jīng)揭示了這一點(diǎn))。最后,一切都依賴基礎(chǔ)設(shè)施,希望它能按照設(shè)計(jì)運(yùn)行——往往并非如此。
身為消息平臺(tái)團(tuán)隊(duì)的一員,我經(jīng)歷過無數(shù)次像下面這樣的對(duì)話:
開發(fā)者:“我需要快速消息傳遞。”
我:“可以偶爾丟失消息嗎?” 開發(fā)者:“什么?當(dāng)然不行!我們要求可靠的消息傳遞。” 我:“好,那我們加上投遞確認(rèn)。不過,如果你的應(yīng)用程序在處理消息之前崩潰了,會(huì)出現(xiàn)什么情況?” 開發(fā)者:“我們?cè)谙⑻幚砗髸?huì)確認(rèn)。” 我:“那如果處理完了但是還沒確認(rèn)的時(shí)候程序崩潰了,怎么辦?” 開發(fā)者:“重試唄。” 我:“也就是說允許重復(fù)發(fā)送?” 開發(fā)者:“這個(gè),還是應(yīng)該有且只有一次發(fā)送。” 我:“你不是想快速發(fā)送嗎?” 開發(fā)者:“是啊。對(duì)了,還要保持消息的順序。” 我:“你要求的就是 TCP 。” |
相反,如果重新評(píng)估系統(tǒng)間交互和系統(tǒng) API 及語(yǔ)義,把其中一些特性從基礎(chǔ)設(shè)施移到應(yīng)用層,我們就能構(gòu)建更健壯、更容錯(cuò)和更高性能的系統(tǒng)。就消息傳遞而言,真的需要基礎(chǔ)設(shè)施層保證先入先出順序嗎?系統(tǒng)存在失效情況下要保證分布式消息的順序,同時(shí)還要提供高可用性,這太難了,代價(jià)高。如果消息是可交換的,就沒必要保證消息的順序。同樣,投遞事務(wù)需要又慢又脆弱的協(xié)調(diào),還無法提供應(yīng)用層的保證。如果消息是冪等的,就不需要事務(wù),重試就行了。如果需要應(yīng)用層的保證,那就在應(yīng)用層構(gòu)建,基礎(chǔ)設(shè)施可保證不了。
我特別喜歡 Gregor Hohpe 寫的「咖啡店不用兩階段提交」。這篇文章揭示了,如果我們仿效真實(shí)世界解決分布式系統(tǒng)問題,解決方案會(huì)非常簡(jiǎn)單。我有信心設(shè)計(jì)更好的系統(tǒng),有時(shí)我們只需換個(gè)角度思考問題。事物的運(yùn)作方式蘊(yùn)含著一定的道理,這可沒用到計(jì)算機(jī)或者復(fù)雜的算法。
不要試圖用脆弱、笨重的抽象來掩蓋復(fù)雜性,相反,在設(shè)計(jì)決策時(shí)識(shí)別問題,端到端思考,直面問題。追尋分布式系統(tǒng)之道的道路漫長(zhǎng)而艱難,現(xiàn)在就開始吧。