分布式系统:间接通信

间接通信

进程间通信和远程调用都是基于发送者和接收者之间的直接耦合,这导致系统在处理改变时显得有些死板。例如在一个简单的客户-服务器交互时,用具有相同功能的另一台服务器替代原来的服务器很困难,当服务器出现故障时客户必须显式地处理故障。使用间接通信可以避免这种直接耦合,间接通信被定义为在分布式系统中实体通过中介者进行通信,没有发送者和接收者(们)之间的直接耦合。此时接收者可以是多个,这表明很多间接通信范型支持一对多的通信。间接通信具有以下两个主要特征:

间接通信的特征 说明
空间解耦 发送者不知道也不需要知道接收者的身份,反之亦然,参与者(发送者或者接收者)可以被替换、更新、复制或迁移
时间解耦 发送者和接收者(们)可以有独立的生命周期,它们不需要同时存在才能通信

在易变的环境下,发送者和接收者可以随时进入和离开,因此间接通信常常用于预期会发生改变的分布式系统中,例如移动环境。间接通信的主要缺点是由于增加间接层带来的性能开销,以及使用间接通信开发的系统更加难以精准地管理。间接通信与异步通信的的区分在于时间解耦,在异步通信中发送者发送一个消息,然后继续工作(不阻塞),因此不需要与接收者在同一时间通信。时间解耦增加了额外的维度,发送者和接收者(们)可以相互独立存在。

组通信

组通信(group communication)服务中消息首先被发送到组内,然后该消息被传送到组中的所有成员,此时发送者不清楚接收者们的身份。组通信是对组播通信的抽象,可以通过 IP 组播或一个等价的覆盖网络实现,它增加了一些重要的特性:管理组的成员、检测故障、提供可靠性和排序保证。组通信对于可靠的分布式系统是一个重要的构造块,其主要的应用领域包括:

  1. 面向可能是大量客户的可靠信息分发,例如金融业;
  2. 支持协作应用,应用中事件被分发到多个用户,从而保留一个共同的用户视图;
  3. 支持一系列容错策略,包括复制数据的一致更新,或者高可用性(复制)服务器的实现;
  4. 支持系统监控和管理,包括负载平衡策略。

编程模型

组通信的核心概念是组和组成员,进程可以加入或离开组,它可以发送一个消息到组中,然后消息就会被被传播到组中的所有成员。因此组通信实现了组播通信,它的特征是一个进程事项只发起一个组播操作,而不是发起多个发送操作到每个进程。使用一个组播操作而不是多次发送操作使得其实现能有效利用带宽,相比于分开的串行传送,可以最小化传递消息到目的地的总时间。

大多数组服务工作关注进程组(process group)概念,即通信的实体是这个组中的进程,是最为常见的实现方式。这种服务是相对低级的,消息被传递到进程时并没有进一步提供对分发的支持;同时消息通常是非结构化的字节数组,不支持对复杂数据类型的编码。相反,对象组(object group)提供更高级的组计算方法,一个对象组是一组对象的集合(形式上是同一个类的实例),这些对象并发地处理同一组调用,然后各自返回其响应。客户对象不需要得到拷贝,它们调用一个充当组的代理的本地对象上的操作,使用组通信系统向对象组的成员发送调用。
还有一些已经被开发的组通信服务,它们因各自的假设不同而不同。在封闭和开放组实现中,一个组只有组成员能组播给它的组被称为封闭组,组外的进程可以发送消息给它的组被称为开放组。封闭组可以用于协作服务器相互发送消息的过程,开放组可以用于传递事件到感兴趣的进程组。在重叠和非重叠组中,重叠组的实体可能成为多个组的成员,非重叠组意味着成员不会重叠,任一进程属于至多一个组。同时在同步和异步系统中,也需要在这两种环境中考虑如何实现组通信。

组通信实现

可靠性和排序

在组通信中,所有组成员必须收到发送给本组的消息的拷贝,并且一般具有传递保证,包括组中每个进程收到的消息和到达的顺序。对于组通信的可靠传输可以使用以下 3 个性质来定义:

