24.分布式系統(tǒng)的困境與NPC性別研究

1 楔子
最近每天晚上聽凱叔西游記,我發(fā)現(xiàn)一個很有趣的事情:每當(dāng)孫悟空要打聽小道消息的時候,都會找當(dāng)?shù)氐腘PC(土地)??梢奛PC這種角色自古有之,并不是網(wǎng)游里的特產(chǎn),他們平時不怎么起眼,但關(guān)鍵時刻總能挺身而出,或者背后捅刀子。
不要問我為啥要聽凱叔,都是為了知識,知識懂不~
不過今天,NPC可是我們的主角,它指代分布式系統(tǒng)中三個重要角色:N(Network網(wǎng)絡(luò))、P(Process進程)、C(Clock時鐘)
2 Network
網(wǎng)絡(luò)模型
年輕的時候看小電影,平時感覺還湊合的網(wǎng)絡(luò),關(guān)鍵時刻總是一卡一卡的,讓人火大。即便這樣,我覺得網(wǎng)絡(luò)是底限的,雖然慢但好賴是靠譜的。直到我后來當(dāng)了程序員,我才發(fā)現(xiàn)年輕的時候果然還是太年輕了。
程序員通常把兩個節(jié)點之前的Point-to-Point通信叫l(wèi)ink或channel,根據(jù)人品價格不同,至少可以分為三類:
Reliable (perfect) links:消息有可能亂序,但是不重不丟。不重不丟也就算了,但是『可能亂序』是個什么鬼?
Fair-loss links:消息可重、可丟、可亂序,但是只要發(fā)送端夠努力,不斷的retry,早晚能把消息發(fā)過去。請注意,這里我使用了早晚這個詞。語文老師經(jīng)常給我們講『這個晚字用得好』。某條從濟南發(fā)給北京的消息可能會先去泰國做了SPA,這中間會磨蹭多長時間完全看人品,幾分鐘到幾小時都不是夢。
Arbitray links:消息可能被惡意竊聽、篡改、丟棄,總之,你攤上事兒了。這年頭,誰心里還沒有一點兒小秘密?
我曾天真地以為網(wǎng)絡(luò)都是Reliable的,是現(xiàn)實教會了我Point-to-Point就是P2P,而P2P是會跑路和暴雷的。在一個稍具規(guī)模的公司里,拔網(wǎng)絡(luò)和貼膠帶都是運維工程師的基本操作,不然你以為他們憑什么拿這么高的薪水?而在某些非社會主義國家的特殊日子,網(wǎng)絡(luò)時好時壞已經(jīng)是業(yè)內(nèi)規(guī)則。
不要問我為啥了解P2P,這叫知識,知識懂么~
還好我是認(rèn)識魯迅的人,我知道:軟件工程里,沒有什么問題是不可以通過增加一個中間層來解決的。
retry+dedup:通過不斷的重試+去重,我們可以把Fair-loss links轉(zhuǎn)化成Reliable links。
TLS:通過某種形式的加密(比如:TLS),我們可以把Arbitrary links轉(zhuǎn)化成Fair-loss links。

CAP與PACELC
還有一種經(jīng)常被拎出來單獨討論網(wǎng)絡(luò)異常現(xiàn)象是Network Partition(網(wǎng)絡(luò)分區(qū))。它特指由于交換機失敗,導(dǎo)致整個網(wǎng)絡(luò)被分割成多個子網(wǎng)絡(luò)分區(qū)的情況。分區(qū)內(nèi)的網(wǎng)絡(luò)節(jié)點可以相互通信,但分區(qū)間的網(wǎng)絡(luò)節(jié)點相互失聯(lián)。

網(wǎng)絡(luò)分區(qū)之所以被大家廣為知曉,很可能是因為跟它相關(guān)的CAP的定理特別出名,盡管這個定理沒啥用。CAP定理中的三個字母分別代表:C(Consistency,一致性,其實是指Linearizabilty,線性一致性),A(Availability,可用性),P(Partition Tolerance,分區(qū)容忍性)。CAP號稱三選二,但在分布式系統(tǒng)中其實只能在一致性和可用性之間二選一,通常只用于面試或?qū)γ刃旅米哟蹬?,對實際系統(tǒng)開發(fā)指導(dǎo)能力有限。

另外,還有一個PACELC理論,它是加強版的CAP定理,卻因為名字起得不好,導(dǎo)致鮮為人知。PACELC強調(diào)它的后三個字母ELC,即Else Latency Consistency,指在沒有發(fā)生網(wǎng)絡(luò)分區(qū)的情況下,分布式系統(tǒng)需要處理好『延遲』和『一致性』的關(guān)系。簡而言之:一個具有N個節(jié)點的分布式系統(tǒng),達成一致的節(jié)點越多,則返回響應(yīng)越慢。
同志們吶,給孩子起個好記的名字是多么的重要。

