Last Updated:

分布式系统的模式

银狐

分布式系统给程序带来了特殊的挑战。它们通常要求我们拥有多个数据副本,这些副本需要保持同步。但是,我们不能依靠处理节点可靠地工作,并且网络延迟很容易导致不一致。尽管如此,许多组织仍依赖一系列核心分布式软件来处理数据存储,消息传递,系统管理和计算功能。这些系统面临共同的问题,可以通过类似的解决方案解决。本文将这些解决方案识别并开发为模式,通过它们我们可以建立对如何更好地理解,交流和教授分布式系统设计的理解。

下面是来自Unmesh Joshi的文章,Unmesh Joshi是ThoughtWorks的首席顾问。他是一位软件体系结构狂热者,他认为,理解分布式系统的原理与近十年来理解Web体系结构或面向对象的编程一样重要。希望他的见解可以为我们更好的理解分布式系统。

原文来自:https://martinfowler.com/articles/patterns-of-distributed-systems/

目录:

这是关于什么的

在过去的几个月中,我一直在ThoughtWorks上进行有关分布式系统的研讨会。举办研讨会时面临的主要挑战之一是如何将分布式系统的理论映射到Kafka或Cassandra等开源代码库,同时保持讨论的通用性足以涵盖广泛的解决方案。模式的概念提供了一个不错的出路。

模式结构本质上使我们能够专注于特定问题,从而很清楚地说明了为什么需要特定的解决方案。然后,解决方案描述使我们能够给出一个代码结构,该结构足够具体以显示实际的解决方案,但又足够通用以涵盖广泛的变体。模式技术还允许我们将各种模式链接在一起以构建一个完整的系统。这为讨论分布式系统实现提供了很好的词汇。

接下来是在主流开源分布式系统中观察到的第一组模式。我希望这些模式集对所有开发人员都有用。

分布式系统-实施角度

当今的企业体系结构充满了按自然分布的平台和框架。如果我们看到今天在典型的企业体系结构中使用的框架和平台的示例列表,它将看起来像以下内容:

平台/框架的类型例子
资料库卡桑德拉,HBase,Riak
消息经纪人卡夫卡(Pulsar)
基础设施Kubernetes,Mesos,Zookeeper,etcd,领事
内存中的数据/计算网格Hazelcast,关键Gemfire
有状态微服务Akka演员,Axon
文件系统HDFS,Ceph

所有这些本质上都是“分布式的”。分发系统意味着什么?有两个方面:

  • 它们在多台服务器上运行。集群中的服务器数量可以从少至三台服务器到几千台服务器不等。
  • 他们管理数据。因此,这些天生就是“有状态”系统。

当多个服务器参与存储数据时,有几种方法可能会出错。上述所有系统都需要解决这些问题。这些系统的实现对这些问题有一些重复性的解决方案。以一般形式理解这些解决方案有助于理解这些系统的广泛实现,并且在需要构建新系统时也可以作为很好的指导。输入模式。

模式

克里斯托弗·亚历山大(Christopher Alexander)提出的概念模式(Patterns)在软件社区中被广泛接受,用于记录用于构建软件系统的设计构造。模式提供了一种结构化的方式来查看问题空间,而解决方案可以多次看到并经过验证。使用模式的一种有趣方式是能够以模式序列或模式语言的形式将多个模式链接在一起,这为实现“整个”或完整的系统提供了一些指导。将分布式系统视为一系列模式是一种获得深入了解其实现的有用方法。

问题及其反复出现的解决方案。

当数据存储在多个服务器上时,可能会出错。

进程崩溃

进程可以随时崩溃。由于硬件故障或软件故障。进程崩溃的方式有很多种。

  • 系统管理员可以将其删除以进行日常维护。
  • 由于磁盘已满,并且未正确处理异常,因此可以在执行某些文件IO时将其杀死。
  • 在云环境中,这可能会更加棘手,因为一些不相关的事件可能会使服务器宕机。

