書摘《分散式系統設計》

Du Spirit
個人書摘
Published in
13 min readMar 16, 2024
圖片來源:Amazon

第一章 簡介

容器 (Container) 和容器編排器 (Container Orchestrators) 普及程度不斷地提高。如同物件導向編程 (Object-Oriented Programming) 裡的概念一樣,這些容器畫建構模組,是開發可重用模組和模式的基礎。 p. 1

這些分散式系統的設計、建構和偵錯可能會更加複雜。建構可靠的分散式系統所需的工程技能,遠遠高於建構手機應用或網頁前端等單機應用程式所需的技能。p. 2

這句話… 很容易引戰的感覺啊

模式的另一個重要價值是提供一組通用的名稱和定義,以便我們不浪費時間擔心命名,而是正確地討論核心概念的細節與實現。 p. 5

有趣的是,現在常常會遇到你的 X 模式不是我的 X 模式…

第一篇 單節點模式

通常,容器的目的是因為要清楚資源的範圍… (略) … 同時,也清楚描繪了團隊的權責。… (略) … 最後,邊界也隱含了關注點分離這件事。 p. 7

即是應用程式很小,而且所有容器都由單一團隊所擁有,但關注分割性可讓應用程式容易理解,並且可以輕鬆進行測試、更新和部屬。小型,有專注點的應用程式更容易理解,而且與其他系統的耦合度 (Coupling) 更少。 p. 8

第二章 邊車模式

邊車 (Sidecar) 的作用是加強和改進應用程式容器,通常沒有應用程式的商業邏輯 (知識)… (略) …邊車模式容器透過原子性容器群組 (Container Group),在同一台計算機上共同調度。 p. 11

像是為沒有支援 HTTPS 的容器增加對 HTTPS 的支持

這種模組化和可重用性,就像在高品質軟體開發中實現模組化一樣,需要專注和紀律,特別是需要專注發展的三個領域:

  • 參數化你的容器
  • 建立容器的 API 介面
  • 記錄你的容器的操作 p. 17

當改變應用程式的成本過高時,邊車可用於更新現有的遺留應用程式。同時,它可以用來建立模組化的實用容器,這容器是共用功能的標準實作。這些實用程序容器可以在大量應用程式中重用,從而提高一致性並降低開發每個應用程式的成本。 p. 20

第三章 大使模式

大使模式 (Ambassador Pattern),它代替應用程式容器與其他世界的服務做互動。 p. 21

不算是很準確,但可以簡單地把邊車想成是外部 -> 邊車 -> 應用程式,大使是應用程式 -> 大使 -> 外部

第四章 適配器

在適配器 (Adapter) 模式中,適配器容器 (Adapter Container) 用嫆修改應用程式容器的介面,以使其符合所有應用程式所需的某些預定義切面。例如,適配器可以確保應用程式實現一致的監視介面。或者它可以確保始終將日誌文件寫入標準輸出 (stdout) 或任何其他協議。 p. 31

有時,設計模式不僅適用於應用它們的開發人員,而且還可以促進社群的發展,這些社群可以在社群成員以及更廣泛的開發人員生態系統之間進行協作和共享解決方案。 p. 39

第二篇 服務模式

可靠性、可擴展性和關注點分離表明真實世界的系統是由許多不同的元件建構的,分散在多台機器上。與單節點模式相比,多節點分散式模式更鬆耦合 (Loosely Coupled)。雖然模式規定了元件之間的通訊模式,但此通訊基於網路呼叫。此外,許多呼叫是平行的,系統透過鬆散同步而非緊密約束進行協調。 p. 41

在不同的微服務之間導入規格化的 API,會解構團隊,並在不同的服務之間提供可靠的約定。此規格化約定減少了團隊之間緊密同步的需要,因為提供 API 的團隊了解保持穩定所需的區域,使用 API 的團隊可以依賴穩定的服務而不必擔心其細節。 p. 42