可靠性 说明
完整性 接收到的和发送的消息一直,没有消息被传递两次
有效性 任何要外发的消息最终都会被传递
协定(agreement) 如果消息被传递到一个进程,则该消息将被传递到本组的所有进程

除了可靠性保证,组通信要求对传递到多个目的地的消息提供消息相对排序方面的额外保障。有序是不能由底层进程间通信原语来保证的,为了解决这个问题,组通信服务提供了有序组播(ordered multicast),它的实现方式有:

有序组播 说明
FIFO 序 先进先出,如果一个消息在另一个消息之前发送,则将以这个顺序传递到组中的所有进程
因果序 考虑了消息之间的因果关系,如果在分布式系统中一个消息在另一个消息之前发生,那么传递相关消息到所有进程时这种所谓的因果关系将被保留
全序 如果在一个进程中,一个消息在另一个之前被传递,则相同的顺序将在所有进程上被维持

组成员管理

开放组的实体可能会加入、离开或者出故障,因此组成员管理对于维护一个准确的当前成员视图非常重要。具体来说,组成员服务有四个主要任务:

组成员服务 说明
提供组成员改变的接口 提供创建和删除进程组、在组中增加或者删除进程的操作
故障检测 使用故障检测器对组成员做出决策,当怀疑其已经出故障或变得不可达时,从成员中去除该进程
组成员改变时通知成员 当增加或去除进程时(故障或者是进程有意退出组),服务通知组成员
执行组地址扩展 当进程组播一个消息时提供组标识,成员管理服务将该标识扩展为要传递的当前组成员

IP 组播是一个较弱的组成员服务方面的例子,它有一些不完全的组成员服务的特性。它允许进程动态地加人和离开组并执行地址扩展,发送者只需提供一个 IP 组播地址作为目的地,但是 IP 组播本身不向组成员提供当前成员的信息,组播传递不会随成员改变而调整。实现这些特性是很复杂的,需要视图同步组通信。

发布-订阅系统

发布-订阅系统(publish-subscribe systems)有时也称为基于事件的分布式系统(distributed event-based system),是应用最为广泛的间接通信技术。在发布一订阅系统中,发布者(publisher)发布结构化的事件到事件服务,订阅者(subscriber)通过订阅(subscription)表达对特定事件感兴趣,其中订阅可以是结构化事件之上的任意模式。发布-订阅系统的任务是把订阅与发布的事件进行匹配,保证事件通知(event notification)的正确传递。一个给定的事件将被传递到许多潜在的订阅者,因此发布-订阅本质上是一对多的通信范型。发布-订阅系统被使用在很多应用领域中,尤其是与大规模的事件分发相关的领域:金融信息系统、实时数据输入、协同工作、支持无处不在计算、监控应用。

发布-订阅系统有两个主要特征,另外可以为通知提供各种不同的传递保证,具体选择将依赖于应用的需求。

发布-订阅系统的特征 说明
异构性 当事件通知被用作一种通信手段时,分布式系统中没有被设计实现互操作的组件可以在一起工作。需要的只是生成事件的对象发布它们提供的事件的类型,其他对象订阅事件模式,并提供一个用于接收和处理结果通知的接口
异步性 通知是由生成事件的发布者异步地发送到所有对其感兴趣的订阅者的,防止发布者需要与订阅者同步,即发布者和订阅者需要解耦

编程模型

发布-订阅系统的编程模型是基于一个小的操作集,一些系统通过引入广告的概念补充上面的操作集。广告是根据感兴趣的事件类型定义的,订阅者用订阅声明他们的兴趣,发布者通过广告声明它们将要生成的事件类型。

发布-订阅系统操作集 操作方 说明
publish(e) 发布者 分发事件 e
subscribe(f) 订阅者 订阅某事件集,其中 f 是一个过滤器,它是定义在所有可能发生的事件集上的一个模式
unsub-scribe(f) 订阅者 取消对事件的兴趣
notify(e) 发布-订阅系统 将事件传递给订阅者
advertise(f) 发布者 在广告机制中,通过该操作声明未来将要发生的事件的特性
unadvertise(f) 发布者 在广告机制中,通过该操作取消某类事件

