分布式系统

概述

    分布式系统是由自主计算机组成的,这些计算机一起工作,给人的外观是一个单一连贯的系统。一个重要的优点是,它们可以很容易地把运行在不同计算机上的不同应用程序集成到单个系统中。另一个优点是,如果设计恰当,分布式系统可以随底层网络的大小而扩展。这些优点往往带来的代价是更复杂的软件、性能的降低,而且往往削弱了系统的安全性。但是,创建和安装分布式系统还是有意义的。

    分布式系统的目标往往是隐藏掉有关分布式处理、数据和控制的复杂性。但是,这种分布透明性不仅导致较差的性价比,而且在实际情况中,是不可能完全实现的。事实是需要在实现分布式透明性与使系统复杂化之间进行平衡。

    事实远非这么简单,很多开发人员最初假设底层网络根本上是错误的。然后,当这些假设行不通时,可能会发现要隐藏掉那些不想要的行为是很困难的。一个常见的示例是,假设网络延时不是重要的因素。后来,当要把一个已有系统移植到广域网时,隐藏起来的系统延时可能会严重影响系统的初始设计。其他陷阱还有假设网络是可靠的,静态的,安全的以及同构的。

    已有的分布式系统根据所支持的计算方法、信息处理和普适性,可分为不同的类型。分布式计算系统通常用于高性能应用。这种应用起源于并行计算领域。分布式系统的一个大类可以在传统的办公环境找到。在这种环境中,数据库起着很重要的作用。通常,事务处理系统部署在这些环境中。最后是一种正在涌现的分布式系统类型,其中的组件比较小,系统以一种自主的方式组成,而且大多数不在是通过系统管理员来管理。最后一种类型的分布式系统通常表现为无所不在的计算环境。

体系结构

    分布式系统一个重要的目标是,通过提供一个中间件层,把应用程序与底层平台分开。采用这样一层是一个重要的体系结构决策,其目的是提供分布式透明性。通过使分布式系统监视自己的行为,当需要时采取适当的措施,就可以获得适应性。这就导致了自治系统(Autonomic Systems)的出现。这种分布式系统经常组成反馈控制循环的形式,形成了系统设计中一个重要的体系结构元素。

    分布式系统可以以多种方式来组织。我们可以区分软件体系结构和系统体系结构。后者可以把组成分布式系统的组件放在不同的机器上。前者则更关注软件的逻辑结构:组件如何交互,以什么方式构建组件,如何实现独立性等。

    关于体系结构的一个关键思想是体系结构的样式。一个样式反映了在组织构成分布式系统的软件组件之间的接口所遵循的基本原则。重要的样式有分层、面向对象、面向事件和面向数据空间。

    分布式系统的组织结构有多种。一个重要的类型是机器分成了客户和服务器。客户发送一个请求给服务器,然后服务器产生一个结果,该结果返回给客户。客户-服务器体系结构反映了模块化软件的传统方法,其中一个模块可以调用另一个模块中的函数。通过在不同机器上放置不同的组件,我们就可以获得多台机器上的函数的物理分布性。

    客户-服务器体系结构经常是高度集中化的。在非集中化的体系结构中,我们可以看到,组成分布式系统的进程起着相同的作用,又称为点对点系统。在点对点系统中,进程组织为一个覆盖网络,这是一个逻辑网络,其中的每个进程都有一个与之通信的其他对等体列表。在覆盖网络中,可以为进程间的消息路由部署确定方案。在非结构化网络中,对等列表多少有点是随意的,这意味着要部署查找算法来定位数据和其他进程。

    另外,还开发了自我管理分布式系统。一定程度上说,这些系统融合了来自系统和软件体系结构的思想。自我管理系统通常可以组织成反馈控制循环。这种循环含有一个监视组件,一个分析组件和多种工具集。反馈终止循环可以集成到分布式系统的很多地方。但如何开发和部署循环还需要更多的研究。