底线是,如果进程负责存储数据,则必须将其设计为对存储在服务器上的数据提供持久性保证。即使进程突然崩溃,它也应保留所有已通知用户已成功存储的数据。根据访问方式,不同的存储引擎具有不同的存储结构,从简单的哈希映射到复杂的图形存储。由于将数据刷新到磁盘是最耗时的操作之一,因此无法将每次对存储的插入或更新都刷新到磁盘。因此,大多数数据库具有内存存储结构,这些存储结构仅定期刷新到磁盘。如果进程突然崩溃,则可能会丢失所有数据。

一种称为预写日志的技术用于解决这种情况。服务器将每个状态更改作为命令存储在硬盘上的仅附加文件中。附加文件通常是非常快速的操作,因此可以在不影响性能的情况下完成文件的添加。单个日志按顺序附加,用于存储每个更新。在服务器启动时,可以重播日志以再次建立内存状态。

这提供了耐用性保证。即使服务器突然崩溃,然后重新启动,数据也不会丢失。但是,在备份服务器之前,客户端将无法获取或存储任何数据。因此,如果服务器发生故障,我们将缺乏可用性。

一种显而易见的解决方案是将数据存储在多个服务器上。因此,我们可以在多个服务器上复制预写日志。

当涉及多个服务器时,还有更多的故障情况需要考虑。

网络延迟

在TCP / IP协议栈中,在通过网络传输消息时所引起的延迟没有上限。它可以根据网络上的负载而变化。例如,一条1 Gbps的网络链接可能会被触发的大数据作业淹没,从而填满网络缓冲区,并可能导致某些消息到达服务器的任意延迟。

在典型的数据中心中,服务器打包在机架中,并且有多个机架通过机架交换机的顶部连接。可能会有一棵交换机树将数据中心的一部分连接到另一部分。在某些情况下,一组服务器可以相互通信,但与另一组服务器断开连接。这种情况称为网络分区。然后,服务器通过网络进行通信的基本问题之一是何时知道特定服务器已发生故障。

这里有两个问题要解决。

  • 特定的服务器不能无限期地等待其他服务器是否崩溃。
  • 不应有两套服务器,每套服务器都认为另一套服务器发生了故障,因此继续为不同的客户端提供服务。这称为裂脑。

为了解决第一个问题,每台服务器都会定期向其他服务器发送HeartBeat消息。如果错过了心跳,则将发送心跳的服务器视为已崩溃。心跳间隔足够小,以确保不需要花费很多时间来检测服务器故障。如下所示,在最坏的情况下,服务器可能已启动并正在运行,但是考虑到服务器出现故障,群集作为一个整体可以继续前进。这样可以确保提供给客户端的服务不会中断。

第二个问题是脑裂。使用裂脑,如果两组服务器独立接受更新,则不同的客户端可以获取和设置不同的数据,一旦裂脑得到解决,就不可能自动解决冲突。

要解决大脑裂的问题,我们必须确保彼此断开连接的两组服务器不能独立取得进展。为确保这一点,只有大多数服务器都可以确认该动作,该服务器执行的每个动作才被认为是成功的。如果服务器无法获得多数,则它们将无法提供所需的服务,并且某些客户端组可能无法接收该服务,但是群集中的服务器将始终处于一致状态。占多数的服务器数量称为法定人数。如何确定仲裁人数?这是根据群集可以容忍的故障数决定的。因此,如果我们有五个节点的群集,则需要三个仲裁。通常,如果我们要容忍f个故障,则需要2f + 1的群集大小。

Quorum确保我们有足够的数据副本以承受某些服务器故障。但是,仅向客户提供强大的一致性保证是不够的。假设客户端在仲裁上启动了写操作,但是该写操作仅在一台服务器上成功。仲裁中的其他服务器仍具有旧值。当客户端从仲裁读取值时,如果具有最新值的服务器可用,则它可能会获得最新值。但是,如果仅当客户端开始读取值时,具有最新值的服务器不可用,它就可以很好地获得旧值。为了避免这种情况,某人需要跟踪仲裁是否同意特定的操作,并且仅将值发送给保证在所有服务器上都可用的客户端。 领导者和追随者在这种情况下使用。其中一台服务器当选的领导者和其他服务器充当追随者。领导者控制并协调对跟随者的复制。领导者现在需要确定哪些更改应对客户可见。高水位标记用于跟踪已知已成功复制到关注者法定人数的写日志中的条目。客户可以看到所有高水位之前的条目。领导者还将高水位标记传播给跟随者。因此,如果领导者失败并且跟随者之一成为新领导者,那么客户看到的内容就不会出现不一致之处。

