内容简介
本书深入剖析了主流开源分布式系统模式,包括模式中的常见问题和解决方案,并展示了Kafka和Kubernetes等系统的真实代码示例,以帮助企业架构师和开发人员更好地理解这些系统的工作原理,以及分布式系统的设计原则,为应对数据存储在多台服务器上时可能出现的各种问题做好准备。
通过阅读本书,读者将:
了解什么是分布式系统,以及为什么需要分布式系统。
更深入地理解分布式系统模式设计所面临的挑战,以选择合适的云服务和产品。
理解包括数据库、内存数据网格、消息代理,以及各种云服务在内的系统的实现原理。
自信地浏览开源代码库,并清晰地看到模式和解决方案如何映射到如Kafka和Kubernetes这样的真实世界系统中。
本书对于分布式架构工程师以及想要构建自己的分布式系统的开发者来说,是一本有价值的参考书。
目录
第一部分 概述
第1章 分布式系统3
1.1 单服务器的限制3
1.2 业务逻辑和数据层分离5
1.3 数据分区 6
1.4 故障观察 7
1.5 复制:屏蔽故障 9
1.5.1 进程终止甚至崩溃 9
1.5.2 网络延迟 9
1.5.3 进程暂停 9
1.5.4 时钟不同步10
1.6 定义分布式系统10
1.7 模式方法10
第2章 模式概述 13
2.1 在单服务器上保持数据的弹性 14
2.2 竞争性更新 15
2.3 处理主节点失效 17
2.4 依托“世代时钟”解决多节点故障问题 21
2.5 符合仲裁机制方可提交日志记录 26
2.6 从节点基于高水位标记提交 29
2.7 主节点用消息队列来保持对众多客户端的响应 34
2.8 由从节点处理读请求以减轻主节点的负担 40
2.9 把大量数据分散到多节点分区 42
2.10 通过复制分区提高集群的弹性 45
2.11 跨分区维持一致性至少需要两个阶段 46
2.12 分布式系统的顺序不能依赖于系统时间戳 49
2.13 一致性核心可以管理数据集群的成员资格 58
2.14 用Gossip传播机制来管理分布式集群 62
第二部分 数据复制模式
第3章 预写日志 71
3.1 问题的提出 71
3.2 解决方案 71
3.2.1 实现考虑 73
3.2.2 在事务存储中的使用 74
3.2.3 与事件溯源对比 76
3.3 示例 76
第4章 日志分段 77
4.1 问题的提出 77
4.2 解决方案 77
4.3 示例 79
第5章 低水位标记 81
5.1 问题的提出 81
5.2 解决方案 81
5.2.1 基于快照的低水位标记 82
5.2.2 基于时间的低水位标记 83
5.3 示例 83
第6章 主节点与从节点 85
6.1 问题的提出 85
6.2 解决方案 85
6.2.1 主节点选举 86
6.2.2 仅有多数读/写不足以提供强一致性保证 91
6.3 示例 92
第7章 心跳机制 93
7.1 问题的提出 93
7.2 解决方案 93
7.2.1 小型集群:基于共识算法的系统 95
7.2.2 技术考虑 96
7.2.3 大型集群:基于Gossip协议 97
7.3 示例 98
第8章 多数法定节点数 99
8.1 问题的提出 99
8.2 解决方案 100
8.2.1 决定集群中服务器的数量 100
8.2.2 灵活的多数法定节点数 101
8.3 示例 102
第9章 世代时钟 103
9.1 问题的提出 103
9.2 解决方案 104
9.3 示例 107
第10章 高水位标记 109
10.1 问题的提出 109
10.2 解决方案 109
10.3 示例 115
第11章 Paxos 117
11.1 问题的提出 117
11.2 解决方案 117
11.2.1 协议流程 118
11.2.2 键值存储示例 127
11.2.3 弹性Paxos 132
11.3 示例 132
第12章 复制日志 133
12.1 问题的提出 133
12.2 解决方案 133
12.2.1 Multi-Paxos和Raft 134
12.2.2 复制客户端请求 135
12.2.3 主节点选举 141
12.2.4 技术考虑 150
12.2.5 推送与拉取 151
12.2.6 日志中有什么 151
12.3 示例 158
第13章 单一更新队列 159
13.1 问题的提出 159
13.2 解决方案 159
13.2.1 队列的选择 164
13.2.2 使用通道和轻量级线程 164
13.2.3 限流 165
13.2.4 其他考虑 166
13.3 示例 166
第14章 请求等待列表 167
14.1 问题的提出 167
14.2 解决方案 167
14.3 示例 173
第15章 幂等接收器 175
15.1 问题的提出 175
15.2 解决方案 175
15.2.1 使已保存的客户端请求过期 179
15.2.2 移除已注册的客户端 180
15.2.3 最多一次、至少一次和恰好一次操作 181
15.3 示例 181
第16章 由从节点处理读请求 183
16.1 问题的提出 183
16.2 解决方案 183
16.2.1 寻找最近的副本 184
16.2.2 连接断开或慢速从节点 187
16.2.3 读写一致性 188
16.2.4 线性化读 191
16.3 示例 191
第17章 版本化值 193
17.1 问题的提出 193
17.2 解决方案 193
17.2.1 版本化键的排序 194
17.2.2 读多个版本 197
17.2.3 MVCC和事务隔离性 199
<前言/序言
本书写作缘由
在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,其代码库中都有这些模式的影子。我期望的是,通过熟悉这些代码示例和模式,你将能更加轻松地阅读和理解各种开源产品的源代码。
阅读指南
本书共分为六部分。首先是两章叙述性内容,这两章构成了第一部分,它们涵盖了分布式系统设计的基本主题,介绍了分布式系统设计中的挑战及其解决策略,但并未深入讨论这些策略的细节。
第二部分至第六部分提供了按模式结构化的详尽解决方案。这些模式被划分为四个核心类