Google Code大学 ​​​​​​​ ——分布式系统设计简介

 

Google Code大学 ​​​​​​​ ——分布式系统设计简介

Google Code大学

分布式系统设计简介

 

目录

Google Code大学

分布式系统设计简介

目录

受众和先决条件

基础

那么它是如何完成的?

远程过程调用

一些分布式设计原则

演习

参考



受众和先决条件

本教程介绍了分布式系统设计的基础知识。先决条件是使用C ++或Java等语言,对网络的基本理解以及数据结构和算法的重要编程经验。

 

基础

什么是分布式系统?如果没有先定义许多其他东西,这是很难定义的事情之一。这是分布式系统的“级联”定义:

 

一个程序

是你写的代码。

一个过程

是你运行它时得到的。

一条消息

用于进程之间的通信。

一个

是一条可能在电线上传播的消息片段。

一个协议

是对消息格式的正式描述,以及两个进程为了交换这些消息必须遵循的规则。

一个网络

是连接计算机,工作站,终端,服务器等的基础设施。它由通过通信链路连接的路由器组成。

一个组件

可以是运行流程所需的流程或任何硬件,支持流程之间的通信,存储数据等。

一个分布式系统

是一个应用程序,它执行协议集合以协调网络上多个进程的操作,以便所有组件一起协作以执行一组或一组相关任务。

为什么要构建分布式系统?有许多优点,包括以开放和可扩展的方式连接远程用户和远程资源的能力。当我们说开放时,我们的意思是每个组件都不断地与其他组件交互。当我们说可扩展时,我们的意思是系统可以很容易地改变,以适应用户,资源和计算实体数量的变化。

因此,考虑到分布式组件的组合功能,分布式系统可以比独立系统的组合更大,更强大。但这并不容易 - 对于分布式系统来说,它必须是可靠的。由于同时运行的组件之间的交互的复杂性,这是难以实现的目标。

为了真正可靠,分布式系统必须具有以下特征:

 

  • 容错:它可以从组件故障中恢复,而不会执行不正确的操作。
  • 高度可用:它可以恢复操作,即使某些组件发生故障也可以恢复提供服务。
  • 可恢复:在修复故障原因后,失败的组件可以自行重启并重新加入系统。
  • 一致性:系统可以在存在并发和故障的情况下协调多个组件的操作。这是分布式系统像非分布式系统一样运行的能力的基础。
  • 可扩展:即使系统的某些方面缩放到更大的尺寸,它也可以正常运行。例如,我们可能会增加运行系统的网络的大小。这增加了网络中断的频率,并且可能降低“不可扩展”系统的性能。同样,我们可能会增加用户或服务器的数量,或者增加系统的总体负载。在可扩展的系统中,这不应该产生显着的影响。
  • 可预测的绩效:能够及时提供所需的响应能力。
  • 安全:系统验证对数据和服务的访问[1]

这些是高标准,难以实现。可能最困难的挑战是分布式系统即使在组件发生故障时也必须能够继续正确运行。这个问题将在下面的Ken Arnold访谈摘录中讨论。Ken是Sun的研究科学家,也是Jini的原始架构师之一,并且是设计CORBA的架构团队的成员。

 


失败是分布式和本地编程之间的定义差异,因此您必须设计具有失败预期的分布式系统。想象一下,问人们,“如果事情发生的可能性是10 13,那么它会发生多少次?” 常识是回答“从不”。就人类而言,这是一个无限大的数字。但是,如果你问一位物理学家,她会说,“一直都是这样。在一立方英尺的空气中,这些事情一直在发生。”

在设计分布式系统时,您必须说“失败始终发生”。所以当你设计时,你会设计失败。这是你最关心的问题。设计失败意味着什么?一个经典问题是部分失败。如果我向您发送消息然后发生网络故障,则有两种可能的结果。一个是消息传到了你,然后网络坏了,我只是没有得到回应。另一个是从来没有告诉过你的消息,因为网络在它到达之前就已经破了。

因此,如果我从未收到回复,我怎么知道这两个结果中的哪一个发生了?没有最终找到你,我无法确定。网络必须修复或者你必须上来,因为可能发生的事情不是网络故障,而是你死了。这如何改变我的设计方式?首先,它将简单性的乘数放在一个乘数上。我能做的事情越多,我就越需要考虑恢复。[2]


处理故障是分布式系统设计中的一个重要主题。失败分为两个明显的类别:硬件和软件。直到80年代后期,硬件故障才成为主要问题,但从那时起内部硬件可靠性大大提高。减小较小电路的发热量和功耗,减少芯片外连接和布线,以及高质量的制造技术都在提高硬件可靠性方面发挥了积极作用。今天,问题通常与连接和机械设备有关,即网络故障和驱动器故障。

