分布式系统的模式 - martinfowler


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

点击标题见原文,大意如下:
当今的企业体系结构充满了按自然分布的平台和框架。如果我们看到今天在典型的企业体系结构中使用的框架和平台的示例列表,它将类似于以下内容:

  • 数据库:Cassandra, HBase, Riak
  • 消息经纪人:卡夫卡、Pulsar
  • 基础设施:Kubernetes,Mesos,Zookeeper,etcd,领事
  • 内存中的数据/计算网格: Hazlecast,Pivotal Gemfire
  • 有状态微服务:Akka Actors, Axon
  • 文件系统:HDFS,Ceph

所有这些本质上都是“分布式的”。分发系统意味着什么?有两个方面:
  • 它们在多台服务器上运行。群集中的服务器数量可以从少至三台到几千台不等。
  • 他们管理数据。因此,这些是天生的“有状态”系统。

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

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

问题及其反复出现的解决方案。
当数据存储在多个服务器上时,可能会出错。(banq注:讨厌出错是追求高可靠性导致)
1. 进程崩溃
进程可以随时崩溃。由于硬件故障或软件故障。进程崩溃的方式有很多种。

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

一种称为预写日志的技术用于解决这种情况。服务器将每个状态更改作为命令存储在硬盘上的仅附加文件中。附加文件通常是非常快速的操作,因此可以完成文件而不影响性能。顺序附加的单个日志用于存储每个更新。在服务器启动时,可以重播日志以再次建立内存状态。
这提供了耐用性保证。即使服务器突然崩溃,然后重新启动,数据也不会丢失。但是,在备份服务器之前,客户端将无法获取或存储任何数据。因此,如果服务器发生故障,我们将缺乏可用性。
一种显而易见的解决方案是将数据存储在多个服务器上。因此,我们可以在多个服务器上复制预写日志。

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

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

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

第二个问题是脑裂。使用裂脑,如果两组服务器独立接受更新,则不同的客户端可以获取和设置不同的数据,一旦裂脑得到解决,就不可能自动解决冲突。
为确保这一点,只有大多数服务器都可以确认该动作,该服务器执行的每个动作才被认为是成功的。如果服务器无法获得多数,则它们将无法提供所需的服务,并且某些客户端组可能无法接收该服务,但是群集中的服务器将始终处于一致状态。占多数的服务器数量称为法定人数

法定人数仲裁确保我们有足够的数据副本以承受某些服务器故障。但是,仅向客户提供强大的一致性保证是不够的。假设客户端在仲裁上启动了写操作,但是该写操作仅在一台服务器上成功。仲裁中的其他服务器仍具有旧值。当客户端从仲裁读取值时,如果具有最新值的服务器可用,则它可能会获得最新值。但是,如果仅当客户端开始读取值时,具有最新值的服务器不可用,它就可以很好地获得旧值。为了避免这种情况,某人需要跟踪仲裁是否同意特定的操作,并且仅将值发送给保证在所有服务器上都可用的客户端。 领导者和追随者在这种情况下使用。

其中一台服务器当选为领导者,其他服务器充当跟随者。领导者控制并协调对跟随者的复制。领导者现在需要确定哪些更改应对客户可见。

3.流程暂停
领导者进程可以任意暂停。进程可以暂停的原因很多。对于支持垃圾回收的语言,可能会有很长的垃圾回收暂停。具有较长垃圾收集暂停时间的领导者可以与关注者断开连接,并在暂停结束后继续向关注者发送消息。同时,由于关注者没有收到领导者的任何心跳信号,因此他们可能选择了新的领导者并接受了客户的更新。如果旧领导者的请求按原样处理,则它们可能会覆盖某些更新。因此,我们需要一种机制来检测过时领导者的请求。 generation时钟用于标记和检测来自较早领导者的请求。generation是单调增加的数字。

4.不同步的时钟和订购事件
从较新的消息中检测较旧的领导者消息的问题是保持消息顺序的问题。看来我们可以使用系统时间戳来订购一组消息,但是我们不能。我们不能使用系统时钟的主要原因是不能保证跨服务器的系统时钟是同步的。计算机中的一天中的时钟由石英晶体管理,并根据晶体的振荡来测量时间。
这种机制容易出错,因为晶体可以更快或更慢地振荡,因此不同的服务器可能具有截然不同的时间。一组服务器上的时钟通过称为NTP的服务进行同步。该服务会定期检查一组全局时间服务器,并相应地调整计算机时钟。
因为这是通过网络上的通信发生的,并且网络延迟可能会如以上各节所述,因此,时钟同步可能会由于网络问题而延迟。这可能导致服务器时钟彼此之间漂移,并且在NTP同步发生后甚至会倒退。由于计算机时钟存在这些问题,因此通常不将一天中的时间用于订购事件。取而代之的是使用一种称为Lamport时间戳的简单技术。generation时钟就是一个例子。

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

1.实现共识的模式序列
共识实现使用状态机复制来实现容错。在状态机复制中,像键值存储一样的存储服务在所有服务器上复制,并且用户输入在每个服务器上以相同顺序执行。用于实现此目的的关键实现技术是在所有服务器上复制预写日志以具有“ Replicated Wal”。
我们可以将这些模式放在一起以实现复制Wal。
为了提供持久性保证,请使用预写日志。使用分段日志将预写日志分为多个段。这有助于低水位标记处理的原木清洁。通过在多个服务器上复制预写日志来提供容错能力。通过使用Leader和Followers管理服务器之间的复制。 法定人数用于更新高水位线, 以确定哪些值对客户可见。通过使用奇异更新队列,所有请求均按严格顺序处理。使用单套接字通道将领导者的请求发送给关注者时,订单将得到维护 。为了优化单个套接字通道上的吞吐量和延迟,使用了 请求管道。追随者了解从领导者那里得到HeartBeat领导者的可用性。如果领导者由于网络分区而暂时从群集断开连接,则可以使用“ generation时钟”来检测它。

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

下一步
分布式系统是一个巨大的话题。这里涵盖的模式集只是一小部分,涵盖了不同的类别,以展示模式方法如何帮助理解和设计分布式系统。我将继续添加到该集合中,以广泛地包括在任何分布式系统中解决的以下类别的问题

  • 组成员资格和故障检测
  • 分区
  • 复制和一致性
  • 存储
  • 处理中