发布-订阅系统的表达能力由订阅(过滤器)模型决定,下面是一些已定义的模式:

过滤器模式 说明
基于渠道 发布者发布事件到命名的渠道,订阅者订阅其中一个已命名的渠道,这是一个定义了物理渠道的模式
基于主题 假设每个通知用一定数量的域来表达,其中的一个域表示主题,订阅是根据感兴趣的主题来定义的
基于内容 该方法是基于主题方法的一般化,它允许订阅表达式具有一个事件通知上的多个域,基于内容的过滤器是用事件属性值的约束组合定义的查询
基于类型 该方法基于对象的方法有内在关联,对象有一个指定的类型,订阅根据事件类型来定义,匹配根据给定的过滤器的类型或者子类型来定义

除了这些经典的分类,一些商业系统基于直接订阅感兴趣的对象实现,和基于类型的方法的不同在于关注感兴趣的对象状态的改变,而不是与对象类型关联的谓词。它们允许一个对象对另一个对象发生的改变做出反应,事件的通知是异步的,由通知的接收者决定。每当状态改变时,负责展示当前状态视图的对象会得到通知。

系统实现

发布-订阅系统的任务是保证所有事件被有效地传递到有过滤器与事件匹配的所有订阅者,在此之上可以有安全性、可伸缩性、故障处理、并发和服务质量等额外需求,使得发布-订阅系统的实现相当复杂。最简单的方法是使用单结点服务器的方式进行集中式实现,在该结点上的服务器作为事件代理。发布者发布事件到该代理,订阅者发送订阅到代理并接收返回的通知,可以通过消息传递或远程调用来实现。集中式的方法易于实现,但是设计缺少弹性和可伸缩性,存在单点故障和性能瓶颈的问题。

在分布式实现的方式中,集中式代理被代理网络所取代,这类方法能在结点故障中正常运行。基于渠道或基于主题的模式的实现也相对简单,一个分布式实现可以通过将渠道或主题映射到相关的组,然后使用底层组播通信设施传递事件到感兴趣的各方。基于内容的分布式实现方法较为复杂,下图描述了这种方法体系结构选择的范围。发布-订阅系统在底层使用一系列进程间通信服务或者更专门的服务,体系结构的核心是由网络覆盖基础设施支持的事件路由层提供。事件路由层的任务是保证事件通知可以高效地路由到合适的订阅者,由覆盖基础设施通过建立适当的代理网络或 P2P 结构来支持。对于基于内容的方法,该路由问题被称作基于内容的路由,目的是利用内容信息将事件有效地路由到指定的目的地。在体系结构的顶层保证事件与一个给定的订阅进行匹配,虽然匹配可以作为一个独立的层实施,但匹配常常被推到事件路由机制中。

在这个体系结构内有很多实现方法:

实现方法 说明
泛洪 这是最简单的方法,也就是向网络中的所有结点发送事件通知,在订阅者端执行适当的匹配。可以利用底层广播或者组播设施来实现,每个代理将到达的事件转发给所有邻居。这种方法的优点是简单,但是会导致很多不必要的网络流量。
过滤 在代理网络中采用过滤,代理通过一个有路径到达有效订阅者的网络转发通知。它的实现是通过先向潜在的发布者传播订阅信息,然后在每个代理上存储相关状态。当一个代理收到来自给定结点的发布请求时,它把事件通知传送到所有连接的有相应匹配订阅的结点,并决定在代理的网络中将事件传播到何处。
广告 订阅本质上采用了泛洪的方法向所有可能得发布者推送,通过与订阅传播类似的(事实上是对称的)方式向订阅者传播广告,可以减少流量负担。
汇聚 汇聚方法找那个将所有可能的事件集合看做一个事件空间,并且将其责任划分到网络中的代理集合上,汇聚结点是负责一个给定的事件空间的子集的代理结点。