进程

    在分布式系统中,进程是一个基本的部分,它们构成了不同机器间通信的基础。一个重要的问题是,进程内部的组织结构究竟是什么样的,它们是否支持多个控制线程。分布式系统中的线程对于在执行阻塞性I/O操作时继续使用CPU是非常有用的。如果采取了多线程方式,就可以构建效率更高的服务器,让服务器并行地运行多个线程,其中某几个线程可以由于阻塞而处于等迭代状态,直到磁盘I/O或者网络通行完成为止。

    以客户端-服务器的方式来组织分布式应用程序已经证实是很有用的。客户进程一般实现用户接口,这种用户接口可以只提供简单的显示,也可以提供能够处理复合文档的高级接口。而且客户软件还通过隐藏于服务器通信的细节,包括服务器当前所在位置和服务器是否被复制等细节,来取得更好的分布透明性。另外客户端软件还可以部分地负责隐藏故障及恢复工作。

    服务器一般要比客户更加复杂,但是与设计相关的问题相对较少。比如说,服务器可以是迭代的也可以是并发的,可以实现一种服务也可以多种服务,可以是状态无关的也可以是状态有关的。其他设计方面的问题涉及寻址服务,以及在服务请求已经提出甚至已经在处理过程中的情况下的中断该服务的机制。

    把很多服务器组织成一个集群需要特别关注。一个通常的目标是对外部世界隐藏集群的内部细节。这意味着集群的组织应该和应用程序无关。为达此目的,大多数集群使用一个单访问点(entry point)把消息转发集群中的服务器。一个挑战性的问题是透明地把这个单访问点换成一个完全分布式的解决方案。

    在不同机器间进行代码迁移是分布式系统中的一个重要主题。支持代码迁移的理由是为了提高性能及灵活性。当通信的开销很大时,我们有时可以把计算工作由服务器移到客户端进行,让客户尽可能多地进行本地处理,以减少通信量。如果客户能够动态下载与特定服务器通信所需的软件,就可以提高灵活性。从服务器下载的软件是该服务器专用的,而不需要客户预装该软件。

    代码迁移会带来与本地资源使用相关的问题,因为迁移要求资源同时迁移,并且在目标机器上重新绑定到本地资源,或者使用系统范围的网络引用。另一个问题是,在迁移代码时要考虑到异构性。目前的实践证明,最好的解决方法是使用虚拟机来处理异构性。虚拟机可以采用向java中的进程虚拟机的形式,或使用虚拟机监视器来允许一组进程随同底下的操作系统一起迁移。

通信

强大灵活的进程间通信功能是任何分布式系统中必需的。在传统的网络应用中,通信常常是通过基于传输层提供的低层消息来传递原语的。中间件系统要解决的一个重要问题是:提供更高层次的抽象,与传输层接口所提供的支持相比,这能更加方便地表述进程间的通信。

    使用最为广泛的抽象方法之一就是远程过程调用(remote procedure call,RPC)。RPC的本地是一种通过过程来实现的服务,该过程的主体在服务器上执行。只向客户提供过程的特征,也就是该过程的名字以及参数,当客户调用该过程的时候,其客户端实现——称为客户存根(stub),将会负责将参数值封装进消息中,然后将消息发送个服务器。由服务器调用实际过程并在消息中返回结果,由客户存根从返回的消息中提取结果值,然后将结果值传递回调用它的应用程序。

    RPC提供了同步通信功能,通过这些功能,可以将客户阻塞,知道服务器送回应答为止。虽然这种机制的某些变体放松了模型中对同步的要求,但是实践证明,通用的高级面向消息模型会更加方便。

    在面向消息的模型中,存在的问题是,通信是否是持久的,以及通信是否是同步的。持久通信的本质是,提交进行传输的消息由通信系统来存储,直到系统将传输给接收方为止。换句话说,发送方和接收方都不必保持运行来等待消息的传输。而在瞬时通信中,不提供存储功能,因此发送消息时接收方必须等待接受该消息。

    在异步通信中,允许发送方在将消息提交并进行传输后立即继续运行——甚至可以在消息实际发送出去之后就继续运行。而在同步通信中,发送方会阻塞,至少要到消息得到接收之后才能释放。发送方还有可能要等到消息已经交付给接收方之后甚至等到接收方进行响应之后才能继续运行,最后一种情况就像RPC一样。

    面向消息的中间件模型一般提供持久异步通信,用在RPC不适用的场合。它们主要用来协助将高度分散的数据库集成进大规模信息系统中。其他的应用还包括电子邮件和工作流。

    流是一种完全不同的通信方式,其主要问题是两个连续的消息是否有时间上的联系。在连续数据流中,每个消息都规定了端到端的最大延迟时间。另外,发送的消息还要受到端到端最小延迟时间的约束。这种连续数据流的典型例子包括视频流和音频流。这种时间上的联系以及希望底层通信子系统提供的服务质量往往是难于说明和实现的。使得事情变得复杂的因素之一是信号的延迟不稳定性。即使平均性能是可接受的,传输时的实际延迟时间的变化可能会导致性能恶化到不可接受的程度。

    最后,分布式系统中一个重要的通信协议是多播。其基本思想是从发送方到多个接收方进行信息传播。这里讨论了两种不同的方法。首先,通过创建从发送发到接收方的树,可以实现多播。如果明白了结点时如何组织为点对点系统的,解决方法也就很显然,以非集中的方式动态创建树。

    信息传播的另一个重要解决的方法是,使用感染协议。事实证明,这种协议很简单,仍然却很健壮。除了进行简单的传播消息,感染协议还可以有效地在大型分布式系统中进行信息的汇集。