软件故障是分布式系统中的一个重要问题。即使经过严格的测试,软件漏洞仍占计划外停机时间的很大一部分(估计为25-35%)。成熟系统中的残留缺陷可分为两大类[5]。

 

  • Heisenbug:一种在观察或研究时似乎消失或改变其特征的虫子。一个常见的例子是在程序的发布模式编译中发生的错误,但在调试模式下进行研究时则不会发生。“heisenbug”这个名字是对“海森堡不确定性原理”的双关语,这是一个量子物理术语,通常(但不准确)用于指观察者影响他们所观察事物的测量的方式,单独观察(这实际上是观察者效应,并且通常与海森堡不确定性原理相混淆)。

     

  • Bohrbug:一种虫子(以玻尔原子模型命名),与heisenbug相反,在研究时不会消失或改变其特征。Bohrbug通常在明确定义的条件下可靠地表现出来。[6]

Heisenbugs在分布式系统中比在本地系统中更普遍。其中一个原因是程序员难以获得并发过程交互的连贯而全面的视图。

让我们更详细地了解分布式系统中可能发生的故障类型:

 

  • 停止失败:组件只是停止。除非超时,否则无法检测到故障:它会停止发送“我活着”(心跳)消息或无法响应请求。你的电脑冻结是一个停顿的失败。
  • 失败停止:通过向其他组件发送某种通知而停止失败。告知其客户端即将关闭的网络文件服务器是故障停止。
  • 省略失败:主要由于缺少缓冲空间而导致无法发送/接收消息,导致丢弃消息而不向发送方或接收方发送通知。当路由器过载时会发生这种情况。
  • 网络故障:网络链路中断。
  • 网络分区故障:网络分成两个或多个不相交的子网络,在这些子网络中可以发送消息,但消息丢失之间。这可能由于网络故障而发生。
  • 定时失败:违反了系统的时间属性。例如,用于协调进程的不同计算机上的时钟不同步; 当邮件延迟超过阈值期间等时
  • 拜占庭故障:它捕获了几种类型的错误行为,包括数据损坏或丢失,恶意程序引起的故障等。[1]

我们的目标是设计一个具有上述特性的分布式系统(容错,高可用,可恢复等),这意味着我们必须设计失败。为了设计失败,我们必须小心不要对系统组件的可靠性做出任何假设。

当他们第一次构建分布式系统时,每个人都做出以下八个假设。这些在这个领域是众所周知的,它们通常被称为“8谬误”。

 

  1. 网络可靠。
  2. 延迟为零。
  3. 带宽是无限的。
  4. 网络是安全的。
  5. 拓扑不会改变。
  6. 有一个管理员。
  7. 运输成本为零。
  8. 网络是同质的。[3]

 

延迟:启动数据请求和实际数据传输开始之间的时间。
带宽:衡量通信信道容量的指标。信道带宽越高,信息带来的信息就越多。
拓扑:构建网络时可采用的不同配置,例如环形,总线,星形或网状。
同构网络:运行单一网络协议的网络。

 

那么它是如何完成的?

构建一个在不可靠的通信网络上运行的可靠系统似乎是一个不可能实现的目标。我们*处理不确定性。进程知道自己的状态,并且知道最近其他进程处于什么状态。但是这些过程无法了解彼此的当前状态。它们缺乏共享内存的等价物。他们还缺乏准确的方法来检测故障,或者将本地软件/硬件故障与通信故障区分开来。

分布式系统设计显然是一项挑战性的工作。当我们不被允许做任何事情时,我们如何做到这一点,有那么多复杂性?我们首先限制范围。我们将专注于特定类型的分布式系统设计,该设计使用主要是标准协议的客户端 - 服务器模型。事实证明,这些标准协议为可靠的网络通信的低级细节提供了相当大的帮助,这使我们的工作更容易。让我们首先回顾一下客户端 - 服务器技术和协议。

Google Code大学 ​​​​​​​ ——分布式系统设计简介 在客户端 - 服务器应用程序中,服务器提供一些服务,例如处理数据库查询或发送当前股票价格。该客户端使用由服务器提供的,无论是显示数据库查询结果提供给用户或进行股票购买建议投资者服务。客户端和服务器之间发生的通信必须可靠。也就是说,不能丢弃任何数据,它必须按照服务器发送的顺序到达客户端。

我们在分布式系统中遇到了许多类型的服务器。例如,文件服务器 管理文件系统所在的磁盘存储单元。数据库服务器容纳数据库并使其可供客户端使用。网络名称服务器实现符号名称或服务描述与诸如提供服务的进程的IP地址和端口号之类的值之间的映射。