微服務方法系統設計當然也有缺點。兩個主要的缺點:由於系統變得鬆散耦合,因此在發生故障時,除錯系統變得困難。… (略)… 基於微服務的系統也很難設計和架構的,它使用多種服務之間的通訊方式、不同的模式 (如同步、異步、訊息傳遞等)、以及服務之間的多種不同協調和控制模式。 p. 43

第五章 複本附載平衡服務

最簡單的分散式模式,也是大多數人熟悉的模式:複本附載平衡服務 (Replicated Load-Balanced Service)。在這樣的服務中,每個伺服器實例與其他的都一模一樣,而且這些伺服器能滿足所需要的流量。這樣的模式由可擴展數量的伺服器組成,前面有負載平衡器 (Load Balancer)。負載平衡器通常是輪詢模式 (Round-Robin),或使用某種形式的粘滯會話 (Session Stickiness)。 p. 45

Readness Probe 則決定應用程式何時準備好服務使用者的請求。這些差異的原因,是因為許多應用程式在開始服務之前,需要一些時間初始化。它們可能需要連接到資料庫,載入額外的延伸套件,或是從網路下載相關服務檔案。在這些情境之下,所有的容器都是 alive,但還沒有 ready。 p. 47

此類的 Session Tracking 會使用來源和目的 IP 的雜湊值 (Hashing),然後使用雜湊值作為伺服器服務請求的識別依據。只要來源和目的 IP 維持不變,所有的請求都會送到同樣的複本。 p. 49

通常,會話追蹤透握一致雜湊函式 (Consisitent Hashing Function) 完成。一致雜湊函式的好處,會在服務複本的擴展或減少時更加明顯。顯然,當複本的數量改變時,特定的使用者到複本服務的應對也會改變。一致雜湊函式可最大限度減少實際更改映射到的複本用戶數,從而減少擴展對應用程式的影響。 p. 50

第六章 分片服務

與使用分片服務的的複本服務對照,每個複本或分片 (Sharded),只能為所有請求的子集提供服務。一個負載平衡節點或根結點 (root),負責檢查每個請求病家每個請求分配到適當的分片或多個分片進行處理。 p. 61

複本服務通常用於建置無狀態服務,而分片服務通常用於建置有狀態服務。拆分資料的主要原因是因為狀態的大小太大,無法由單個機器節點提供服務。透過拆分成分片,可以根據需要提供的狀態大小來擴展服務。 p. 61

由於快取作為戰存資料的性質,快取錯失本質上不是問題,而且系統一定知道如何重新計算資料。但是,本質上這種重新計算比直接使用快取慢,因此對終端使用者有效能影響。 p. 63

快取效能是根據其命中率 (Hit Rate) 定義的。命中率是快取時間的百分比,它包含用戶請求資料的時間。最後,命中率決定了分散式系統的總容量,並影響系統的整體容量與效能。 p. 63

系統的效能不僅僅是根據它可以處理的請求數量來定義。系統的最終使用者效能也是根據請求的延遲 (Latency) 來定義的。快取的結果通常比從頭計算結果快得多。因此,快取可以以提高請求的速度以及處理得請求總數。 p. 64

如何決定請求應該使用 0 到 9 內的哪個分片 S?此對應是分片函式 (Sharding function) 的責任。分片函式與雜湊函式 (hashing function) 非常類似,在學習雜湊表 (hastable) 資料結構時可能會遇到這種功能。實際上,基於水桶 (bucket-based) 的雜湊表可以被視為分片服務的範例。 p. 69

當你需要更改分片服務中得分片數時會發生什麼?這種「重新分片」(re-sharding) 通常是個複雜的過程。 p. 71

沒事別自己做分片,有既有的套件或服務就盡量用,自己實作 sharding 真的很累。

許多分片函式使用一致性雜湊函式。一致性雜湊函式是特殊的雜湊函式,在調整為 #shards 的大小時,保證只重映射 #keys / #shards。 p. 72

理想情況下,分片快取的負載將是完全平均的,但在許多情況下並非如此,並且出現「熱分片」(Hot Shards),因為有機負載模式 (Organic Load Pattern) 會驅使更多流量到一個特定分片。 p. 73