其中在过滤方法的每个结点必须维护邻居表、订阅表和路由表,在代理网络中的每个结点上实现匹配,返回通知与订阅成功匹配的结点集。代理还需要处理到达的订阅事件,如果订阅事件来自于一个直接相连的订阅者,那么订阅必须加入到订阅表中。否则代理是一个中介结点,该结点知道通往这个订阅的路径存在,因此向路由表中添加相应的条目。在这两种情况下,订阅事件均被传递给除了源结点以外的所有邻居结点。

过滤方法的存储信息 说明
邻居表 包含该结点在代理网络中所有相连接的邻居
订阅表 包含由该结点为之服务的所有直接连接的订阅者
路由表 维护该路径上的邻居和有效订阅的列表

在汇聚方法中算法必须定义下表中的两个函数,每个汇聚结点像过滤方法一样维护一个订阅列表,并将所有的成功匹配的事件转发到订阅结点集。如果考虑到可靠性,SN(s) 和 EN(e) 都可以返回多于一个结点。这种方法仅在如下情况下可行:对于一个给定的与 s 进行匹配的 e,EN(e) 和 SN(s) 交集必须非空。基于汇聚的路由的一种解释是将事件空间映射到分布式散列表(Distributed Hash Table,DHT),这是一种网络覆盖形式。DHT 将散列表分布到一个 P2P 网络的结点集合中,散列函数可以被用于将事件和订阅映射到相应的管理这些订阅的汇聚结点上。

汇聚方法函数 说明
SN(s) 以一个给定的订阅 s 为参数,返回负责订阅 s 的一个或者多个汇聚结点
EN(e) 当事件 e 被发布,该函数返回一个或多个汇聚结点,这些汇聚结点负责在系统中将 e 和订阅进行匹配

还有一些其他对等中间件方法来支持发布-订阅系统中的事件路由,一种专门的方法是采用闲聊协议作为支持事件路由的一种手段。基于闲聊的方法是一种常见的实现组播(包括可靠组播)的机制,主要是通过网络中的结点周期性地、以一定概率地与邻居结点交换事件,无需考虑网络结构。闲聊方法实际上是上述泛洪实现的一个替换策略,它可以考虑本地信息和内容来实现知情闲聊

消息队列

分布式消息队列是间接通信系统的一个重要的类别,它使用队列概念作为一种间接机制提供点对点的服务,从而实现时间和空间解耦性质。消息队列也称为面向消息的中间件,发送者将消息放置到队列中,此后由一个进程移走该消息。

编程模型

消息队列是在分布式系统中通过队列进行通信的一种方法,生产者进程发送消息到特定队列,消费者进程从该队列中接收消息。

通常消息队列支持如下三种接收方式,消息排队的策略通常是先进先出(FIFO),但大多数消息队列的实现也支持优先级概念,即高优先级的消息先被传递。消费者进程也能基于消息的优先级从队列中选择消息。

消息接收方式 说明
阻塞接收 保持阻塞直到有合适的消息可用为止
非阻塞接收(轮询) 检查队列的状态,返回可用消息或一个不可用的指示
通知操作 当在相关的队列中有一条消息可用时,会发出一个事件通知

一条消息通常由以下三个部分构成,消息大小可以按需配置。鉴于消息体不透明,通常通过定义在元数据上的谓词表示选择消息的规则。

消息的组成部分 说明
目的地 一个指定目的队列的唯一标识符
与消息相关的元数据 包括如消息的优先级、传递模式等字段
消息体 消通常是不透明的,且未被消息队列系统改变过,通常会进行序列化处理

消息队列系统的一个重要特性是消息是持久的,即在消息被消费之前消息队列会无限期存储消息,最后将消息提交到磁盘以实现可靠传递。发送的任何消息最终都会被收到(有效性),接收到的和发送的消息是相同的,没有消息被发送两次(完整性)。因此消息队列系统确保消息将会被传递,但是不能保证传递的时间。消息队列还能支持如下一些功能:

  • 大部分商用系统支持消息的发送或接收可以包含在一个事务内,目的是保证或者在事务中所有步骤都能完成;
  • 一些系统也支持消息转换,据此可以任意改造到达的消息,实现转换不同格式的消息来处理底层数据表示的异构性;
  • 一些消息队列实现也提供安全性的支持,例如 WebSphere MQ 使用安全套接字层提供对保密数据传输的支持。