在分布式系统中,可以存在许多特定类型的服务器,例如,多个文件服务器或多个网络名称服务器。术语服务用于表示特定类型的一组服务器。我们说 当需要访问服务的进程与提供服务的特定服务器相关联时,就会发生绑定。有许多绑定策略定义了如何选择特定服务器。例如,策略可以基于位置(Unix NIS客户端首先在自己的机器上查找服务器); 或者它可以基于负载平衡(CICS客户端以这样的方式绑定,即尝试对所有客户端进行统一响应)。

分布式服务可以采用数据复制,其中服务维护多个数据副本以允许在多个位置进行本地访问,或者在服务器进程可能崩溃时提高可用性。缓存是一个相关概念,在分布式系统中非常常见。我们说如果一个进程在本地维护了一个数据副本,就会缓存数据,以便在需要时再次快速访问。一个高速缓存命中是当一个请求来自高速缓存的数据满意,而不是从主要服务。例如,浏览器使用文档缓存来加速对常用文档的访问。

缓存类似于复制,但缓存的数据可能会变得陈旧。因此,可能需要一种用于在使用之前验证缓存数据项的策略。如果主服务主动刷新缓存,则缓存与复制相同。[1]

如前所述,客户端和服务器之间的通信需要可靠。您之前可能听说过TCP / IP。Internet协议(IP)套件是一组通信协议,允许在Internet和大多数商业网络上进行通信。传输控制协议(TCP)是该套件的核心协议之一。使用TCP,客户端和服务器可以创建彼此的连接,通过它们可以在数据包中交换数据。该协议保证从发送方到接收方的数据的可靠和有序传送。

IP套件可以看作一组图层,每个图层都具有仅使用下面图层功能的属性,并且只向上面的图层输出功能。实现由层组成的协议行为的系统称为协议栈。协议栈可以用硬件或软件实现,也可以两者混合实现。通常,只有较低层以硬件实现,较高层以软件实现。


资源:TCP / IP的历史反映了互联网的发展。以下是此历史的简要概述。


IP套件中有四层:

  1. 应用层:大多数需要网络通信的程序都使用应用层。数据以特定于应用程序的格式从程序传递到下一层,然后封装到传输层协议中。应用程序的示例是HTTP,FTP或Telnet。

     

  2. 传输层:传输层的职责包括独立于底层网络的端到端消息传输,以及错误控制,分段和流控制。传输层的端到端消息传输可以分类为面向连接 (TCP)或无连接(UDP)。TCP是两种协议中更复杂的,提供可靠的传输。首先,TCP确保接收计算机已准备好接受数据。它使用三包握手,其中发送方和接收方都同意他们已准备好进行通信。其次,TCP确保数据到达目的地。如果接收方未确认特定数据包,TCP通常会自动重传该数据包三次。如有必要,TCP还可以将大数据包拆分为较小的数据包,以便数据可以在源和目标之间可靠地传输。TCP丢弃重复的数据包并重新排列不按顺序到达的数据包。

    Google Code大学 ​​​​​​​ ——分布式系统设计简介

    UDP类似于TCP,因为它是一种通过网络发送和接收数据包的协议,但有两个主要区别。首先,它是无连接的。这意味着一个程序可以将一大堆数据包发送到另一个程序,但这就是他们关系的结束。第二个可能会发送一些回到第一个,第一个可能会发送更多,但从来没有一个牢固的连接。UDP也与TCP不同,因为它不提供任何类型的保证,接收器将接收以正确顺序发送的数据包。所有保证的是数据包的内容。这意味着它要快得多,因为在数据包级别之上没有额外的错误检查开销。出于这个原因,游戏经常使用这个协议。在游戏中 如果用于更新屏幕位置的一个数据包丢失,则播放器将稍微猛拉一下。其他数据包只会更新位置,而丢失的数据包 - 虽然使运动稍微粗糙 - 不会改变任何东西。

    Google Code大学 ​​​​​​​ ——分布式系统设计简介

    虽然TCP比UDP更可靠,但协议仍有许多方面失败的风险。TCP使用确认和重传来检测和修复丢失。但它无法克服较长的通信中断,导致发送方和接收方断开足够长的时间以破坏重传策略。正常的最大断开时间在30到90秒之间。当两个端点都很好时,TCP可以发出故障信号并放弃。这只是TCP如何失败的一个例子,即使它确实提供了一些缓解策略。

     

  3. 网络层:如最初定义的那样,网络层解决了在单个网络上获取数据包的问题。随着网络互联概念的出现,该层增加了额外的功能,即将数据从源网络获取到目标网络。这通常涉及通过网络网络(例如因特网)路由分组。IP执行从源到目标获取数据包的基本任务。
  4. 链路层:链路层处理数据的物理传输,通常涉及将帧头和预告片放在数据包上,以便在物理网络上传输并在此过程中处理物理组件。

 