命名系统

    名称可用来表示实体。基本上3种类型的名称。地址是与实体相关的访问点的名称,也可以简称为实体地址。标识符是另一种类型的名称。标识符有三个属性:每个实体都只有被一个标识符应用;一个标识符只能引用一个实体,不能再赋值给其他实体。最后,用户友好的名称的目标是由人来使用,因此由字符串表示。

    无层次命名系统需要把标识符解析为其相关实体的地址。实体的这种定位可以以不同的方法来完成。第1种方法是使用广播或多播。实体的标识符被广播给分布式系统的每个进程。为实体提供访问点的进程通过提供访问点的地址进行响应。很明显,这种方法的可扩展性有限。

    第二种方法是使用转发指针。每当一个实体转移到另一个位置时,它就会留下一个指针,说明它下一步所在的位置。定位实体需要遍历转发指针形成的路径。为了避免形成太长的指针链,定期缩短指针链是很重要的。

    第三种方法就是给实体指定一个宿主位置。每当实体转移到另一个地方时,它都会通知宿主位置。告诉宿主位置自己当前的位置。在定位实体时,首先询问宿主位置,以便了解实体的当前位置。

    第四种方法是把所有节点组织成一个结构化的点对点系统,同时考虑到各个实体的标识符,把结点赋给实体。随后,通过设计一个路由算法,把查询请求移到指定实体的节点,从而可能实现高效且健壮的名称解析方法。

    第五种方法是创建一颗分层搜索树。网络划分成不重叠的域。域可以组成更高层的域,以此类推。这里只有一个顶层域,它涵盖了整个网络。每个层次的每个域都有关联的目录结点。如果一个实体位于域D中,那么更高一层的域的目录节点将拥有一个指向D的指针。最低层目录节点存储了实体的地址。最高层的目录节点知道所有的实体。

    结构化名称可以很容易地组织成一个名称空间。名称空间可以用一个命名图来表示,在这个命名图中,一个结点表示的是一个具名实体,而边的标识则表示该名称位于哪个实体下。具有多条进入边的结点表示的是一个实体集,也可以是一个上下文结点或目录。大型的命名图往往组织成基于根结点的无环有向图。

    命名图可以以结构化的方式很方便地组织用户友好的名称。一个实体可以用一个路径名来引用。名称解析的过程是,通过查找路径名的各个组成部分(每次一个)来贯穿该命名图。大型的命名图是通过把结点分布在多个名称服务上来是实现的。当通过贯穿命名图来解析路径时,一旦某个结点到达实现了它的名称服务器,名称解析就在下一个名称服务器中继续进行。

    基于属性的命名方法的问题很多。在基于属性的命名系统中,实体是由(属性,值)对集来描述的。查询也是以这样的对来表示的,它基本上都要求对所有标识符进行穷举搜索。只有当这些标识符都存储在单个数据库中时,这种搜索才是可行的。人们还设计了其他的解决方法,把属性值对映射到基于DHT的系统,使得实体标识符集可以实现分布性。

    利用分布式搜索技术,基于属性的命名系统逐步取代了名称解析。在语义覆盖网络中就使用了这种方法。在语义覆盖网络中,结点维护一个有关其他结点的本地列表,这些结点在语义上具有类型的内容。通过首先查询进紧接着的领结点,只有在此没有找到后继者之后才部署(有限的)广播,这些语义列表就可以实现高效搜索。