流程暂停

但这还不是全部,即使有了Quorums和Leader and Followers,仍然需要解决一个棘手的问题。领导者进程可以任意暂停。进程可以暂停的原因有很多。对于支持垃圾回收的语言,可能会有很长的垃圾回收暂停时间。具有较长垃圾收集暂停时间的领导者可以与关注者断开连接,并在暂停结束后继续向关注者发送消息。同时,由于关注者没有收到领导者的任何心跳信号,因此他们可能选择了新的领导者并接受了客户的更新。如果旧领导者的请求按原样处理,则它们可能会覆盖某些更新。因此,我们需要一种机制来检测过时领导者的请求。 生成时钟用于标记和检测较早的领导者的请求。世代是单调增加的数字。

不同步的时钟和订购事件

从较新的消息中检测较旧的领导者消息的问题是保持消息顺序的问题。看来我们可以使用系统时间戳来订购一组消息,但是我们不能。我们不能使用系统时钟的主要原因是不能保证跨服务器的系统时钟是同步的。计算机中的一天中的时钟由石英晶体管理,并根据晶体的振荡来测量时间。

这种机制容易出错,因为晶体可以更快或更慢地振荡,因此不同的服务器可能具有截然不同的时间。一组服务器上的时钟通过称为NTP的服务进行同步。该服务会定期检查一组全局时间服务器,并相应地调整计算机时钟。

因为这是通过网络上的通信发生的,并且网络延迟可能会如上节所述,因此,时钟同步可能会由于网络问题而延迟。这可能会导致服务器时钟彼此偏移,并且在NTP同步发生后甚至会倒退。由于计算机时钟存在这些问题,因此通常不将时间用于订购事件。取而代之的是使用一种称为Lamport时间戳的简单技术。世代时钟就是一个例子。

放在一起-分布式系统示例

我们可以看到从头开始理解这些模式如何帮助我们建立一个完整的系统。我们将以共识实施为例。分布式共识是分布式系统实现的一种特例,它提供了最强的一致性保证。在流行的企业系统中常见的示例有ZookeeperetcdConsul。他们实现了zabRaft等共识算法 ,以提供复制和强大的一致性。还有其他流行的算法可以实现共识,Paxos用于Google的Chubby锁定服务,查看图章复制和虚拟同步。用非常简单的术语来说,共识是指一组服务器,它们在存储的数据,存储的顺序以及何时使该数据对客户端可见方面达成一致。

实现共识的模式序列

共识实现使用状态机复制来实现容错。在状态机复制中,存储服务(如键值存储)在所有服务器上复制,并且用户输入在每个服务器上以相同顺序执行。实现此目的的关键实现技术是在所有服务器上复制预写日志以具有“ Replicated Wal”。

我们可以将这些模式放在一起,以实现复制Wal,如下所示。

为了提供持久性保证,请使用预写日志。使用分段日志将预写日志分为多个段。这有助于低水位标记处理的原木清洁。通过在多台服务器上复制预写日志来提供容错能力。通过使用Leader和Followers管理服务器之间的复制。 法定人数用于更新高水位标记, 以确定哪些值对客户可见。通过使用奇异更新队列,所有请求均按严格顺序处理。在使用Single Socket Channel将领导者的请求发送给关注者时,将维护订单 。为了优化单个套接字通道上的吞吐量和延迟,使用了 请求管道。追随者了解从领导者那里得到HeartBeat领导者的可用性。如果领导者由于网络分区而暂时从群集断开连接,则可以使用“生成时钟”来检测它。

这样,以一般的形式理解问题及其重复出现的解决方案,有助于理解整个系统的组成部分。