系统实现

消息队列系统可以通过集中式或分布式进行实现,集中式实现由位于指定结点上的消息管理器管理一个或者多个消息队列,这种模式的优点是简单,但是这些管理器可能变成重量级组件,可能成为瓶颈或单点故障。因此对于消息队列的实现,使用分布式实现将更为理想。
WebSpherer MQ 是 IBM 开发的分布式的基于消息队列概念的中间件,其核心是在消息发送者和接收者之间提供了一个间接机制。WebSphere MQ 中的队列由队列管理器管理,允许应用通过消息队列接口(Message QueueInterface,MQI)访问队列。MQI 是允许应用程序执行一些操作的简单接口,例如与队列建立连接或者断连、从队列发送/接收消息。多个队列管理器以及访问一个队列管理器的客户应用如果在不同的机器上,就必须通过所谓的客户渠道(client channel)和队列管理器通信。客户渠道在代理上发出 MQI 命令,这些命令通过 RPC 被透明地传送到队列管理器去执行。在这种配置下,一个客户应用正在发送消息到远端队列管理器,接着多个服务使用到达的消息。

在实践中比较常见的是队列管理器被连接成一个联邦结构,为了实现这个目标,MQ 引人了消息渠道(message channel)的概念。消息渠道作为两个队列管理器之间的单向连接,用于从一个队列异步地转发消息到另一个队列。消息渠道通过两端的消息渠道代理(Message ChannelAgent, MCA)进行管理,两个代理负责建立和维护渠道。每个队列管理器包含路由表以及渠道,允许创建任意的拓扑结构,集线器和辐条拓扑是一种典型的拓扑结构。在集线器和辐条拓扑中,指定一个队列管理器作为集线器承载一系列服务。客户应用通过指定作为辐条的队列管理器来连接,辐条转发消息到集线器的消息队列以便由不同服务进行处理,它按照某种策略放置到网络中支持不同的客户。集线器放置在有足够资源处理网络流量的结点上,这一方法的关键是要能通过高带宽连接到一个本地辐条。这个体系结构的缺点是集线器将成为潜在的瓶颈和单点故障,WebSphere MQ 也支持其他设施包括队列管理器集群来克服这些问题,也能实现负载均衡。

共享内存

分布式共享内存

分布式共享内存(DSM)是一种用于给不共享物理内存的计算机共享数据的抽象,进程的读和更新看上去是在其地址空间中普通的内存来访问的。DSM 的底层在运行时系统透明地保证运行在不同的计算机上的进程可以观察到其他进程的更新,就好像进程在访问单个共享内存,但是事实上物理内存是分布式的。DSM 的要点是节省了写应用程序时对消息传递的考虑,适合用于并行应用、任何分布式应用或者一组能直接访问单个共享数据项的应用。一个典型的实现是 Apollo Domain 文件系统,该系统的不同工作站拥有的进程通过将文件同时映射到它们的地址空间来共享文件,由此可见分布式共享内存可以是持久的。

DSM 不太适合客户-服务器系统,因为客户一般通过请求对服务器的抽象资源进行访问。DSM 随着共享内存多处理器的发展而增加,在硬件体系结构层次,开发工作的目标是在实现快速内存访问的低延迟和高吞吐量时,最大化可支持的处理器数目。在分布式内存多处理器和现成的计算组件集群中,处理器没有共享内存但是通过一个非常高速的网络来连接。
消息传递与 DSM 通信机制具有以下一些不同:

服务 消息传递 DSM
编码 变量必须在一个进程中编码,被传递后在接收进程中解码成其他变量 相关进程直接共享变量,所以不需要编码
地址空间 通过拥有各自私有地址空间而保护彼此 进程可能因为诸如错误地变更数据而引起其他进程失效
同步 通过消息传递原语本身,使用锁服务器实现技术来同步 通过共享内存编程的常规组成成分,例如锁和信号量实现
持久化 进程必须在同一时刻执行消息传递 可以持久化,通过 DSM 通信的进程可能在非重叠的生命周期上执行
可见性 所有的远程数据访问是显式的 任何特定的读或者更新有可能涉及、也可能不涉及底层运行时支持提供的通信