同步化

    分布式系统中的进程同步问题与进程间通信有密切的关系。同步就是在适当的时刻做恰当的事情。分布式系统和计算机网络中的一个普片问题是没有全局共享时钟。换句话说,不同机器上的进程都有自己的时间。

    分布式系统中有很多同步时时钟的方法,但是所有方法本质上都是基于交换时钟值,同时考虑了发送和接受消息时所采用的时间。通信延迟的变化和对这些变化处理的方式很大程度决定了时钟同步算法的准确性。

    与这些同步化问题有关的是几何覆盖网络中定位节点。其基本思想是为每个结点赋给一个m维的坐标,这样,几何距离就可以用来准确地度量两个结点之间的延时。赋给坐标值的方法很想在GPS中用于确定位置和时间的方法。

    许多情况下是不需要知道绝对时间的。重要的是不用进程中的相关事件的正确顺序发生。Lamport算法说明,通过引入逻辑时钟的概念,可以使得进程集就事件的正确顺序达成全局一致。本质上,每个事件e,例如发送或者接收消息,都被分配一个全局唯一的逻辑时间戳C(e),这样,当事件a发生在事件b之前时,C(a)<C(b)。Lamport时间戳可以扩展成向量时间戳:如果C(a)<C(b),那么我们就知道事件a在因果关系上发生b之前。

    一个重要的同步算法是分布式互斥。这些算法确保在分布式进程集中,每次至多一个进程可以访问共享资源。如果我们使用一个协作者来跟踪应该轮到谁来访问时,分布式互斥就可以容易地实现。还完全的分布式算法,但是它们有缺点,它们更容易使通信和进程发生故障。

    进程间的同步化常常要求一个进程扮演协作者。在这些情况下,协作者不是固定的,需要分布式计算来决定谁将成为协作者。可通过选举算法来做这样的决定。选举算法主要用于协作者可能崩溃的情况。但是,它们还可以用于在点对点系统中选举超级点。

 

一致性和复制

    复制数据主要有两个原因:提高分布式系统的可靠性或提高性能。复制导致了一致性问题:每当一个副本被更新,该副本就变得与其他副本不同了。为了保持各副本一致,我们需要以一种让人注意不到的暂时的不一致的方式传播更新。但是,这样做可能会严重降低性能。在大型分布式系统中尤其如此。

    这一问题的唯一解决方法是放松一致性的要求。有多种不同的一致性模型。对持续一致性来说,其目标是设置副本之间的数值偏差、新旧偏差以及操作顺序偏差的界限。

    数值偏差指的是副本之间的数值差。这种类型的偏差与应用程序高度相关。例如,可以用在股票记录的副本中。新旧偏差指的是认为某个副本认为一致性的时间段,尽管在一段时间之前已经发生了更新。新旧偏经常用于Web缓冲。最后,顺序偏差指的是服务器未发送出的暂时写操作的最大数,此时该服务器无需与其他副本服务器同步。

    一致性的操作顺序是很多一致性模型的基础。有多种一致性模型,但只有一部分在应用程序开发人员中流形。顺序一致性主要提供的是编程人员在并行程序设计时所期望的语义;每个写操作都是以相同的顺序执行。另一种较少使用的是因果一致性,它反映的是相互依赖的操作按照其他的依赖关系执行。

    弱一致性模型考虑的是读操作和写操作的序列。特别是,它们假定每个序列根据同步变量(例如锁)伴随的操作被适当地"分类"。尽管这要求程序开发人员显式地说明,但是弱一致性模型一般比纯顺序一致性模型更容易以高效的方式实现。

    与这些以数据为中心的模型相对,移动用户的分布式数据库领域的研究人员已定义了一些列以客户为中心的一致性模型。这种模型不考虑数据可能被多个用户共享,而是关注单个客户应提供的一致性。其基本设想是,客户在一致性模型主要是确保每当客连接一个新副本时,该副本使用该客户先前操作的数据更新,而这些数据有可能存储在其他副本处。

    要传播更新,可使用不同的技术。不同的技术根据实际传播的信息,传播到什么地方以及谁启动传播加以区分。我们可以决定传播通知、操作或状态。同样,不是每个副本都需要立即被更新。什么时候更新哪个副本取决于分布式协议。最后,传播更新技术还可以选择更新是被推入到其他副本,还是一个副本从其他副本出拉入更新。

    一致性协议描述的是一致性模型的特定实现。按照顺序一致性及其各种变型,一致性协议可分为两种:基于主备份的协议和复制的写协议。在基于主备份的协议中,所有更新操作都被转发到一个主副本,然后该主副本确保更新被正确地排序和转发。在复制的写协议中,更新同时被转发到多个副本。在这种情况下,正确对操作进行排序变得更加困难。、

