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