簡單的比喻,餐廳也是有熱門跟非熱門的差別,理想上,每個分片上熱門與非熱門餐廳的資料數量接近,那負載就會平衡,但若某個分片有較多熱門餐廳的資料,那就容易形成熱分片。

第七章 分配/聚集

與複本和分片系統一樣,分配/聚集模式是一種樹狀模式 (Tree Pattern),其根結點 (Root) 分配請求,在葉節點 (Leaf) 處理這些請求。但是,與複本和分片系統相比,分配/聚集請求同時被分配到系統中的所有複本。每個複本執行少量處理,然後將結果的一部分返回到根結點。最後,根伺服器將各種部份結果組合再一起,形成對請求的單個完整回應。 p. 75

分配/聚集可以看做是對服務請求所需的計算進行分片,而不是分片資料p. 75

看起來在分配/聚集模式中,複製資料到大量的葉節點是一個好方法。可以平行運算,以減少處理任何特定請求所需的時間。但是,增加平行化需要付出代價,因此在分配/聚集模式中選擇正確數量的葉節點,對於設計高效能分散式系統是至關重要的。 p. 80

簡單說,別對自己進行 DDoS 攻擊,越多平行處理,對系統的基礎設施負擔也越大。

除了增加更多葉節點,可能實際上不會加速處理的事實之外,分配/聚集系統也遭受「落後者」(Straggler) 問題。要了解其工作原理,請務必記住,在分配/聚集系統中,根結點在回傳結果給使用者之前,必須等待所有發送到葉節點的請求返回結果。由於需要來自每個葉節點的資料,因此處理使用者請求所花費的總時間,由發送回應的最慢葉節點決定。 p. 81

同樣的落後者問題是用於可用性,如果向 100 個葉節點發出請求,並且任意葉節點故障機率為 100 中的 1,則實際上幾乎等於每個使用者請求都會失敗。 p. 81

這是簡單的機率問題,故障率為 1%,意味著成功率為 99%,同時 100 個節點都成功的機率為 0.99100,成功率大概只有 36.6%,連四成都不到。

第八章 功能函式與事件驅動程序

將事件驅動處理這樣的特定解決方案視為萬用工具也很誘人。然而,事實是它適用於一組特定的問題。在特定的情境中,它是非常強大的工具,但將其延伸適應所有應用程式或系統,會導致過於複雜、以及脆弱設計。 p. 84

這種強制解耦可以提高開發服務的靈活性和速度,但也會使同一服務的維運變的複雜化。特別是,通常很難獲得對服務的全面了解,這取決於各種功能如何相互整合,並了解出現問題的時間以及出錯的原因。 p. 84

隨著業務的增長,服務的請求數量會增加到可以使處理器處於持續活動的狀態,然後持續處理用戶請求。此時,案請求付費模式的精細開始變差,而且只會變得更糟。 p. 86

FaaS 其實真的很貴。

FaaS 非常適合部署簡單的功能,這些功能可以接收輸入,將其轉換為輸出,然後將其傳遞給不同的服務。此通用模式可用於增強或修飾 (Decorate) 進出不同服務的 HTTP 請求。 p. 87

有些應用程式它們本質上容易以解耦事件的流水線程序 (Pipeline) 來思考。 p. 91

流水線程序和「微服務」架構這兩種類型之間有什麼不同?有兩個主要的差異。其一,一般功能長期運行服務 (long-running service) 之間的主要區別,即基於事件的流水線程序本質上是由事件驅動的,而微服務架構由長期運行的服務構成。其二,事件驅動的流水線程序,其中連接在一起的事物中可能是高度非同步與多樣化的。 p. 91

第九章 所有權選舉

通常,建立分散式所有權是設計一個可靠分散式系統中,最複雜也是最重要的。 p. 96

不信?若去讀 MongoDB、kafka 或是其他分散式系統的文件,一定有相關的內容。

老實說,如果叢集中的機器每天都故障,這個問題比服務的正常運行時間 (uptime) 嚴重。 p. 97

當然,值得思考的是,停機理由並不只是因為機器故障。當部署新的軟體,下載並啟動新版本需要時間。使用單一機器,新舊版本無法同時執行,所以必須要在升級版本時間承擔停機,如果下載的映像檔很大,則會需要花時間。 p. 97