资源:有关IP套件的更多信息,请参阅Wikipedia文章


远程过程调用

许多分布式系统是使用TCP / IP构建的,作为组件之间通信的基础。随着时间的推移,客户端与服务器交互的有效方法演变为RPC,这意味着远程过程调用。它是一种基于扩展本地过程调用概念的强大技术,因此被调用过程可能不存在于与调用过程相同的地址空间中。这两个进程可能位于同一系统上,也可能位于不同的系统上,并且网络连接它们。

RPC类似于函数调用。与函数调用一样,当创建RPC时,参数将传递给远程过程,并且调用者将等待返回响应。在下图中,客户端进行过程调用,向服务器发送请求。客户端进程等待,直到收到回复或超时。当请求到达服务器时,它调用执行所请求服务的调度例程,并将回复发送到客户端。RPC调用完成后,客户端进程继续。

 

Google Code大学 ​​​​​​​ ——分布式系统设计简介

线程在基于RPC的分布式系统中很常见。每个传入服务器的请求通常都会生成一个新线程。客户端中的线程通常会发出RPC然后阻塞(等待)。收到回复后,客户端线程将恢复执行。

编写基于RPC的代码的程序员做了三件事:

 

  1. 指定客户端 - 服务器通信的协议
  2. 开发客户端程序
  3. 开发服务器程序

通信协议由协议编译器生成的存根创建。存根是一个例程,除了声明自身及其接受的参数之外,实际上并没有做太多的事情。存根包含足够的代码以允许它被编译和链接。

客户端和服务器程序必须通过协议中指定的过程和数据类型进行通信。服务器端注册可由客户端调用的过程,并接收和返回处理所需的数据。客户端调用远程过程,传递任何所需数据并接收返回的数据。

因此,RPC应用程序使用存根生成器生成的类来执行RPC并等待它完成。程序员需要在服务器端提供类,这些类提供处理RPC请求的逻辑。

RPC引入了一组本地过程编程中不存在的错误情况。例如, 当客户端启动时服务器未运行时,可能会发生绑定错误。 如果客户端是针对服务器的一个版本编译的,则会发生版本不匹配,但服务器现在已更新为较新版本。服务器崩溃,网络问题或客户端计算机上的问题可能导致超时。

某些RPC应用程序将这些类型的错误视为不可恢复。但是,容错系统具有关键服务的备用源以及从主服务器到备份服务器的故障转移

当客户端需要知道请求的结果以便在服务器发生故障后采取下一步骤时,会发生具有挑战性的错误处理案例。这有时会导致不正确的操作和结果。例如,假设客户端进程请求售票服务器检查卡内基音乐厅管弦乐部分的座位。如果可用,服务器会记录请求和销售。但是请求因超时而失败。座位是否可用且销售情况如何?即使有可以重新发出请求的备份服务器,也存在客户将被出售两张票的风险,这在卡内基音乐厅[1]中是一个昂贵的错误。

以下是一些需要处理的常见错误情况:

  • 导致重新传输的网络数据丢失:通常,系统尝试实现“最多一次”传输尝试。在最坏的情况下,如果发生重复传输,我们会尽量减少多次接收数据所造成的任何损害。

     

  • RPC操作期间服务器进程崩溃:如果服务器进程在完成任务之前崩溃,则系统通常会正确恢复,因为客户端将在服务器恢复后启动重试请求。如果服务器崩溃完成任务但在发送RPC回复之前,有时会由于客户端重试而导致重复请求。

     

  • 客户端进程在收到响应之前崩溃:客户端重新启动。服务器丢弃响应数据。

 

一些分布式设计原则

