分散式系統的麻煩:不可靠的網路、不可信的時鐘,與半死不活的節點
· tech
#distributed-systems#book-notes#reliability
📑 目錄
上一篇結尾埋了個鉤子:當系統跨到多台機器,連「鎖」和「驗證」本身都變得不可靠。這章就是那個「不可靠」的總清算——也是我認為全書最該精讀的一章。核心一句話:單機世界是確定性的,要嘛全好、要嘛全壞;分散式世界的常態,是「部分失效(partial failure)」——一部分壞了、其他還在跑,而且你常常分不清誰是誰。 三個不可靠,層層遞進:網路、時鐘、然後是節點自己。
不可靠的網路:「沒有回應」有四種原因,而你分不出來
你送一個請求出去,遲遲沒有回應——發生了什麼? 這章最重要的一張圖,就是這個問題的答案:你不知道,而且原則上無法知道。
這個「不可區分」不是工程沒做好,是非同步網路的本質——沒有任何機制能保證訊息在多久內送達。所以 Sentinel 才要分主觀下線(我覺得它掛了=我的 timeout 到了)與客觀下線(過半都覺得它掛了),所以重試一定要搭配冪等——因為你重送的那個請求,可能第一次已經成功了。
不可信的時鐘:想要順序,用序號,不要用時間
第二個不可靠更陰,因為它平時看起來很正常。先分清機器上的兩種鐘,再看誤用的災難:
半死不活的節點:你連「自己還活著」都不能確定
第三層最哲學:連節點自己的判斷都不可信。 一個行程可能在任何一行程式碼之間被暫停——GC stop-the-world、VM 被 suspend、還不完的 page fault——暫停幾秒甚至幾分鐘,而它自己毫無知覺。醒來的那一刻,它以為自己還是 leader、以為鎖還在手上,但世界早就變了。這正是我在分散式鎖畫過的那場戲:GC 暫停超過 TTL,兩個 client 同時「持有」鎖——而解法(fencing token,單調遞增的序號、由資源端把關)也在那篇講透了,這裡不重畫。
把三層合起來,Ch8 的結論就浮出來了:任何單一節點的判斷——包括它對自己的判斷——都不可信;所以「真相」在分散式系統裡,只能是多數節點投票的結果(quorum)。 一個節點就算自認活著,只要過半宣告它死了,它就「死」了,必須讓位。這就是 Sentinel 的過半、Cluster 的過半一路埋的伏筆——而「多數怎麼安全地達成一個決定」,正是下一章共識的主題。(至於「節點會說謊」的拜占庭故障:除非你在做區塊鏈或航太,一般自家機房假設節點誠實但會壞就夠了——別為用不到的威脅模型付設計稅。)
反思
timeout 不是「知識」,是「決定」——想通這句,重試與冪等就成了信仰
那張「四種原因不可區分」的圖,是我認為整本 DDIA 最值得裱框的一張。它戳破一個工程師普遍的錯覺:timeout 到了 = 對方掛了。不,timeout 到了只代表「你決定不再等」——你依然不知道請求是沒到、做了一半、還是做完了但回應丟了。這個「不知道」推出兩條我奉為紀律的實務規則:第一,重試是必須的,所以冪等不是選項(可靠性那篇的地基,原來根在這);第二,任何單點的死活判斷都只是猜測,要行動就得湊多數(SDOWN→ODOWN 的設計,原來理論在這)。一章書,把我散落在 Airflow、Redis、SRE 的三個實務習慣,收攏成同一條公理的推論。
「想要順序,用序號,不要用時鐘」——資料工程的每一天都在用這句話
LWW 靠時間戳丟資料那一幕,對做資料的人應該格外刺痛,因為我們天天在跟它的變體搏鬥:event time vs processing time、遲到的事件、跨機房的日誌合併排序。這章給了我一個統一的答案:牆上的鐘只能拿來「大概標記」,凡是正確性依賴順序的地方,一律用單調遞增的序號——Kafka 的 offset、資料庫的 LSN、fencing token,全是這個原則的化身。我現在設計任何 pipeline,看到「用 timestamp 判斷誰新誰舊」就會停下來問:這兩個 timestamp 來自同一個鐘嗎? 不是的話,就換序號、或接受近似。這句話便宜、好記、能擋住一整類無聲的資料損毀。
部分失效不是要修的 bug,是要接受的世界觀
讀完這章,我對「分散式」三個字的敬畏又深了一層:單機是「要嘛全好、要嘛全壞」的確定性世界,分散式是「永遠有一部分半死不活」的機率世界——而後者不是工程不夠好,是本質。這給我兩個層次的啟示。往下,它解釋了為什麼 K8s 的 reconcile loop、SRE 的一切設計都圍繞「預期失敗」——在這個世界觀裡,可靠性不是「不出事」,是出事時系統還能收斂。往上,它再次替 先確認痛點背書:每跨出一台機器,你就把自己從確定性世界搬進機率世界,那是一整套認知稅——網路、時鐘、quorum、fencing 全要補課。單機扛得住,就別急著分散;真要分散,就把這章當入場券,一字一字讀完再上路。