徹底厘清真實(shí)世界中的分布式系統(tǒng)
為了厘清分布式系統(tǒng)的設(shè)計(jì)首昔,很重要的一點(diǎn)是明確立論的指導(dǎo)原則或者說是定理,其中最基礎(chǔ)的一個(gè)可能是「兩將軍問題」,首先由 Akkoyunlu 等人在論文「網(wǎng)絡(luò)通信設(shè)計(jì)中的一些約束和權(quán)衡」中提出,而在 1975 年版和 1978 年版的《數(shù)據(jù)庫操作系統(tǒng)注記》(http://research.microsoft.com/en-us/um/people/gray/papers/DBOS.pdf)中, Jim Gray 對兩將軍問題的討論构资,使得這個(gè)問題開始被人們所熟知。兩將軍問題表明:通過不可靠網(wǎng)絡(luò)通信的兩個(gè)進(jìn)程不可能達(dá)成一致的決定陨簇。兩將軍問題非常接近于必須保證下列條件成立的二元共識問題(“攻擊”或者“不攻擊”):
終止(Termination):所有正確的進(jìn)程都會決定某個(gè)取值(活性/liveness)吐绵;
合法性(Validity):所有正確的進(jìn)程,如果它決定的取值是 v 塞帐,那么這個(gè) v 必然是由某個(gè)正確的進(jìn)程提議的(非平凡/non-triviality)拦赠;
誠實(shí)性(Integrity):所有正確的進(jìn)程,最多只決定一個(gè)取值 v 葵姥,并且這個(gè) v 是正確的取值(安全性/safety)荷鼠;
一致性(Agreement):所有正確的進(jìn)程決定的取值是相同的(安全性/safety)。
很顯然榔幸,任何有用的分布式算法都涉及活性和安全性屬性允乐。如果再考慮到網(wǎng)絡(luò)是異步的、存在崩潰失效削咆,問題就更復(fù)雜了:
異步:消息有可能被無限延遲牍疏,但是最終會被投遞;
崩潰失效:進(jìn)程有可能無限停機(jī)拨齐。
對上述情境的思考鳞陨,引導(dǎo)我們?nèi)チ私鈸?jù)稱是最重要的分布式系統(tǒng)理論結(jié)果之一: FLP 不可能性,由 Fischer, Lynch 和 Patterson 在 1985 年論文「只要存在一個(gè)可能失效的進(jìn)程就不可能達(dá)成共識」中首次提出瞻惋。這個(gè)結(jié)果表明兩將軍問題是不可能解決的厦滤。在崩潰-失效模型中,如果進(jìn)程完成工作且給出響應(yīng)的耗時(shí)沒有上限歼狼,我們就不可能區(qū)分下面兩種情況:進(jìn)程已經(jīng)崩潰或者只是響應(yīng)的耗時(shí)比較長掏导。 FLP 結(jié)果還表明,在異步環(huán)境中羽峰,只要至少有一個(gè)進(jìn)程有可能失效趟咆,就不存在能夠確定解決共識問題的算法。也就是說梅屉,存在崩潰-失效的異步環(huán)境中值纱,不可能存在完美的失效檢測器。
譯者注:把 failure 翻譯為「失效」履植,意味著系統(tǒng)已經(jīng)完全不能工作计雌;把 fault 翻譯為「故障」,意思是系統(tǒng)組件有問題玫霎,不能按照原先設(shè)計(jì)目的正常地工作凿滤。
討論故障容忍(fault-tolerant)系統(tǒng)時(shí),很重要的一點(diǎn)是把拜占庭故障(實(shí)質(zhì)上就是任意的故障)考慮在內(nèi)庶近。此類故障包括但不限于:試圖破壞系統(tǒng)的攻擊翁脆。例如,一次安全攻擊可能會生成或者偽造消息鼻种。拜占庭將軍問題是兩將軍問題的泛化版反番,它描述的就是拜占庭故障。拜占庭故障容忍是指檢測出或者屏蔽掉大量的拜占庭故障叉钥,保護(hù)系統(tǒng)免受威脅罢缸。
我們?yōu)槭裁慈绱酥匾暪沧R?因?yàn)樗墙鉀Q分布式系統(tǒng)設(shè)計(jì)中很多重要問題的關(guān)鍵投队。領(lǐng)導(dǎo)人選舉(leader election)要實(shí)現(xiàn)共識枫疆,這樣才能動態(tài)選出一個(gè)協(xié)調(diào)者,避免單點(diǎn)失效敷鸦。分布式數(shù)據(jù)庫要實(shí)現(xiàn)共識息楔,這樣才能保證不同節(jié)點(diǎn)的數(shù)據(jù)是一致的。消息隊(duì)列要實(shí)現(xiàn)共識扒披,這樣才能支持消息投遞事務(wù)或保證消息投遞的順序值依。分布式初始化(init)系統(tǒng)要實(shí)現(xiàn)共識,這樣才能協(xié)調(diào)不同的進(jìn)程碟案。共識根本就是分布式程序設(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)。文中列舉了很多新手會誤以為真的假設(shè)惦费,其中第一條就是“網(wǎng)絡(luò)是可靠的”兵迅。這些實(shí)際上不成立的假設(shè)包括:
網(wǎng)絡(luò)是可靠的。
延遲為零薪贫。
帶寬是無限的恍箭。
網(wǎng)絡(luò)是安全的。
拓?fù)洳粫淖儭?/p>
肯定有一個(gè)管理員瞧省。
傳輸?shù)拇鷥r(jià)為零扯夭。
網(wǎng)絡(luò)是同質(zhì)的鳍贾。
最近胸遇, CAP 定理被認(rèn)真審視地啰,人們爭論這個(gè)定理的作用是否被夸大了。盡管如此哼蛆, CAP 定理仍然是一個(gè)有用的工具构拳,它能幫助我們建立分布式系統(tǒng)的基本權(quán)衡因素咆爽,認(rèn)清廠商玩的花招。 Gilbert 和 Lynch 合寫的「對 CAP 定理的看法」 明確了易出故障(fault-prone)系統(tǒng)固有的安全性(safety)與活性(liveness)之間的權(quán)衡置森,而 Fox 和 Brewer 合寫的「完備度斗埂、完成概率和可擴(kuò)展的容忍系統(tǒng)」從更實(shí)用角度描述了 CAP 定理的特征。我將一直毫不含糊地說凫海, CAP 定理在分布式系統(tǒng)領(lǐng)域的地位非常重要呛凶,對分布式系統(tǒng)設(shè)計(jì)者和實(shí)踐者來說,它具有重大的意義盐碱。
重燃希望
根據(jù)前面這些理論結(jié)果把兔,很多分布式算法,包括實(shí)現(xiàn)線性化操作瓮顽、序列化事務(wù)和領(lǐng)導(dǎo)人選舉的算法县好,都沒什么用。果真如此嗎暖混?當(dāng)然不是缕贡。只要精心設(shè)計(jì),分布式系統(tǒng)不用靠撞大運(yùn)就能保持正確性拣播。
首先需要指出晾咪, FLP 定理并沒有說共識是無法達(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)的共識」一文中描述了部分同步的兩個(gè)模型叼旋。在第一個(gè)模型中,上限是固定的但是預(yù)先不知道沦辙;在第二個(gè)模型中夫植,上限是已知的,但只是從某個(gè)未知的時(shí)間點(diǎn) T 開始才保證這個(gè)上限成立油讯。Dwork 和 Lynch 針對這兩種模型(搭配不同的故障模型)详民,分別給出相應(yīng)的能夠容忍故障的共識協(xié)議延欠。
Chandra 和 Toueg 在「可靠分布式系統(tǒng)中的不可靠失效檢測器」介紹了不可靠失效檢測器的概念。每一個(gè)進(jìn)程都有一個(gè)本地阐斜、外部的失效檢測器衫冻,這個(gè)檢測器有可能出錯(cuò)诀紊。失效檢測器監(jiān)控系統(tǒng)中的部分進(jìn)程谒出,維護(hù)一個(gè)它懷疑已經(jīng)崩潰進(jìn)程的列表。檢測失效的方法很簡單:檢測器定期向某個(gè)進(jìn)程發(fā)送打招呼消息邻奠,如果超過某個(gè)耗時(shí)上限(2×消息來回的最大可能耗時(shí))笤喳,仍然沒有收到該進(jìn)程的響應(yīng),就把它列入懷疑名單碌宴。檢測器有可能犯錯(cuò)杀狡,把正確的進(jìn)程列入懷疑名單中。不過贰镣,如果檢測器在后續(xù)時(shí)段又收到進(jìn)程的響應(yīng)呜象,會自動糾錯(cuò),把這個(gè)進(jìn)程從懷疑名單去掉碑隆。在一個(gè)條件稍微放松的系統(tǒng)模型中恭陡,只要有失效檢測器,即使它是不可靠的上煤,也能解決共識問題休玩。
共識保證了不同的進(jìn)程就某個(gè)取值達(dá)成一致,而原子化廣播(atomic broadcast)保證了每一個(gè)進(jìn)程按照相同的順序投遞同一個(gè)消息劫狠。在上面那篇論文中拴疤,作者證明了共識和原子化廣播彼此是等價(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ù)測的延遲糙麦。從這個(gè)意義上辛孵,實(shí)際的系統(tǒng)是部分同步的。
我們注意到赡磅,部分同步只是說最終保證消息投遞的耗時(shí)有一個(gè)固定的限制魄缚,但最終是指什么時(shí)候,沒有明確指出焚廊。類似地冶匹,我們稱這樣的系統(tǒng)是最終同步的。這里的最終同步咆瘟,并不是說過了一段時(shí)間后系統(tǒng)就永遠(yuǎn)是同步的嚼隘,也不是說系統(tǒng)開始是異步的,一段時(shí)間之后變成同步的搞疗。相反嗓蘑,最終同步是指系統(tǒng)有時(shí)是異步的,此時(shí)消息投遞的耗時(shí)有可能是無限長匿乃,但是也存在系統(tǒng)同步的時(shí)段桩皿,足夠一個(gè)算法做有用的工作或者運(yùn)行完。關(guān)鍵是要記住幢炸,異步系統(tǒng)不提供任何定時(shí)保證泄隔。
最后,在「分布式共識所需的最少同步」一文中宛徊, Dolev, Dwork 和 Stockmeyer 描述了一種分布式共識協(xié)議叫做 t-復(fù)原(t-resilient)佛嬉,它能在最多 t 個(gè)進(jìn)程失效時(shí)保證系統(tǒng)仍然正常地工作。本文給出幾個(gè)關(guān)鍵的系統(tǒng)參數(shù)和同步條件闸天,描述不同的參數(shù)和條件對算法的影響暖呕。可以證明苞氮,在某些模型中共識是可達(dá)的湾揽,在另外一些模型中則不行。
依靠法定多數(shù)(quorum),能夠?qū)崿F(xiàn)容忍故障的共識库物。直覺上霸旗,如果大多數(shù)進(jìn)程能就每一個(gè)決定達(dá)成一致,即使出現(xiàn)故障戚揭,也至少有一個(gè)進(jìn)程了解完整的歷史诱告。
在某些系統(tǒng)模型中,不可能達(dá)成確定性共識民晒,許多有用的算法也因此無法實(shí)現(xiàn)精居。但是,大部分實(shí)際系統(tǒng)對應(yīng)的模型能夠規(guī)避這一點(diǎn)镀虐。不管怎樣箱蟆,這都顯示出分布式系統(tǒng)固有的復(fù)雜性,以及解決特定問題所需的嚴(yán)格性刮便。
從理論轉(zhuǎn)向?qū)嵺`
上述理論有什么實(shí)踐意義呢?對于初學(xué)者而言绽慈,這意味著分布式系統(tǒng)沒有表面看起來那么簡單恨旱。不認(rèn)識到這一點(diǎn),人們就會在文檔中不確切地描述權(quán)衡因素坝疼,還有很多因?yàn)檎J(rèn)識不足而導(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 相連》餍現(xiàn)在要求把這個(gè)文件從計(jì)算機(jī) A 無損地傳輸?shù)接?jì)算機(jī) B 穴墅,在此過程中有可能出現(xiàn)各種失效。換言之,這是一個(gè)文件傳輸應(yīng)用程序封救,依賴底層存儲和網(wǎng)絡(luò)的抽象拇涤。開發(fā)者考慮到下列問題有可能發(fā)生:
文件剛寫到計(jì)算機(jī) A 的磁盤時(shí),數(shù)據(jù)是正確的誉结。如果現(xiàn)在讀這個(gè)文件鹅士,有可能因?yàn)榇疟P存儲系統(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è)考慮這些威脅,很顯然滥沫,即使我們在底層實(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)對此沒什么幫助畦浓。通信系統(tǒng)的可靠性和正確性并非那么很重要痹束,在通信層保證復(fù)原性并不能減少應(yīng)用層的負(fù)擔(dān)。實(shí)際上讶请,僅僅依靠底層不可能保證正確性祷嘶,因?yàn)橄?2 個(gè)威脅要求編寫正確的程序屎媳,但是并非所有的程序都是由文件傳輸應(yīng)用開發(fā)者自己編寫的。
根本上论巍,在底層實(shí)現(xiàn)功能會引發(fā)兩個(gè)問題烛谊。首先,底層不清楚應(yīng)用的需求和語義嘉汰,這就意味著在底層實(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)功能特石。總之鳖链,端到端原則主張底層應(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)它們。現(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ī)制的通用性比不上分布式事務(wù)甜孤,但是更有效率(尤其是執(zhí)行橫跨幾百行的更新操作時(shí)),也更適合我們采用的跨數(shù)據(jù)中心樂觀復(fù)制的模式畏腕。
接下來的討論中缴川,我們把端到端原則視為一個(gè)常識。
到底由誰來保證
一般來說描馅,我們要靠健壯的算法把夸、事務(wù)管理器和協(xié)調(diào)服務(wù)來維護(hù)一致性和應(yīng)用的正確性。這會引發(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 這樣的共識系統(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è)消息代理候址,對投遞保證的依賴都屬于分布式系統(tǒng)設(shè)計(jì)中的反模式吕粹。很難正確地處理投遞的語義。尤其是對分布式消息投遞而言岗仑,你想要的往往不是你需要的匹耕。重要的是審視其中涉及的權(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ā)工作肯定變得更輕松∠晨ǎ現(xiàn)實(shí)情況是這些服務(wù)很多時(shí)候不能完美地運(yùn)行链韭。你會在凌晨一點(diǎn)收到警報(bào),不得不查找問題的源頭: 從監(jiān)控服務(wù)看煮落,RabbitMQ 明明一切正常敞峭,為什么整個(gè)系統(tǒng)卻接連出現(xiàn)問題?
如果部署在生產(chǎn)環(huán)境的系統(tǒng)依賴此類保證蝉仇,那你遲早會遇到一次(往往不止一次)上述的麻煩旋讹。最終殖蚕,所謂的保證就不存在了,因此導(dǎo)致的后果可大可小沉迹。這種設(shè)計(jì)系統(tǒng)的方式不光危險(xiǎn)睦疫,也不可取,尤其是當(dāng)你運(yùn)維一個(gè)大規(guī)模系統(tǒng)鞭呕,特別看重系統(tǒng)的吞吐或者需要提供關(guān)鍵的服務(wù)等級約定時(shí)蛤育。
分布式事務(wù)顯然會影響性能。協(xié)調(diào)的代價(jià)是昂貴的葫松,因?yàn)檫M(jìn)程不能單獨(dú)繼續(xù)運(yùn)行瓦糕,這會限制系統(tǒng)的吞吐、可用性和擴(kuò)展性腋么。 Peter Bailis 有一個(gè)非常棒的演講「沉默是金:避免協(xié)調(diào)的系統(tǒng)設(shè)計(jì)」咕娄,他詳細(xì)討論了協(xié)調(diào)的代價(jià)以及如何避免協(xié)調(diào)。他提到一個(gè)特別的例子党晋,其中分布式事務(wù)會導(dǎo)致系統(tǒng)的吞吐下降 400 倍谭胚。
如果不需要協(xié)調(diào),系統(tǒng)可以無限橫向擴(kuò)展未玻,從而極大提高系統(tǒng)的吞吐和可用性灾而。但是有時(shí)協(xié)調(diào)是不可避免的。在《數(shù)據(jù)庫系統(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)槲覀冊谶@里可以用應(yīng)用的語義而不是底層數(shù)據(jù)庫操作來定義正確性。
不知道應(yīng)用程序的正確性定義(例如辟狈, I-confluence 用到的那些不變性)肠缔,在讀寫模型中,能夠保證的最佳正確性是序列化哼转。
給定事務(wù)集明未,以及統(tǒng)一分散狀態(tài)的合并函數(shù),就可以判定 I-confluence 是否成立壹蔓。如果成立趟妥,就意味著存在一種保證不變性的無協(xié)調(diào)執(zhí)行策略。如果不成立佣蓉,意味著這樣的策略不存在披摄,協(xié)調(diào)就是必需的亲雪。由此可見, I-confluence 能夠幫我們識別出何時(shí)需要協(xié)調(diào)行疏,何時(shí)不需要匆光。由于是在應(yīng)用層定義和保持不變性,就不會存在超出需要的設(shè)計(jì)酿联。
回想一下终息,分布式計(jì)算的同步性(synchrony)只是對時(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 大會做的演講「分布式奥帘、最終一致的計(jì)算」中,用汽車打比方來解釋協(xié)調(diào)舅桩。駕駛汽車需要摩擦力酱虎,但是只能有非常少量的摩擦點(diǎn)。太多的摩擦點(diǎn)會出問題或者降低效率擂涛。如果把物理時(shí)間看做摩擦力读串,完全消除它是不可能的,因?yàn)檫@是問題的本質(zhì)屬性撒妈。但是我們可以盡量減少它在系統(tǒng)中的使用恢暖。通常,可以選用邏輯時(shí)間取代物理時(shí)間狰右,例如杰捂,使用 Lamport 時(shí)鐘或者其他沖突消除技術(shù)。有關(guān)這一思路的經(jīng)典介紹棋蚌,是 Lamport 寫的書《分布式系統(tǒng)的時(shí)間琼娘、時(shí)鐘和事件順序》。
系統(tǒng)在執(zhí)行延遲敏感操作時(shí)通常會完全放棄協(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)心原子性趾浅、隔離級別或者線性化,它關(guān)心的是兩個(gè)用戶共享同一個(gè) ID 或者兩個(gè)訂單預(yù)定了同一個(gè)房間或者銀行賬戶有負(fù)結(jié)余馒稍,數(shù)據(jù)庫是不知道這些的皿哨。有時(shí),諸如此類的規(guī)則甚至不需要任何代價(jià)昂貴的協(xié)調(diào)纽谒。
如果把應(yīng)用規(guī)則和約束編碼成基礎(chǔ)設(shè)施層理解的語言证膨,這會引發(fā)幾個(gè)問題。首先鼓黔,必須把應(yīng)用語義無縫地轉(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)行——往往并非如此。
身為消息平臺團(tuán)隊(duì)的一員润梯,我經(jīng)歷過無數(shù)次像下面這樣的對話:
開發(fā)者:“我需要快速消息傳遞过牙。”
我:“可以偶爾丟失消息嗎纺铭?”
開發(fā)者:“什么寇钉?當(dāng)然不行!我們要求可靠的消息傳遞舶赔∩ǔ”
我:“好,那我們加上投遞確認(rèn)竟纳。不過撵溃,如果你的應(yīng)用程序在處理消息之前崩潰了疚鲤,會出現(xiàn)什么情況?”
開發(fā)者:“我們在消息處理后會確認(rèn)缘挑〖”
我:“那如果處理完了但是還沒確認(rèn)的時(shí)候程序崩潰了,怎么辦语淘?”
開發(fā)者:“重試唄诲宇。”
我:“也就是說允許重復(fù)發(fā)送亏娜?”
開發(fā)者:“這個(gè)焕窝,還是應(yīng)該有且只有一次發(fā)送∥兀”
我:“你不是想快速發(fā)送嗎它掂?”
開發(fā)者:“是啊。對了溯泣,還要保持消息的順序虐秋。”
我:“你要求的就是 TCP 垃沦】透”
相反,如果重新評估系統(tǒng)間交互和系統(tǒng) API 及語義肢簿,把其中一些特性從基礎(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)問題,解決方案會非常簡單糯钙。我有信心設(shè)計(jì)更好的系統(tǒng)粪狼,有時(shí)我們只需換個(gè)角度思考問題。事物的運(yùn)作方式蘊(yùn)含著一定的道理任岸,這可沒用到計(jì)算機(jī)或者復(fù)雜的算法再榄。
不要試圖用脆弱、笨重的抽象來掩蓋復(fù)雜性享潜,相反困鸥,在設(shè)計(jì)決策時(shí)識別問題,端到端思考剑按,直面問題疾就。追尋分布式系統(tǒng)之道的道路漫長而艱難,現(xiàn)在就開始吧艺蝴。
本文翻譯:柳泉波