鉴于我们到目前为止所涵盖的内容,我们可以定义一些每个分布式系统设计人员和软件工程师应该知道的基本设计原则 其中一些可能看起来很明显,但随着我们开始有一个好的起始列表,这将是有帮助的。

 


  • 正如Ken Arnold所说:“你必须设计分布式系统,并期望失败。” 避免假设系统中的任何组件处于特定状态。典型的错误方案是将进程发送到第二台机器上运行的进程。第一台机器上的进程接收一些数据并对其进行处理,然后将结果发送回第二台机器,假设它已准备好接收。在此期间,任何数量的事情都可能失败,并且发送过程必须预见到这些可能的失败。

     

  • 明确定义故障情景并确定每种情况发生的可能性。确保您的代码完全覆盖最可能的代码。

     

  • 客户端和服务器都必须能够处理无响应的发送者/接收者。

     

  • 仔细考虑您通过网络发送的数据量。尽可能减少流量。

     

  • 延迟是启动数据请求和实际数据传输开始之间的时间。最小化延迟有时可归结为是否应该进行许多小调用/数据传输或一次大调用/数据传输。做出这个决定的方法是试验。做小测试以确定最佳折衷方案。

     

  • 不要假设通过网络发送的数据(或者甚至从磁盘发送到机架中的磁盘)在到达时是相同的数据。如果必须确定,请对数据执行校验和或有效性检查,以验证数据是否未更改。

     

  • 缓存和复制策略是用于处理跨组件的状态的方法。我们尝试最小化分布式系统中的有状态组件,但它具有挑战性。状态是代表另一个地方的流程在一个地方持有的东西,是任何其他组件都无法重建的东西。如果它可以重建它是一个缓存。缓存有助于降低跨组件维护状态的风险。但是缓存的数据可能会变得陈旧,因此在使用缓存数据之前可能需要有一个策略来验证缓存的数据项。

    如果进程存储了无法重建的信息,则会出现问题。一个可能的问题是,“你现在是一个单点故障吗?” 我现在必须和谈谈- 我不能跟别人说话。那么如果你下来会发生什么?要解决此问题,您可以复制。复制策略在减轻维护状态的风险方面也很有用。但是这里也存在挑战:如果我与一个复制者交谈并修改一些数据,那么我会与另一个人交谈怎么办?这种修改是否保证已经到达另一个?如果网络被分区并且复制者无法相互通信会发生什么?有人可以继续吗?

    在决定维护状态的方式和位置以及何时使用缓存和复制方面存在一系列权衡。由于设置不同机制的开销,在这些场景中运行小测试更加困难。

     

  • 对速度和性能敏感。花些时间确定系统的哪些部分会对性能产生重大影响:瓶颈在哪里?为什么?设计您可以做的小测试来评估替代方案。简介和措施以了解更多信息。与您的同事讨论这些替代方案和结果,并决定最佳解决方案。

     

  • Ack很昂贵,并且尽可能在分布式系统中避免使用。

     

  • 重传是昂贵的。实验非常重要,因此您可以调整提示重传最佳的延迟。

 

演习

  1. 你见过Heisenbug吗?你是如何孤立和修复它的?

     

  2. 对于上面列出的不同故障类型,考虑一下程序员试图防范它们的难度。可以在程序中添加哪些处理来处理这些故障?

     

  3. 解释为什么8个谬误中的每一个都是谬误。

     

  4. 对比TCP和UDP。在什么情况下你会选择一个而不是另一个?

     

  5. 缓存和数据复制有什么区别?

     

  6. RPC实现中的存根是什么?

     

  7. 在分布式环境中需要防范的一些错误条件是什么,我们在本地编程环境中不需要担心?

     

  8. 为什么指针(引用)通常不作为参数传递给远程过程调用?

     

  9. 这是一个有趣的问题,称为部分连接,可以在分布式环境中发生。假设A和B是需要相互通信的系统。C是一位大师,也分别与A和B交谈。A和B之间的通信失败。C可以说A和B都是健康的。C告诉A向B发送内容并等待发生这种情况。C无法知道A不能与B通话,因此等待和等待等待。您可以在代码中添加哪些诊断来处理这种情况?

     

  10. 什么是领导者选举算法?如何在分布式系统中使用?

     

  11. 这就是拜占庭将军的问题:两位将军在山谷两侧的山丘上。他们每人都有1000名士兵。在山谷的树林里是一支1500人的敌军。如果每次一般攻击,他的军队就会失败。如果他们一起攻击,他们就会赢。他们希望派遣使者穿过山谷来协调何时进行攻击。然而,信使可能会迷失或陷入困境(或被*以传递不同的信息)。他们如何设计一种方案,以便他们以高概率攻击,或者根本不攻击?

     

 

参考

[1] Birman,Kenneth。可靠的分布式系统:技术,Web服务和应用程序。纽约:Springer-Verlag,2005年。

[2] 采访Ken Arnold

[3] 八大谬误

[4] 关于IP套件的*文章

[5] Gray,J。和Reuter,A。Transaction Processing:Concepts and Techniques。San Mateo,CA:Morgan Kaufmann,1993。

[6] Bohrbugs和Heisenbugs

引自:http://www.hpcs.cs.tsukuba.ac.jp/~tatebe/lecture/h23/dsys/dsd-tutorial.html