分布式
2024年12月29日
Patterns of Distributed Systems
分布式系统模式

Patterns of Distributed Systems
该书通过分析Apache Kafka、Cassandra等开源项目的代码,提炼出分布式系统中常见的设计模式。书中涵盖了数据复制、数据分区以及节点间通信等方面的一系列模式,例如写前日志(Write-Ahead Log)、分段日志(Segmented Log)、领导者与追随者(Leader and Followers)、一致性核心(Consistent Core)、八卦传播(Gossip Dissemination)以及请求批处理(Request Batch)等,并结合代码示例解释了每种模式的原理、实现以及应用场景,旨在帮助读者更好地理解和构建分布式系统。
第一部分:导论与概述
- 导论:本书的核心概念是将复杂的分布式系统抽象成可复用的模式。通过命名组件、描述行为和交互方式,构建了一个分布式系统的模式语言,使系统可以被视为可组合的“乐高积木”。 这有助于避免在讨论分布式系统时因术语歧义而产生的误解。
- 概述:分布式系统的基本构建块包括数据分区和复制。本书首先关注数据复制,然后介绍分区。一个简单的示例数据记录被复制到多个节点,展示了分布式系统中数据一致性的挑战。例如,如果一个节点在更新数据过程中崩溃,会导致数据不一致。 使用领导者(Leader)模式可以解决这个问题,由一个领导者节点处理所有更新请求。 当领导者失效时,需要通过心跳(Heartbeat)机制来检测,并选举新的领导者。 高水位标记(High-Water Mark) 用于确保所有节点在领导者切换后保持数据一致性。
第二部分:数据复制模式
写前日志(Write-Ahead Log, WAL):
- 问题:如何在单服务器上实现持久化的原子更新?
- 解决方案:在修改数据之前,将所有操作记录到日志中。只有在日志写入成功后,才将操作应用到数据存储。 WAL 确保了即使发生崩溃,数据也可以从日志中恢复。
- 实现考虑:WAL 用于提供原子更新,而一致性和隔离需要通过并发控制机制(例如锁)来实现。
- 与其他模式的比较:与事件溯源模式相比,WAL 更侧重于数据持久性,而不是记录所有事件的历史。
分段日志(Segmented Log):
- 问题:如何高效地管理大型的写前日志?
- 解决方案:将日志分割成多个段,可以独立地管理每个段,例如,可以删除或归档旧的段。
- 低水位标记(Low-Water Mark): - 问题:如何在复制的数据中安全地删除旧的日志条目? - 解决方案:使用低水位标记来指示所有副本都已经应用的日志索引,之后就可以安全地删除此索引之前的日志条目。低水位标记可以是基于快照的或基于时间的。
心跳(Heartbeat):
- 问题:如何检测分布式系统中节点的失败?
- 解决方案:节点定期发送心跳消息给其他节点。如果一个节点在指定的时间内没有收到心跳,它会被认为是失败的。 心跳机制可以用于小集群(使用基于共识的系统)或大集群(使用基于 Gossip 的协议)。
- 技术考量:使用单套接字通道时,需要避免队头阻塞。
领导者和追随者(Leader and Followers):
- 问题:如何在分布式系统中实现数据复制和一致性?
- 解决方案:选举一个领导者来处理所有的写操作,然后将这些写操作复制给追随者。这确保了所有节点上的数据保持一致。
- 技术考量:使用单一更新队列来避免同步和锁定的麻烦。
Paxos:
- 问题:如何在不可靠的网络中达成共识?
- 解决方案:Paxos 算法分为三个阶段:准备阶段、接受阶段和提交阶段。
- 在准备阶段,提议者(proposer)向所有接受者(acceptor)发送准备请求,并获取承诺。
- 在接受阶段,提议者发送一个提议值,并请求接受者接受。
- 在提交阶段,提议者通知所有节点值已被选定。
- 核心概念:Paxos 使用多数仲裁来确保只有一个值被选定。
复制日志(Replicated Log):
- 问题:如何保证所有节点按照相同的顺序应用更新?
- 解决方案:通过一个复制日志记录所有更新操作,确保所有的副本按照相同的顺序来执行这些操作。 复制日志通常与领导者和追随者模式一起使用,其中领导者将日志复制到追随者。
- 技术考量:领导者需要知道之前大多数仲裁可能已经复制的日志条目。
单一更新队列(Singular Update Queue):
- 问题:如何处理来自多个生产者线程的并发更新,并保证顺序执行?
- 解决方案:使用一个单一的队列来处理所有更新请求,并使用一个专门的线程来从队列中读取并执行这些请求。 这确保了所有请求按照它们加入队列的顺序执行。
- 技术考量:队列的选择和背压是重要考虑因素。
请求等待列表(Request Waiting List):
- 问题:当需要向多个节点发送请求时,如何跟踪并处理这些请求的响应?
- 解决方案:使用一个请求等待列表来记录所有未完成的请求。当收到来自其他节点的响应时,将调用相应的回调函数。
- 技术考量:每个请求都标记一个关联 ID,用于将响应与请求匹配。
幂等接收器(Idempotent Receiver):
- 问题:如何在网络不可靠的情况下处理重复请求?
- 解决方案:确保服务器可以多次接收相同的请求,而不会产生副作用。 这可以通过检查请求是否已经处理过,或者使用唯一标识符来防止重复处理来实现。
- 技术考量:需要考虑如何过期保存的客户端请求。
追随者读取(Follower Reads):
- 问题:如何允许追随者节点读取数据,同时保持一致性?
- 解决方案:追随者可以读取数据,但如果请求的版本在追随者上不可用,则需要等待直到版本更新。
- 技术考量: 追随者读取必须有超时,以防止无限等待。
版本化值(Versioned Value):
- 问题:如何在分布式系统中处理并发的写操作,并避免数据丢失?
- 解决方案:为每个值分配一个版本号。当多个节点更新相同的值时,使用版本号来区分不同的更新。
版本向量(Version Vector):
- 问题:如何处理在不同节点上同时发生的并发更新?
- 解决方案:使用版本向量来跟踪每个节点的更新。版本向量可以检测并发更新,并允许存储多个版本的值。
- 核心概念:使用版本向量进行比较可以确定值的顺序关系:之前、之后或并发。
第三部分:数据分区模式
固定分区(Fixed Partitions):
- 问题:如何将数据均匀地分布在多个节点上?
- 解决方案:将数据划分成固定数量的逻辑分区,然后将这些分区映射到集群节点。 分区数量通常是节点数量的倍数。 使用哈希函数来确定哪个分区存储特定的数据。
- 技术考量:客户端库通过一致性核心获取分区到集群节点的映射。
按节点数比例分配分区: - 问题:如何在节点数量变化时,保持数据分区均匀分配? - 解决方案:根据集群中节点的数量动态地调整分区数量。
键范围分区(Key-Range Partitions):
- 问题:如何支持范围查询,并允许按键的范围进行数据分区?
- 解决方案:将数据按照键的范围划分成多个分区。 每个分区都包含一个特定范围内的键。
- 技术考量:键范围分区支持自动分裂,以处理数据量增长。
事务性键值存储:
- 问题:如何在分布式环境中提供ACID事务保证?
- 解决方案:通过两阶段提交协议来协调跨多个节点的事务。
- 技术考量:可以使用不同的等待策略来处理锁冲突,例如Wound-Wait 和 Wait-Die。
多版本并发控制(MVCC):
- 问题:如何在不阻塞读取操作的情况下,实现并发的写操作?
- 解决方案:为数据的每个版本分配一个时间戳,并且保留多个版本的数据。 读操作可以读取过去版本的数据,而不会被写操作阻塞。
Lamport 时钟:
- 问题:如何在分布式系统中维护事件的因果关系?
- 解决方案:为每个事件分配一个逻辑时间戳,并确保如果一个事件发生在另一个事件之前,则它的时间戳小于另一个事件的时间戳。
- 技术考量:Lamport 时钟为分布式系统提供了一种部分顺序。
混合时钟(Hybrid Clock):
- 问题:如何在分布式系统中维护单调的时间戳,并反映真实时间?
- 解决方案:结合系统时间和逻辑计数器来生成混合时间戳,可以确保时间的单调性,同时又能大致反映事件的真实时间。
- 技术考量:混合时钟可用于实现多版本存储。
时钟边界等待(Clock-Bound Wait):
- 问题:如何在具有不确定时钟的分布式系统中,保证读写操作的一致性?
- 解决方案:每个节点都维护一个时钟边界,并使用这个边界来等待写入操作完成,确保写入时间在过去。
- 技术考量:读操作也需要等待直到确定可以读取到最新的数据。
第四部分:分布式系统模式
一致性核心(Consistent Core):
- 问题:如何在分布式系统中实现强一致性的元数据存储?
- 解决方案:使用共识算法(例如 Raft)来实现一个强一致的键值存储,用于存储元数据。 一致性核心通常用于跟踪集群成员、注册租约以及实现服务发现。
- 技术考量:使用单套接字通道与一致性核心通信。
租约(Lease):
- 问题:如何为分布式资源分配访问权限?
- 解决方案:使用租约来授权访问特定的资源。 租约在一段时间后到期,需要定期刷新。 租约可以用于节点注册和故障检测。
- 技术考量:租约通常在一致性核心上实现。
状态监视(State Watch):
- 问题:如何在集群状态发生变化时通知其他节点?
- 解决方案:使用状态监视来注册对特定状态变化的监听器。当状态发生变化时,监听器会收到通知。
- 技术考量:状态监视通常由一致性核心提供。
Gossip 传播:
- 问题:如何在分布式系统中传播节点的状态信息?
- 解决方案:节点定期与其他节点交换状态信息,确保所有节点最终获得相同的状态。 Gossip 协议可以用于组播和故障检测。
- 技术考量:Gossip 协议是最终一致性的。
涌现领导者 (Emergent Leader):
- 问题:如何在一个去中心化的集群中选举领导者?
- 解决方案:集群节点通过互相通信来选择领导者,而不是依赖于一个中心化的协调器。
- 技术考量:需要处理脑裂问题。
单套接字通道(Single-Socket Channel):
- 问题:如何在服务器之间建立高效的通信通道?
- 解决方案:使用单个套接字通道来发送和接收请求,避免了多余的连接开销。
- 技术考量:单套接字通道使用阻塞的 IO 操作。
请求批处理(Request Batch):
- 问题:如何减少网络请求的开销?
- 解决方案:将多个请求打包到一个批次中,然后将这个批次发送到服务器。 服务器解包这个批次,并处理其中的每个请求。
总结
这本书深入探讨了构建可靠、可扩展的分布式系统所需的各种模式。它不仅解释了每个模式如何解决特定问题,还提供了技术考量和实际应用示例。 这些模式可以作为构建分布式系统的基础,帮助开发者更好地理解和设计复杂的系统。