容错性

    容错是分布式系统设计中的一个重要主题。容错是系统的这样一种特性:系统可以掩饰故障的发生并从故障中恢复过来。换句话说,如果系统可以在发生故障时继续操作,那么它就具有容错性。

    存在集中类型的故障。如果只是简单地停止了进程,那么就发生崩溃性故障。如果进程不对到来的请求做出响应,那么就会发生了遗漏性故障。当进程对请求的响应过快或过慢时就发生定时性故障。如果以错误的方式响应到来的请求,那么就是响应性故障。最难处理的失败是系统出现的任意类型故障,这事称为任意性故障或拜占庭故障。

    冗余是获得容错性所需的关键技术。对于进程来说,进程组的概念很重要。一个进程组由很多进程组成,它们紧密协作来提供服务。在容错的进程组中,一个或多个进程的失效不会影响到该组实现的服务的可用性。通常,组内的通信必须高度可靠,而且为了获得容错性,应该具有严格的次序和原子属性。

    可靠的组通信也称为可靠多播,它具有多种不同的形式。只要组相对较小,那么实现可靠性就是可行的。但是,如果需要支持非常大的组,可靠多播的可扩展性就称为一个问题。获得可扩展性的关键问题在于减少接收方发送的反馈消息数量,这些消息用来报告多播消息(不)成功接收。

    如果需要提供原子性,那么事情就变得更复杂。在原子多播协议中,关键在于每个组成员都对多播消息应该被传送给哪个成员具有相同的视图。原子多播可以根据一种虚拟同步的执行模式进行准确的定义。这种模式引入了边界的概念,在边界之间组成员关系不发生变化,而且消息也可靠地进行传输。一条消息不会跨越边界。

    组成员关系改变时每个进程都需要在组成员表上达成一致的一个例子。可以使用提交协议来达成这样的协议,其中两阶段提交协议是使用最广泛的。在两阶段提交协议中,首先一个协作者检查是否所有的进程都同一执行执行相同的操作(也就是它们是否都同意提交),然后在第二阶段中多播表决结果,可以使用三阶段提交协议来处理协作者崩溃的情况,而不必在协作者恢复之前使所有的进程阻塞以达成协议。

    容错系统中的恢复可以通过有规律地对系统状态设置检查点来获得。检查点是完全分布式道德。不幸的是,设置检查点是一个开销很大的操作。为了提高性能,很多分布式系统在检查点中结合使用了消息日志。通过记录进程之间的通信,有可能在发生崩溃之后重放系统的执行过程。

安全性

    安全在分布式系统中扮演一个非常重要的角色。分布式系统应该提供一些机制来实施多种不同的安全策略。开发和正确应用这些机制一般使安全成为了一项困难的工程实践。

    三个重要问题可以区别开来。第一个问题是分布式系统应该提供建立进程间安全通信的功能。原则上,安全通道提供通信各方互相进行身份认证的方法,并保护消息在传输期间免受篡改。安全通道一般还提供机密性,使得除通信参与方之外,没有人能够读取该通道通过的消息。

    一个重要的设计问题是只使用对称加密系统(基于共享密钥),还是将其与公钥系统结合起来使用。当前的实践显示,为分配短期共享密钥进行的公钥加密的方法使用较多。短期共享密码要称为会话密钥。

    安全分布式系统的第二个问题是访问控制和身份认证。身份认证以这样一种方式处理资源保护问题,即仅具有适当访问权限的进程能够实际访问和使用那些资源。访问控制总在进程通过身份认证后发生。

    实现访问控制有两种方式。首先,每个资源能够保持一个访问控制表,正确列出每个用户或进程的访问权限;另一种方式是进程可以携带一个证书明确声明其对一个特定资源集合的访问权限。使用证书的主要好处时进程可以容易地将其票据传至另一个进程,也就是说,委派其访问权限。然而,证书具有通常难以吊销的缺点。

    在移动代码的情况中处理访问控制时需要特别注意。除了要能够保护移动代码免受恶意主机危害之外,一般来说更重要的是保护主机免受恶意移动代码侵害。人们已经提出了若干建议,其中沙箱是当前应用最广泛的一个。然而,沙箱有一定的限制性,而且基于真正的保护域的更灵活的方法也已设计出来。

    安全分布式系统中的第三个问题关系到管理。本质上有两个重要的子专题:密钥管理和身份认证管理。密钥管理包括加密密钥的分配,可靠的第三方发布道德证书对其扮演重要的角色。关于身份认证管理的重要点在于属性证书和委派。