本書深入剖析了主流開源分布式系統模式,包括模式中的常見問題和解決方案,并展示了Kafka和Kubernetes等系統的真實代碼示例,以幫助企業架構師和開發人員更好地理解這些系統的工作原理,以及分布式系統的設計原則,為應對數據存儲在多臺服務器上時可能出現的各種問題做好準備。通過閱讀本書,讀者將:了解什么是分布式系統,以及為什么需要分布式系統。更深入地理解分布式系統模式設計所面臨的挑戰,以選擇合適的云服務和產品。理解包括數據庫、內存數據網格、消息代理,以及各種云服務在內的系統的實現原理。自信地瀏覽開源代碼庫,并清晰地看到模式和解決方案如何映射到如Kafka和Kubernetes這樣的真實世界系統中。本書對于分布式架構工程師以及想要構建自己的分布式系統的開發者來說,是一本有價值的參考書。
介紹了在集群節點之間建立有效網絡通信的技巧。介紹了常用的分區方案,并深入研究兩階段提交協議的復雜性。介紹了數據庫中邏輯時間戳的使用,以幫助讀者理解數據版本控制的基本概念。闡述了Paxos和Raft等共識算法的構建塊,以確保分布式系統中副本的一致性。闡述了用于實現集群協調任務(如組成員資格、故障檢測以及健壯的集群協調)的機制。涵蓋了數據復制模式、數據分區模式、分布式時間模式、集群管理模式以及節點間通信模式等內容。
本書寫作緣由
在2017年,我參與了一個名為“Thirty Meter Telescope”(TMT)的大型光學望遠鏡軟件系統的開發項目。我們的任務是構建供各個子系統使用的核心框架和服務。這些子系統組件必須能夠相互發現與檢測組件故障,并能夠存儲有關各組件的元數據。負責這些信息存儲的服務必須是容錯的。考慮到望遠鏡生態系統的特殊性,我們不能采用現成的產品和框架,只能從零開始,打造適用于不同軟件子系統的核心框架和服務,從本質上說,我們要建立的是一個分布式系統。
我曾設計并構建過依賴于Kafka、Cassandra和MongoDB等產品的企業系統,它們使用AWS或GCP等云服務。這些產品和服務都是分布式的,解決了一系列相似的問題。對于TMT系統,我們必須自行開發解決方案。為了驗證和比較這些成熟的產品,我們需要更深入地理解它們的內部機制。了解這些云服務和產品的構建方式及其背后的原因是必要的,它們的官方文檔往往太過產品化,不利于達成我們的目標。
關于構建分布式系統的信息分散在各種研究論文中。然而,這些學術資源也有局限,它們往往只關注特定領域而忽略了相關主題。以“Consensus: Bridging Theory and Practice”(Ongaro,2014)這篇精彩的論文為例,它詳細解釋了實現Raft共識算法的過程。但你不會從中了解到像etcd這樣的產品如何使用Raft來追蹤集群成員資格和其他產品的相關元數據,如Kubernetes。Leslie Lamport的著名論文“Time, Clocks, and the Ordering of Events in a Distributed System”(Lamport,1978)中討論了邏輯時鐘的使用,但它并未解釋像MongoDB這樣的產品如何使用邏輯時鐘作為版本號來控制數據的版本。
我相信編寫代碼是驗證理解正確與否的最佳方式。正如Martin Fowler所說:“代碼就像數學,我們必須消除其中的歧義。”因此,為了深刻理解分布式系統的基礎模塊,我決定自己動手構建這些產品的簡化版本。我從打造一個玩具版的Kafka開始,一旦有了合理的版本,我就用它來探討分布式系統的一些基本概念。這種方法被證明非常有效。為了驗證通過代碼來闡釋概念的效果,我在Thoughtworks開展了一系列內部研討會,這些研討會對我幫助極大。因此,我將這種方法擴展到了Cassandra、Kubernetes、Akka、Hazelcast、MongoDB、YugabyteDB、CockroachDB、TiKV和Docker Swarm等產品。我提取了代碼片段來理解這些產品的構建模塊。果不其然,這些模塊之間存在許多相似之處。幾年前,我偶然與Martin Fowler討論過這個話題,他建議我將其整理成模式。本書便是我與Martin Fowler合作,將分布式系統中的共通的構建模塊整理成模式的成果。
本書讀者
在當今軟件架構和開發的選擇豐富多樣的背景下,面對眾多分布式產品和云服務,架構師和開發者面臨著復雜的設計抉擇。這些產品和服務的設計折中可能難以直觀理解。單憑閱讀文檔是遠遠不夠的。比如,當我們考慮“AWS MemoryDB通過復制的事務日志確保了數據的持久性”“Apache Kafka現能獨立于ZooKeeper運作”或者“Google Spanner通過同步的全球時間來維護外部一致性”這樣的句子時,該如何理解這些技術性描述?
為了深入了解,專業人士往往依賴于產品供應商的認證培訓。然而,這些認證大多局限于特定產品,關注的是表層特性,而非背后的技術原理。專業開發者需要對這些技術細節有直觀的把握,既能具體到在源代碼層面描述,又能通用到適應不同場景。這正是模式的價值所在。本書介紹的模式旨在幫助從業者深入理解各種產品和服務的內在機制,以便做出明智且有效的決策。
本書的主要讀者將是這些專業人士。除了那些需要與現有的分布式系統打交道的人員,還有一部分讀者可能需要構建自己的分布式系統。我期望本書中的模式能夠為這些讀者提供一些有價值的參考,并幫助他們領先一步。書中引用了許多不同產品的設計方案,這些信息對讀者來說同樣有益。
關于代碼示例的說明
本書中大多數模式都提供了代碼示例。這些代碼示例建立在我研究這些模式時,對各種產品所做的微型實現基礎之上。選擇編程語言的依據是它的普及度和可讀性—Java便是一個優秀的選擇。示例中只用到了Java最基本的語言特性,即方法和類,這在大多數編程語言中都是通用的。即便是只熟悉其他編程語言的讀者,也應該能夠輕松地理解這些代碼示例。不過,需要明確的是,本書并非專為某個具體的軟件平臺編寫。一旦掌握了這些代碼示例,你會發現無論是C++、Rust、Go、Scala還是Zig,其代碼庫中都有這些模式的影子。我期望的是,通過熟悉這些代碼示例和模式,你將能更加輕松地閱讀和理解各種開源產品的源代碼。
閱讀指南
本書共分為六部分。首先是兩章敘述性章節,這些章節構成了第一部分,它們涵蓋了分布式系統設計的基本主題,介紹了分布式系統設計中的挑戰及其解決策略,但并未深入討論這些策略的細節。
第二部分至第六部分提供了按模式結構化的詳盡解決方案。這些模式被劃分為四個核心類別:復制、分區、集群管理和網絡通信。每一類都是構建分布式系統的關鍵要素。
請將這些模式當作一種參考手冊,不需要逐字逐句地閱讀。你可以先瀏覽敘述性章節,以獲得對本書內容的整體理解,再根據個人興趣和實際需求,深入研究各個模式。
我希望這些模式能夠協助同行軟件專業人員在工作中做出明智的決策。
Unmesh Joshi (烏梅什·喬希)軟件架構領域的領軍人物,Thoughtworks 首席顧問,擁有超過24年的IT行業經驗。分布式系統領域的資深專家,對分布式系統的設計和實現有著深刻的理解,對分布式系統的架構模式有系統的梳理和總結。在 Scala、Akka、Kafka、Cassandra、Kubernetes、Docker和云服務等技術領域積累了豐富的經驗,這些技術專長使他能夠從理論到實踐全面掌握分布式系統的核心問題。
譯者序
推薦序
前言
致謝
第一部分 概述
第1章 分布式系統 2
1.1 單服務器的限制 3
1.2 業務邏輯和數據層分離 4
1.3 數據分區 4
1.4 故障觀察 5
1.5 復制:屏蔽故障 6
1.5.1 進程終止甚至崩潰 6
1.5.2 網絡延遲 6
1.5.3 進程暫停 6
1.5.4 時鐘不同步 7
1.6 定義分布式系統 7
1.7 模式方法 7
第2章 模式概述 9
2.1 在單服務器上保持數據的彈性 10
2.2 競爭性更新 11
2.3 處理主節點失效 12
2.4 依托“世代時鐘”解決多節點故障問題 14
2.5 符合仲裁機制方可提交日志記錄 17
2.6 從節點基于高水位標記提交 19
2.7 主節點用消息隊列來保持對眾多客戶端的響應 23
2.8 由從節點處理讀請求以減輕主節點的負擔 29
2.9 把大量數據分散到多節點分區 30
2.10 通過復制分區提高集群彈性 32
2.11 跨分區維持一致性至少需要兩個階段 33
2.12 分布式系統的順序不能依賴于系統時間戳 35
2.13 一致性核心負責管理數據集群的成員資格 42
2.14 使用Gossip傳播機制來管理分布式集群 45
第二部分 數據復制模式
第3章 預寫日志 53
3.1 問題的提出 54
3.2 解決方案 54
3.2.1 實現考慮 56
3.2.2 在事務存儲中的使用 56
3.2.3 與事件溯源對比 57
3.3 示例 58
第4章 日志分段 59
4.1 問題的提出 60
4.2 解決方案 60
4.3 示例 61
第5章 低水位標記 62
5.1 問題的提出 63
5.2 解決方案 63
5.2.1 基于快照的低水位標記 63
5.2.2 基于時間的低水位標記 64
5.3 示例 65
第6章 主節點與從節點 66
6.1 問題的提出 67
6.2 解決方案 67
6.2.1 主節點選舉 67
6.2.2 僅有多數讀/寫不足以提供強一致性保證 72
6.3 示例 72
第7章 心跳機制 73
7.1 問題的提出 74
7.2 解決方案 74
7.2.1 小型集群:基于共識算法的系統 75
7.2.2 技術考慮 76
7.2.3 大型集群:基于Gossip協議 76
7.3 示例 77
第8章 多數法定節點數 78
8.1 問題的提出 79
8.2 解決方案 79
8.2.1 決定集群中服務器的數量 79
8.2.2 靈活的多數法定節點數 80
8.3 示例 81
第9章 世代時鐘 82
9.1 問題的提出 83
9.2 解決方案 83
9.3 示例 86
第10章 高水位標記 87
10.1 問題的提出 88
10.2 解決方案 88
10.3 示例 92
第11章 Paxos 93
11.1 問題的提出 94
11.2 解決方案 94
11.2.1 協議流程 94
11.2.2 鍵值存儲示例 101
11.2.3 彈性Paxos 105
11.3 示例 105
第12章 復制日志 106
12.1 問題的提出 107
12.2 解決方案 107
12.2.1 Multi-Paxos和Raft 107
12.2.2 復制客戶端請求 108
12.2.3 主節點選舉 113
12.2.4 技術考慮 118
12.2.5 推送與拉取 120
12.2.6 日志中有什么 120
12.3 示例 125
第13章 單一更新隊列 126
13.1 問題的提出 127
13.2 解決方案 127
13.2.1 隊列的選擇 130
13.2.2 使用通道和輕量級線程 131
13.2.3 限流 131
13.2.4 其他考慮 132
13.3 示例 132
第14章 請求等待列表 133
14.1 問題的提出 134
14.2 解決方案 134
14.3 示例 139
第15章 冪等接收器 140
15.1 問題的提出 141
15.2 解決方案 141
15.2.1 使已保存的客戶端請求過期 144
15.2.2 移除已注冊的客戶端 145
15.2.3 最多一次、至少一次和恰好一次操作 145
15.3 示例 146
第16章 由從節點處理讀請求 147
16.1 問題的提出 148
16.2 解決方案 148
16.2.1 尋找最近的副本 148
16.2.2 連接斷開或慢速從節點 151
16.2.3 讀寫一致性 151
16.2.4 線性化讀 154
16.3 示例 154
第17章 版本化值 155
17.1 問題的提出 156
17.2 解決方案 156
17.2.1 版本化鍵的排序 156
17.2.2 讀多個版本 159
17.2.3 MVCC和事務隔離性 160
17.2.4 使用類似RocksDB的存儲引擎 161
17.3 示例 162
第18章 版本向量 163
18.1 問題的提出 164
18.2 解決方案 164
18.2.1 版本向量比較 165
18.2.2 在鍵值存儲中使用版本向量 167
18.3 示例 174
第三部分 數據分區模式
第19章 固定分區 177
19.1 問題的提出 178
19.2 解決方案 178
19.2.1 選擇哈希函數 179
19.2.2 將分區映射到集群節點 179
19.2.3