元组空间

编程模型

元组空间是分布式计算的一种新形式,该方法中进程通过在元组空间放置元组间接地进行通信,其他进程可以从该元组空间读或者删除元组。元组没有地址,但是可以通过内容上的模式匹配进行访问。在元组空间编程模型中,元组由一个或多个带类型的数据域组成,元组类型的任何组合都可能存在于相同的元组空间中。

进程通过访问同一元组空间实现共享数据,主要包括 write、read、take 操作。为了使得进程能够同步其活动,read 和take 操作都会阻塞,直到在元组空间中找到一个相匹配的元组。

元祖空间操作 说明
write 在元组空间中加入一个元组,不影响元组空间中已存在的元组
read 返回一个元组的值进行读取,并不影响元组空间的内容
take 也返回一个元组进行提取,它会从元组空间中删除该元组

当从元组空间中读或者删除元组时,进程需要提供一个元组规约,元组空间返回符合该规约的任何元组。一个元组规约包括域的数量和所需的域值或者域类型。例如 take(<String,integer>) 可以提取 <"fred",1958> 或者 <"sid",1964>,take(<String,1958>) 只能提取到两者中的 <"fred",1958>。元组空间不允许直接访问空间中的元组,进程必须替换元组空间中的元组而不是修改,也就是元组是保持不变的。元组空间可以实现空间和时间的解耦:

解耦 说明
空间解耦 放置在元组空间中的元组可能源自任何数量的发送者进程,也可能会被传递到任何一个潜在的接收者
时间解耦 放置在元组空间中的元组会保留在元组空间中直到被删除,发送者和接收者不需要在时间上重叠

系统实现

许多元组空间的实现采用了集中式的方案,此时元组空间资源由一个服务器管理。集中式的优势是实现简单,但是它显然不能容错也不能伸缩,因此需要一些分布式解决方案。在状态机方法中假设元组空间的行为像状态机,维护状态或者改变状态以响应来自其他副本或者环境的事件。为了保证一致性,副本需要保证如下 3 个要求:

  • 必须从相同的状态开始;
  • 必须以相同的顺序执行事件;
  • 对每个事件必须做出确定的反应。

也有方法通过使用特定的元组空间操作语义来优化复制策略,此时元组空间的更新是在当前视图的上下文中执行,元组基于逻辑名称被划分到不同的元组集合。该系统由一系列在元组空间上执行计算的组件和一系列元组空间副本组成,一个给定的物理结点可以包含任何数量的工作者、副本或者两者都有。结点通过通信网络连接,网络也可能发生分区。在这种实现方法中,write、read、take 操作的定义如下:

元祖空间操作 说明
write 在不可靠的通信通道上向视图的所有成员发送一个组播消息,成员将元组放置到他们的副本中并确认收到。
read 发送一个组播消息在每个副本寻找一个匹配,并将匹配到的结果返回到请求站点。
take 元组规约被发送到所有的副本,副本试图获取在相关的元组集上的锁,如果不能获得锁就拒绝请求。接着元组必须从所有副本删除,通过重复发组播直到收到所有的删除被确认来实现。

纽约大学开发的 Linda 内核采用了将元组划分到可用的元组空间服务器上的方法,该方法中元组没有副本,每个元组只有一个拷贝。这样可以提高元组空间的性能,当一个元组放置到一个元组空间时,需要使用散列算法选择一个要使用的元组空间服务器。还有一些实现采用了对等方法,由所有结点合作提供元组空间服务,该方法有固有的可用性和可伸缩性。

参考资料

《分布式系统概念与设计》[英]George Coulouris, Jean Dollimore, Tim Kindberg,Gordon Blair,金蓓弘,马应龙 译,机械工业出版社

posted @ 2024-04-03 17:09  乌漆WhiteMoon  阅读(8)  评论(0编辑  收藏  举报