3 Process
失敗模型
我們通常把運行中的計算機(節(jié)點)抽象為進程,進程可能發(fā)生各種奇奇怪怪的問題。除去跟代碼邏輯密切相關(guān)的問題,在通用層面上進程(節(jié)點)可能出現(xiàn)的問題至少包括:
Crash-stop(fail-stop):進程可隨時崩潰并停止響應(yīng),不再恢復(fù),永遠消失。這可能會伴隨著物理硬件的損壞或丟失,比如你上洗手間的時候把手機洗了。
Omissions:進程不接受其它節(jié)點的請求或者不返回響應(yīng)(注意,其它節(jié)點無法區(qū)分這兩種情況)。從其它節(jié)點的角度看,就是懶政、不作為。
Crash-recovery(fail-recovery):進程可隨時崩潰,但在一段時間后可以恢復(fù)并再次響應(yīng)。在該模型中,經(jīng)過持久化(到非易失性存儲)的數(shù)據(jù)可以恢復(fù),但在內(nèi)存中的狀態(tài)則可能會丟失。
Byzantine(fail-arbitrary):拜占庭將軍問題,問題進程可以干任何事情,包括試圖作弊和欺騙其它節(jié)點。
一個有趣的現(xiàn)象是,很多時候問題節(jié)點并不會(或不能)主動通知其它節(jié)點它出問題了,甚至有些節(jié)點會主動隱藏自己的行為。就像病人需要看醫(yī)生一樣,這時可能需要通過系統(tǒng)中的其它節(jié)點,感知問題節(jié)點的狀態(tài)。比如通過docker或supervisor自動重啟意外crash的進程,通過heartbeat或ping/pong機制探測某個節(jié)點是否工作正常。雖然不是所有的解決方案都簡單直接,但更加完善和細(xì)致的監(jiān)控,確實有利于我們及時發(fā)現(xiàn)和排查問題。

腦裂
另一個也許值得探討的問題是:當(dāng)你周圍所有的人都認(rèn)為你瘋了的時候,你如何證明自己沒有?類似的,當(dāng)醫(yī)生判斷你已經(jīng)要玩完的時候,你如何爭取再搶救一下?以及,當(dāng)所有其它節(jié)點都覺得某個節(jié)點已經(jīng)僵死的時候,被觀察的節(jié)點如何自證清白?
也許,這個進程只是打了個盹,比如發(fā)生Full GC了,結(jié)果一覺醒來就變成了千夫所指。也許,只是其它節(jié)點觀察它的方式不對,比如發(fā)生網(wǎng)絡(luò)分區(qū)了。但無論如何,嗓子啞了喊不出來,與喊的很大聲但別人卻聽不見,這兩者在別人的眼中并無本質(zhì)的區(qū)別。

這種被孤立的進程(節(jié)點),客觀上有效,但主觀上已經(jīng)被宣布社會性死亡。這對于無狀態(tài)集群來說,可能傷害性不大??梢院唵蔚那袚Q到對等節(jié)點上,對外提供幾乎一樣的服務(wù),只是集群中少了一個勞動力而已。但對于有狀態(tài)存儲,傷害就比較大。比如MySQL主從集群中,leader節(jié)點被宣布死亡,可能會導(dǎo)致集群切換leader節(jié)點。但更糟糕的情況是:萬一舊的leader節(jié)點又復(fù)活了呢?現(xiàn)在集群中同時存在兩個leader節(jié)點,聽誰的?會不會腦裂?

這個問題被稱為分布式共識問題,這個看似簡單的問題實際上遠比我們簡單腦補出來的要復(fù)雜的多。真實情況是,長久以來,學(xué)術(shù)界都沒能找到一種邏輯正確的算法解決這個問題。而直到Paxos算法出現(xiàn)近20年之后,工業(yè)界才慢慢理解了這個算法并有了相對穩(wěn)定的實現(xiàn)。直到近些年,相對簡化的Raft等算法才開始在Etcd, TiDB等分布式存儲系統(tǒng)中應(yīng)用開來。
4 Clock
時鐘和計時是如此重要,即使在跟時間無關(guān)的業(yè)務(wù)中也會大量使用到,比如:
什么時候發(fā)送提醒郵件?
緩存何時過期?
日志的時間戳是多少?
某個請求是否超時了。
某個服務(wù)的9分位響應(yīng)時間是多少?
在過去的5分鐘內(nèi),服務(wù)的QPS是多少?
但你有沒有想過,你使用的系統(tǒng)時間,可能是不準(zhǔn)的?
你可能會想,這怎么可能?操作系統(tǒng)不是會使用NTP協(xié)議校對時間嗎?但有沒有一種可能,正是因為NTP對表導(dǎo)致時間更錯亂了呢?
前面示例中,1~3描述是瞬時時間(時間點),4~6描述是持續(xù)時間(時間差)。要正確的計算它們,需要分別使用計算機中兩種不太一樣的時鐘:墻上時鐘和單調(diào)時鐘。
時鐘還要分兩種嘛?是的,現(xiàn)實世界只有一種,計算機世界卻有兩種?我能說什么,我也很無奈啊~
墻上時鐘
墻上時鐘根據(jù)某個日歷返回當(dāng)前的日期和時間。linux使用clock_gettime(CLOCK_REALTIME),java使用System.currentTimeMillis()會返回自紀(jì)元1970年1月1日(UTC)以來的秒數(shù)和毫秒數(shù),不含閏秒。
墻上時鐘可以與NTP同步。但是,如果本地時鐘遠快于NTP服務(wù)器,強行重置之后會導(dǎo)致本地時鐘跳回到之前的某個時間點。這種不穩(wěn)定性,導(dǎo)致墻上時鐘不太適當(dāng)測量時間差。