有兩種方法可以時做所有權選舉。首先實作分散式一致性演算法 (Distributed Consensus Algorithm) 的 Paxos RAFT,但這些演算法太過複雜超過本書的範圍,而且不值得實作。 p. 98

能用現成的就用吧!這章後面討論不少實作鎖該注意的事,很值得看,但很難摘錄,建議還是去看書吧!

第三篇 批次運算模式

第十章 工作佇列系統

最簡單的批次處理形式是工作佇列 (Work Queue)。在工作佇列系統中,有一批工作要執行。每件工作完全獨立於另一件工作,不需要額外的互動。通常,工作佇列系統的目標是確保在一定時間內處理每個工作。主要工作單元 (Workers) 會依照需求自動擴展,確保可以順利處理任務。 p. 113

第十一章 事件驅動批次處理程序

協作工作佇列 (Coordinating Work Queue) 的第一個模式是複製器 (Copier)。複製器的工作是取得單個工作項目流程,並將其複製到兩個或多個相同的流程中。 p. 127

第二個是件驅動批次處理的模式是過濾器 (Filter)。過濾器的用途是透過過濾不符合特定條件的工作項目,將工作減少到較小的工作串流。 p. 127

有時候,不只是想過濾掉不需要的東西,而是工作項目有兩種不同的輸入,你想把他們分成兩個獨立的工作佇列,每個都要處理。針對這樣的任務,需要使用拆分器 (Splitter)。拆分器的作用是評估一些標準,就像過濾器一樣,但不會消去輸入工作項目,拆分器依據不同的條件,向不同的佇列發送不同的輸入。 p. 128

稍微更加通用的拆分器是分片器 (Sharder),與前面章節中看到的分片伺服器 (Sharded Server) 非常相似,分片器在工作流程中的作用,是根據分片函式 (Sharding Function),將單個佇列劃分為均勻分布的工作項集合。 p. 129

是件驅動批次處理程序的最後一個模式是合併器。合併器 (Merger) 與複製器相反。合併的工作是取得兩個不同的工作佇列,然後將它們轉換成單個工作佇列。 p. 131

我們已經看到了各種抽象模式,用於將不同的是件驅動批次處理模式串聯在一起。但是,當實際建置這樣一個系統時,需要清楚如何管理透過事件驅動工作流程傳遞的資料串流。 p. 133

真的不要自己實作分散式的 event system 或 Pub/Sub,很複雜,用平台提供的或是 open source 的,都會省去大量的時間。

第十二章 協作批次處理程序

我們需要一個更強大的、協作的原聲方式,用來進行批次處理資料處理,而原生程序是結合模式 (Join Pattern)。結合 (Join) 類似於加入執行緒。基本想法是所有工作都是同步進行的,但是在完成平行處理的所有工作項目之前,工作項目不會從結合中釋放出來。這通常也稱為並行編程 (Concurrent Programming) 中的屏障同步 (Barrier Synchronization)。 p. 138

它的缺點,是需要在後續計算開始之前,前一個階段必須處理好所有資料。這降低了批次處理工作流程中可能存在的平行性,從而增加了運行工作流程的總體延遲。 p. 138

然而,與前面描述的結合模式相反,Reduce 的目的不是等待所有資料都被處理好,而是樂觀地將所有平行資料項目。合併到一個完整即合理做單一呈現。 p. 139

使用歸納模式 (Reduce Pattern),reduce 中的每個步驟將幾個不同的輸出合併為一個輸出。此階段稱為 reduce 歸納,因為他減少了輸出總數。 p. 139

心得

這本書從 docker 的角度出發,介紹很多可重複使用的 pattern,除了翻譯某些地方有點怪之外,算是很有趣的一本書,後面很多的 pattern 可以想成是 sidecar 的進階使用方式,在不改變應用程式的情況下,增加不同的功能,相當實用。重點是,這是一本很薄的書,很適合在通勤時間看,我也是在通勤的捷運上看完這本書,推薦給大家。

--

--