單調(diào)時鐘
單調(diào)時鐘的名字來源于它總是單調(diào)增加,而不會出現(xiàn)類似于墻上時鐘的回?fù)墁F(xiàn)象。它通常是計算機啟動之后經(jīng)歷的納秒數(shù)(或其它數(shù)值),因此單調(diào)時鐘的絕對值沒有任何意義,但特別適合測量時間差。linux使用clock_gettime(CLOCK_MONOTONIC),java使用System.nanoTime()獲取單調(diào)時鐘。
NTP時間同步并不會直接修改單調(diào)時鐘的絕對值,但這并不代表NTP不會影響單調(diào)時鐘。如果NTP檢測到本地石英鐘比時間服務(wù)器上的更快或更慢,NTP會調(diào)整本地時間的震動頻率,以加快或減慢單調(diào)時鐘步進的速度。
這年頭,連時鐘都不守時了
計算機中的石英鐘不夠精確。是的,先生,剛買的、還沒用過的、一萬塊錢的計算機上的時鐘也不精確,經(jīng)常漂移(步進速度會加快或減慢)。時鐘漂移主要取決于機器的溫度。按google的說法,如果服務(wù)器的時鐘偏移為200ppm(百萬分之一)。那么,如果每30s同步一次,則可能出現(xiàn)的最大偏差為6ms;而如果一天同步一次,則最大偏差為17秒。

閏秒是指一分鐘有59s或61s的現(xiàn)象。很神奇吧?想想我們每隔幾年還來一次閏月,是不是覺得閏秒正常多了?然而,閏秒會使一些毫無防范的系統(tǒng)混亂,甚至崩潰。處理閏秒的推薦方式是,不管NTP服務(wù)器具體如何實現(xiàn),在NTP服務(wù)器匯報時間時故意做些調(diào)整,目的是在一天的周期內(nèi)逐步調(diào)整閏秒(稱為拖尾)。
全民上云的時代,應(yīng)用已經(jīng)很難保證自己是運行在物理機還是虛擬機中。在虛擬機中,由于硬件時鐘也是被虛擬化的,這對于需要精確計時的應(yīng)用程序提出了額外的挑戰(zhàn)。當(dāng)虛擬機共享一個CPU核時,每個虛擬機會出現(xiàn)數(shù)十毫秒的暫停以便切換客戶虛機。但從應(yīng)用的角度來看,這種停頓會表現(xiàn)為時鐘突然向前跳躍。
時鐘雖然看起來簡單,但仍然有不少使用上的陷阱:一天可能并不總是86400s,時鐘會向前向后回?fù)埽粋€節(jié)點上的時間可能會另一個節(jié)點上的時間完全不同。跟網(wǎng)絡(luò)和進程問題相比,時鐘問題更加隱蔽,不容易被及時發(fā)現(xiàn)。如果石英時鐘有bug,或NTP客戶端配置錯誤,最后出現(xiàn)了時間偏差,對大多數(shù)功能可能影響不大。但對于一些調(diào)試依賴于精確計時的軟件,可能會出現(xiàn)一些隱式的后果,比如丟失數(shù)據(jù)而不是突然崩潰。
因此,如果應(yīng)用確實需要精確同步的時鐘,最好仔細(xì)監(jiān)控所有節(jié)點上的時鐘偏差。如果某個節(jié)點上的時鐘漂移超出上限,應(yīng)將其宣告為失效,并從集群中移除。這樣可能確保在出現(xiàn)重大損失之前及早發(fā)現(xiàn)并處理。
5 男人都是大豬蹄子
Network網(wǎng)絡(luò)、Process進程、Clock時鐘,本來還想給它們中的一個加加薪,結(jié)果仔細(xì)琢磨了一下,就沒有一個是靠譜的。
這不禁讓我想起孔子的一句名言:男人都是大豬蹄子。我孟子忍不住要補一句:孔子說得對啊,這年頭,還是公眾號『吹牛拍碼』最靠譜啊~
6 參考文獻
Designing Data-Intensive Applications 數(shù)據(jù)密集型應(yīng)用系統(tǒng)設(shè)計
Distributed Systems 2.3: System models Martin Kleppmann的講座
Distributed algorithms - Chapter 3 : Group Communications 法國尼斯大學(xué)的pdf講稿
Consistent Backends and UX: Why Should You Care? 這個系列有4篇文章,這是第一篇
Why are Distributed Systems so hard? A network partition survival guide - Denise Yu 很有趣的演講
https://deniseyu.io/art/ 我喜歡這個女生的漫畫
分布式理論最新提出的PACELC理論作為CAP理論的擴展。具體補充了哪些內(nèi)容?
FAILURE MODES IN DISTRIBUTED SYSTEMS
Modes of Failure (Part 1)
