Posted by & filed under Uncategorized.

About yangwei

wei yang has written 16 post in this blog.

        共享内存系统是普通单机程序开发人员熟悉的开发范式,通过简单的使用读、写命令,就能确保将我们需要的值从内存中放入和读取出来,数据的一致性等问题,在单机系统中,开发人员根本不需要考虑,比如你不需要考虑当你进行了i=i+1后,再获取i的值时,i的值可能还没有来的及变化,因为这些都已经在读写原语的原子性中被考虑了,然而在分布式环境下,由于数据出现多个副本,且副本的数量有可能动态增加和减少,要实现同样的功能,又能保证读写性能,就需要新的算法和实现。本文介绍了分布式环境下实现共享内存模型会遇到的各种问题和挑战,针对不同问题,介绍多种算法,并比较其优劣性。最终选取两种实现进行实际测评。本文是对现阶段该领域研究现状的总体介绍,通过阅读该文,我们能够了解动态分布式共享内存研究的前沿状况,了解该领域中的挑战与机遇,是有志于分布式领域的架构人员,开发人员,研究人员的必读资料。

要点透视

 

  • 动态共享存储(DSM)系统支持通过读,写操作存取对象,同时需要在底层分布系统的各种不确定性干扰的条件下,保证数据一致性与可用性
  • 开发人员能够使用DSM来实现基于共享内存范式的分布式系统,并将他们的工作重点放在系统功能上,不用过分操心底层的信息设置,异步操作以及失败机制。
  • 动态共享存储系统实现了对象副本集合的透明化、运行时重配置,并支持多种副本模式的实现,包含从基于存储网络节点的实现,以及类似ad hoc网络中基于移动设备的实现。

读(Reading),写(’riting),算(‘rithmetic),所谓3R依然是大多数人类智力活动的基础,同时,3R也是现代计算科技的重要组成本分。实际上,无论图灵机还是冯诺依曼体系,都遵循读、写、算模型,所有投入实际使用的单处理器实现,都基于3R进行工作。随着网络科技的发展,通讯(communication)日益成为了重要系统活动。然而在高层次的抽象上,使用读、写、算模型进行思考仍然显然更为自然。

 

 

虽然很难想象分布式系统——比如基于万维网(World Wide Web)的应用——不需要通讯参与其中,我们还是常常把基于浏览器的应用程序简单想象成 获取数据(即读取数据),进行计算,存储(即写入)结果三个阶段。在本文中,我们主要讨论分布式系统中可共享读写的数据的存储,并聚焦于能够在动态环境下提供弹性及一致性的实现,所谓动态环境是指底层由电脑和网络组成的、容易受到各种因素干扰的分布式系统。

这些干扰包括:某些电脑退出工作,动态改变系统中参与计算的工作集合,通讯链路的失败和延迟。

 

共享内存服务是大多数信息时代系统的核心,我们这里所详细分析的共享内存系统支持两种存取操作:读(read)操作获取对象的当前值,写(write)操作替换对象的旧值为当前新值。操作中涉及的对象往往被叫做寄存器(registers)。虽然我们不打算包含更多复杂的对象语意,比如事务或者整合的读改写操作,但是任何分布式系统实现都需要接受下面谈到的挑战。

 

想象一个中心服务器类型的存储系统实现,服务器接收客户端请求并对数据进行处理,然后回复。虽然概念简单,但这种实现会产生两个主要的问题。第一个是中心服务器是性能瓶颈,第二个是服务器本身是单点故障。这种实现方式下,服务质量随着客户端的增加显著下降,如果服务器崩溃,服务将无法使用(想象一下网络新闻服务如果设计为中心服务器结构,其结果将难堪重任)

 

因此系统的重中之重在于保持可用。这就意味着他必须具备一定的容错性,比如,系统必须可以屏蔽掉某些服务器的错误或者通讯失败。系统必须支持大量并发访问,并不会导致剧烈的性能下降。唯一能保证可用性的方法只有冗余,也就是说使用多个服务器,并在服务器间制造对象内容的副本。另外数据复制必须从地理上分散开来,并位于不同的网络位置,才能让系统屏蔽某些数据服务器子集的失效。

 

保证数据长期有效也非常关键。一个存储系统可能容许一些服务器失效,但是长远来说,我们可以想象,所有服务器都有可能被更换,因为没有服务器是永远可靠,或者因为计划中升级。此外,在移动环境下,比如搜救或者军事行动中,很可能需要将数据从一个服务器集合迁移到另外一个,使得数据能够根据需要移动。无论我们关注的是数据的长期有效性还是移动能力,存储系统必须要提供数据的实时无缝迁移:没有谁可以让世界停止运转,以便等待他慢慢重新配置系统以应对系统失效或者环境变化。

 

由数据副本带来的主要问题是一致性问题,系统怎样才能从多个副本中找到最新值?这个问题在单服务器中心的实现中不会出现:一台唯一的服务器永远包含最新的值。在多数据副本的情况下,有可能需要询问每一个副本才能找到对象的最新值,但是这个办法代价太大,并且也做不到容错,因为必须要求每个节点都能被访问。无论如何,所以的实现对客户端都应该是透明的,客户端幻想着正在操作一个单副本对象,并且所有的存取操作都一个接一个按顺序执行,任何读取操作都能返回前一个写入操作的结果,并且该值和前一个读操作所获得的值至少一样新。总的来说,对象的行为,从外部看起来,必须和对象的抽象序列化数据类型一致,这样在开发使用这些对象的程序时才能放心的使用这些对象类型。一致性的概念通过原子性(atomicity28)或者等效的线性一致性(linearizability.25)来实现。

 

毋庸置疑,原子性是的最方便的一致性概念(是一致性中最强的概念),我们也注意到,基于效率的考量,一些相对较弱的一致性概念也被提出并实现。比如在多处理器内存系统领域,出现了几个不是那个直观的定义,他们的出现主要是基于一个有趣的理念:“没有人能找到解决一致性的方法,但是内存访问速度很快。”原子性提供最强的保障,也导致其实现花销高于较弱的一致性保障4 。Eric Brewer猜想没有分布式系统可以同时实现一致性,可用性,分区容忍性;这就是著名的“CAP猜想”,这个猜想现在已被证明成为了定理23 。因此在一些情况下,较弱的一致性模型需要被考虑8 。尽管如此,我们认为实现简单和直观的原子一致性仍然非常重要。

 

这种情况让人想起了编程语言发展的造期,汇编语言被认为更加优秀,因为他能产生更有效率的代码,或图形界面的早期,命令行界面被认为更优秀,因为他们消耗更少的资源。可想而知,在原子性领域的类似争论迟早也尘埃落定。当故障是间歇性并且存在时间较短的时候,我们可以像处理长时间网络延迟一样处理它,把恢复工作交给底层的网络模型。如果故障是永久性的,一致性仍然可以通过将服务限制在主要分区(partion)来实现。在最近的一次演讲中30 ,ACM的 2008图灵奖获得者Barbara Liskov说,虽然原子性实现代价确实较高,但如果我们不保证原子性,开发人员会非常头痛。

 

有趣的是已经有存储系统提供了原子化的 读-改-写 操作原语,这种存取原语的实现比我们涉及的读写原语要强大得多,不过实现代价也非常高,其中核心部分原子化的更新操作,有多种不同实现,有的将系统的一部分改为单一写模型(比如微软的Zure9),或者依靠始终时钟同步硬件(如Google Spanner13),或依赖如vector clocks这样的复杂机制来解决事件的顺序问题(Google’s Spanner13)。我们对读写原子性的阐述展示了分布式存储系统面临的一般性挑战。

分布式和一致性

这里,我们将描述一个综合的的分布式环境,该环境实现一致的共享内存访问服务。

分布式平台建模

我们将该系统建模为一些互相连接的电脑或节点,互相之间使用点对点消息进行通讯。每个节点都有一个唯一的标示符(比如IP地址)、本地存储并能进行本地计算。计算中,某个节点可能随时故障 ,节点故障意味着:节点不进行任何内部计算,不发送任何消息,任何发送给它的消息都不会被成功接收。

 

系统是异步的,节点不读取全球时钟,也没有同步机制。这意味着不同节点的处理速度是随机的,节点也不知道本地计算任务的时间上限。消息的延迟也是随机的,节点也不知道消息延迟的上限(尽管限制可能存在)。因此,算法不能假设知道全球时间或者延迟,因为不同节点的处理速度和延迟都不确定。

 

我们假设消息在传输中是有序的。消息不会自发的出错,重复或者生成。任意一条被收到的消息一定在这之前被发送过。消息不会无故丢失,但是消息的丢失可以被看做是长时间的延迟的后果(如何利用重发机制或者gossip协议建设一个可信的通信服务不是本文的讨论范围)

 

我们将分布式网络系统分成动态和静态的两类,分类标准如下。静态网络系统中,节点的集合是固定的,每个节点可能知道其他所有参与节点的信息。节点崩溃(或者主动退出)会导致节点被从系统中删除。动态为网络系统的节点数是不固定的,随着时间变化,由于节点崩溃,退出以及新节点加入,系统的参与节点有可能完全变化,而且这些变化可能随时发生。

 

我们先不假设动态环境中的故障程度,留待解决方案完整提出后再行讨论。无论动态与否,系统的一致性都需要被保留。计算过程产生的干扰可能对所讨论的内存服务的性能产生负向影响,不过内存的存取操作在某些假设被满足的情况下一定会终止。比如,当超过半数数据副本被激活,且网络延迟可控,静态内存服务将保证内存操作停止。动态内存服务不需要超过半数数据副本被激活,但是要求在特定时间内,多数数据副本处于激活状态(比如某法定数量(quorums))。如果这些假设不能被满足,内存服务需要依然保证操作的原子性,不过操作所需的时间会显著增加,或永远无法停止。

 

总的来说,我们认为所有参与工作的节点都是“好同志”,他们既有能力,又愿意参与工作,而且不会有意无意的捣乱—故意进行错误的计算。因此我们不用采取措施来处理恶意行为。如果你认为参与者有可能干坏事,我们建议对这方面感兴趣的读者阅读相关的文章,Rodrigues et al.,35  Martin and Alvisi.34

 

分布式共享内存和一致性。一个分布式共享内存服务利用网络节点和节点间通讯,模拟一段共享的内存空间,空间包含一系列可以读写的对象(常常被叫做寄存器(registers)),服务使用多个数据副本来保证数据的存在性和可用性。对客户端来说,服务是隐形的。对象的内容被复制到多台不同的服务器或者副本主机上,客户端发起读写操作。发起读操作的客户端被称作阅读者(readers),发起写操作的客户端被称作写入者(writers),一个客户端可能既是reader又是writer.

 

为了响应客户端请求,服务端发起一个包含与副本主机间通讯。这个通讯协议就是内存系统能够保证一致性的关键所在。原子一致性要求每个操作“缩水”为一个序列化点,这些序列化点的执行顺序必须和真实操作的顺序一致,最终的操作结果必须与序列化操作的结果一致。具体来讲,当一个读操作在一个写操作完成后被调用的话,读操作的返回值必须是那上述写操作写入的值,或者是上述写操作之后,读操作之前其他写操作的值。另外,如果读操作在另外一个读操作之后被调用,其返回值必须与前一个读相同,或者是一个比刚才的值更”新”的值。(在ACM 数字图书馆的附录里有更正式的论述)。由于这些自然属性,原子性是最简便和直观的一致性保证。
数据拷贝引起的最大问题就是一致性,我们怎么保证系统在不同拷贝间找到最新的值?

Lamport 28 引入了原子寄存器(registers)。Herlihy 和 Wing26 ,提出了一个等效的定义,线性化性(linearizability),这个也将原子性扩展到了任意的数据类型。由于原子性是关于一致性最强的概念,提供最有用的访问语意,所以我们聚焦于原子性的共享内存系统。

基础工具:静态化网络系统条件下的共享内存系统

为静态环境设计的算法仍然需要适应一些动态的行为,比如有限的异步性、暂时故障、永久崩溃等等。我们在这里先讨论静态环境下的共享内存服务有两个原因:首先,为讨论动态内存打下一个基础,同时聚焦某些实现原子性的方法,这些方法在动态服务中也会用到。第一个针对静态内存共享系统综合考察来自Chockler et al.12 。他的著作中提到的拷贝存取算法可以作为我们动态系统的基础材料,然而为静态系统设计的算法不能被直接用的动态系统,因为他们缺乏处理拷贝集合变化的能力。Attiya, Bar-Noy 和 Dolev,3 提出了处理共享内存的原始算法,这个算法被称作ABD算法,这项工作使得他们于2011年获得了Dijkstra 奖。

 

这个算法实现了原子内存,同时拷贝提升容错能力和可用性。给定的总拷贝数是n,系统能够容忍 f 个拷贝失效,n>2f,。我们的报告包含Lynch and Shvartsman32对原始算法做出的改进。
每个副本主机i 都保存寄存器(register)的本地值,valuei 以及一个附加到副本上的tag,tagi = 〈seq, pid〉,seq是一个序列号,pid是写入节点的唯一标识。Tags按照词典顺序进行比较排序。每一次新的写入都分配一个唯一的tag,发起节点的id被用来打破可能出现的无限循环。这些tags被用来确定写操作的顺序,也能同时决定相应读操作的返回顺序。

共享内存服务的客户端应该幻想自己在使用一个单拷贝对象[?],对此对象的所有存取都是序列化的.

读取(Read)和写入(Write)操作类似,每一个操作都由两个阶段组成,一个获取(Get)阶段,用于从多个拷贝中获取信息,一个放入(Put)阶段,将信息传播到所有可用拷贝中。每一个阶段的协议都保证超过半数的可用节点参与通讯交换:首先信息被发送到所有可用拷贝主机,接着所有可用拷贝主机的响应会被收集起来。前面提到过由于n>2f,所以一定存在超过半数的未故障节点。因此,每个阶段会在一轮通讯后停止,任意一个操作,会在两轮通讯后结束。这种实现方式的正确性,即原子性是这样被保证的:对于任意两个连续操作,至少一个正确的副本会在第一个操作的put阶段中被更新,又被第二个操作的get阶段所读取(译者:两次操作至少共享一个节点);这就保证了第二个操作总会“看见”最近的前置操作的结果。我们可以发现,在与不同主机集合通讯时,[?]需要要求任意两个集合之间有交集。如果等待超过半数的可用主机响应过于消耗资源和时间,可以设定一个法定数量系统(quorum system).32,38 (详细和完整的伪代码可以在ACM数字图书馆的附件中找到)

在动态网络环境中模拟共享内存系统

接下来,我们介绍几种能够在更加动态的系统中提供一致性共享内存的方法,更加动态的系统是指不仅节点可能崩溃或者自发的退出服务,而且新的节点可能随时加入服务。总的来说,对象拷贝集合的数量可能随着时间进行变化,最终迁移到完全不同的拷贝主机集合上。ABD算法不能应用在这个场景中,因为它建立在原有的拷贝主机集合一直可用的基础上。为了能够在动态环境中使用类似ABD方法,必须要提供方法对复制主机的集合进行管理,并保证阅读者(reader)和写入者(writer)能访问可用的集合。

 

有必要指出,处理动态环境以及管理节点集合并不直接解决内存服务一致性的问题。这些问题代表着动态分布计算领域面临的更加广泛的挑战。这也启发我们:实现共享内存的一致性有时可以使用分布式的基础工具,比如那些用来管理参与节点集合的工具,提供通讯原语的工具以及在动态分布式环境中获取共识(consensus)的工具。Aguilera et al.1提供的教程中涵盖了其中的若干主题。

 

我们首先从获取共识(consensus)的问题开始,因为它通过建立共同的操作顺序,为实现内存服务的原子性提供了自然的基础。也因为共识(consensus)也被用于原子化内存实现的其他方面。接着,我们我们将提出组通讯服务(GCS)解决方案:使用强通讯原语,比如完全排序广播,来对操作排序。最后,我们聚焦一些方法,这些方法通过用显式的拷贝主机集合管理,可以用于扩展ABD算法至动态环境。

 

共识

在分布式环境中如何协调并达成一致是计算机科学的基本问题。在分布式环境下达成一致的问题被称作共识问题。由于不同节点提供了多个参考值,一组进程需要对该值达成一致。

 

任何解决方案都必须保证下面几个属性:【译者,此处定义翻译参考pixos算法调研报告】一致性:至多有一个值被通过,即两个不同节点不能够通过两个不同的值;合法性:如果一个值被通过,那么这个值一定是由某个节点提出过的。即只有被提议过的值才会被通过;终结性:每个未发生故障的节点都会最终通过某个值,即一致性算法能够取得进展。共识在设计分布式系统时,是一个强大的工具,33,但是在异步系统中,共识问题的难度也臭名昭著,因为当一个进程出现问题时,终结性很难被保证19 ;所以共识必须小心使用(一个解决共识的方法是引入“错误探测器”,它能提供或者说限制关于节点的信息10。

 

共识算法能直接被应用于原子化的数据服务,我们只需要让参与者对全局所有的操作顺序取得一致.29 。无论使用哪种共识的实现,正确性(一致性)都能够被保证,但是了解下层系统的特征能够指导我们选择性能更好的实现(要知道各种实现的精妙之处,请参见e Lynch33)。无论如何,每个操作都使用共识是一个笨拙的实现方式,特别是一些干扰会延迟甚至阻止操作终止。因此使用共识时,需要尽量避免与某些的内存操作一同使用,并使得操作不影响共识的终结。

我们应该注意到,实现共识比实现原子操作更为复杂,特别是两个或者两个以上操作的共识问题不能通过原子读写寄存器来实现31。

 

组通讯服务

在分布式系统中最重要的基础材料就是组通讯服务(GCSs)5 ,GCS使得在不同节点上运行的操作共同以组的方式工作。操作通过GCS多播服务发送消息到所有组成员以实现分组协作。GCSs 负责保证消息传输的顺序和可靠性。

GCS的基础是组成员服务。每个进程,在每个时间都有一个唯一的组的视图(view),视图包含该组中的操作列表。视图可以随着时间变化,有可能在不同的进程中视图也不相同。GCS引入的另一个重要的概念是虚拟同步性 (virtual synchrony )【译者,参考wiki文章】,其核心要求是通过两个连续的视图同时处理的操作发送同样的消息集合。这样接收者就能根据消息、组成员,应用程序预设规则进行协同合作5。

 

GCS提供一个方法来实现动态网络的共享内存。其方法如下,例如,在GCS视图同步(view-synchronous GCS)18基础上实现一个全局的、完全排序的多播服务(发给每个视图的消息都附加了一个总顺序,每个参与者收到一个包含这个顺序的前缀)。有序组播是用来对存储器存取操作进行排序,以产生原子性的内存。这种解决方案的主要缺点是,对于大多数GCS实现,形成一个新的视图需要大量的时间,在此期间客户端的内存操作会被延迟(或终止)。新视图的形成通常需要几个回合的沟通,在典型的GCS实现中,即使只有一个节点故障,性能下降也非常明显。在内存服务中,最好能保证读写操作在重构(reconfiguration)时能正常进行,具备一定程度容错而又不产生性能下降是最理想的。

 

另一种方法是将GCS与ABD算法整合。例如,由De Prisco et al.14所描述的动态原始配置(dynamic primary configuration GCS ) , 在每个配置中使用技术Attiya3以实现原子内存。在这里,一个配置与一个Quorum系统组相结合。配置可以随时改变,由于系统的动态性质或因为需要一个不同的法定人(quorum)数系统,就像和其他以GCS为基础的解决方案一样,读取和写入在重构时被延迟。最后,任何新的配置与以前的配置有交集。这就影响重构的灵活性,可能需要过渡的配置的变化以达到最终所需目的。

 

Birman6提出了一个关于动态服务复制的通用方法Paxos29 ,这种重构模型结合了虚拟同步(virtual synchrony )与状态机副本,以解决共识问题。

 

dynastore算法

dynastore2是多writer/多reader的动态原子存储服务的实现。它集成了ABD算法,并允许副本主机集合重构,而且不需要共识的使用。

 

参加者从一个默认的本地配置开始,即一套副本主机。该算法支持三种操作:读,写,和重新配置。读和写操作分为两个阶段,如果不考虑重新配置,协议与ABD类似的:它使用超过半数的ABD的副本,每个副本保持对象的值和相关的标签。

 

参与者使用重配置操作(reconfig)来修改当前配置,(+,i)表示副本主机i加入,(–,J),表示主机j移除,reconfig分为两个阶段,阶段一使用一个分布式的弱快照服务发表由update原语引起的变化,阶段二通过scan原语获得由配置中的其他成员提交的变化。快照服务本身不是原子的,它不会为update操作进行全局排序,同时scan操作不能保证反映所有以前完成的update。然而,快照服务足够建立一套有向无环图(DAG),作为参与者的一种状态,存储于每个参与者中。图的顶点对应一种配置,图的边意味这对配置的修改【译者注:c1->(+,i)->c2-(-,j)->c3 c是配置,c1通过添加i节点成为c2,再通过一出j节点成为c3】。

 

Reconfig需要遍历这个DAG,DAG代表了配置的变更过程。每次遍历过程中,DAG都有可能被反复多次更新,因为不同主机的更新。假设超过半数的参与主机没被移除也没有崩溃,就能确保有某条通过DAG路径在所有主机之间都是相同的。有趣的是,尽管主机本身不了解这个共同的路径,但通过遍历所有的路径同样能确保这条共同的路径也被遍历。遍历过程终止于汇聚节点。为了确保重构过程能够终止,必须假设“存在有限数量的重新配置”。

 

现在我们回到reconfig的两个阶段。第一阶段的目标是类似于ABD的 获取(Get) 阶段:发现该对象的最新的值-标签对。第二阶段的目标是类似于ABD的Put阶段:传达最新的值-标签对到超过半数的主机。主要的区别是,这两个阶段运行的上下文分别变成了对配置进行增量修改,以及发现其他参与者提交的配置修改。这个过程最终将“引导”出可能的新配置。这个过程是这样实现的:通过遍历DAG的所有可能的路径-也就是配置-从而保证共同路径也被遍历,从而获得最终的新配置。

 

最后,我们再讨论下read-write操作。Read的实现和reconfig基本相同,不同的地方是:(a)配置更改的集合是空的,(b)所发现的值返回给客户端。Write也类似reconfig,差异是:(a)变更集是空的,(B)一个新的,更高的标签在第一个阶段的完成被产生,(c)新的标记值对在第二阶段传播。请注意,读写操作的配置更改的集合是空的,但两个操作都可以发现其他地方提交的变化,并帮助引导配置的修改。如reconfig操作一样,超过半数节点保持稳定工作保证协议能能顺利运行,有限的重构保证操作能正确终结。

 

值得重申的是Dyna Store的实现不包含关于重构的获得一致的协议。另一方面,向配置中增加和删除和单个节点,可能导致较大的开销,与之相比,直接用一个完整的配置替换原有配置性能可能更高。因此,read和write操作的延迟更加依赖重构的速度(我们以后将在讨论dynadisk时详细讨论)。最后,为了保证终止,dynastore假设的重新配置是有限的并最终会停止。

Rambo 架构

兰博是支持多读多写对象的动态存储服务24;Rambo是“基本对象的可重构原子内存”的缩写 【Reconfigurable Atomic Memory for Basic Objects】。该算法使用的配置由一组的副本主机加一个定义在这些主机上Quorum系统组成,Rambo通过替换配置来实现重构。值得注意的是,任何quorum配置可以在任何时间安装,不同的quroum配置不需要有非空交集。该算法保证了在所有执行的原子性。

 

没有重构时,算法类似于ABD 算法3(Lynch and Shvartsman32):每两个阶段都会与当前配置中一个完整的quorum进行交互。新的参与者通过与至少一个现有的参与者进行消息握手(不需要重新配置)。

任何参与者可能会在任何时间崩溃。为使服务的长期运行,quorum可以重新配置。重新配置与读写操作并行进行,并不会直接影响这些操作。此外,多个重构过程可能同时进行。重构涉及两个不相关的协议:用于引入和更新新的配置的组件叫做Recon,回收废弃的配置的组件叫做垃圾处理。

 

详细来说,每个参与者都保持一个配置地图,或CMAP,存储的配置序列,在节点i,cmapi(k)表示他的第k个配置。

 

Recon不断提出的新配置,旧的配置又被垃圾回收,这使得序列不断演变。不同参与者CMAP中的内容可能不同,但是Recon始终发出唯一的新配置K,该配置被存储在每个cmapi(K)。这是这样做到的,在c = cmapi(K–1)这个配置中的任何节点i可以在任何时间提出了一个新的配置。不同的配置建议通过执行c成员间的共识以达成和解(在这里,共识可以通过使用某个版本paxos来实现29)。

 

虽然取得共识的过程可能是缓慢的,在某些情况下,甚至有可能不会终止,但是这不延迟的读写操作(只需保证在操作过程中中至少一个quorum是完整的)。注意该结构的成员可能或可能不知道对象的最新值。升级协议负责垃圾收集旧的配置以及传播最新的对象信息。在这里,一个两阶阶段算法首先通知每个旧配置中的某个quorum有新的配置,然后获取对象的最新值并广播到新配置中的quorum里,并删除过时的配置。

 

参与者的cmap中可能拥有多个活动配置(尚未被垃圾收集的),如果配置发生的太快,或如果配置升级滞后。在这种情况下,读写操作的行为如下。第一阶段从活动配置中的quorums收集的信息;第二阶段向活动配置中的quorum广播信息。请注意,在每一个阶段,新的配置可能会被发现的。为了解决这个问题,每一阶段都是由一个涉及每个活动配置的定点条件而终止。

 

存储器存取操作从配置解耦使得读写操作一定会终止,即使Recon的过程可能很缓慢:由于其使用的共识。重构本身涉及到两个独立的活动:通过共识产生的一致的配置序列,然后升级过程最后完成重构。在某些情况下整合这两种活动,可能是有利于提高性能的,例如,RDS服务.11

 

最后,Rambo 可以视为改进和优化的基础框架,其中有的改进已经有原型系统实现,例如Georgiou et al.20,接下来我们描述了一个对兰博架构的改造:移动Ad Hoc网络。

Ad hoc网络

Ad hoc网络中不使用预先存在的基础设施;相反,移动的节点以及从源到目的地的路径通讯组成了整个网络。

GeoQuorums是一个能够在移动节点的活动随意性很大的物理平台上实现原子共享内存的方法。

GeoQuorums的可以被看作是一个两层的系统,上层实现了动态副本的存储系统,底层基于由移动节点组成的固定焦点(focal point)来实现对象复制。

所谓焦点(Focal point)指地理上的区域,在这个区域中移动节点(设备)常常产生聚集。焦点可能是一个路口,观景台,或沙漠中的绿洲。在焦点附近的移动节点共同组成一个固定的虚拟对象,称为焦点对象(focal point object)。每个focal point均支持本地广播服务,LBCAST,该协议能提供可靠的全序广播(totally ordered broadcast)。LBCAST用来实现一种复制状态机,这种机制能容忍的移动节点加入和离开。如果所有移动节点都离开焦点,焦点对象将失效。

 

其次,该方法在焦点上定义了一组quorum系统。每个quorum系统涉及两个集合,称为get-quorum和put-quorums,每个get-quorum都和put-quorum有交集。quorums使得焦点能容忍有限数量的故障节点。由于性能的原因,或由于移动节点周期性的迁移,可以使用不同的的quorum系统。

 

为了促进焦点对象(focal point objects)通信,GeoQuorums假定 GeoCast 服务(如在Imielinski26中一样) 能可靠的使消息传递到一个焦点(focal point)的地理区域。使用此设定,你可以使用兰博框架作为原子存储器系统的顶层,而是用focal point 作为副本主机。出于简单性和效率的考虑,GeoQuorums的方法需要做做额外的修改。第一是如何处理重新配置,第二如何影响读写操作的处理。

无论底层的分布式平台产生怎样的扰动,动态原子性存储服务都需要提供数据的一致性和可用性。

GeoQuorums第一次引入了一种不依赖共识的重新配置。该算法实现在有限数量的预定配置之间的重新配置.算法使用类似Rambo升级协议(update protocal)的两阶段协议取代了共识。在第一阶段,调用者联系所有配置中的完整的get-querum和put-quorum(注意,针对每个focal point最多只需要一次消息交互,即使配置的数量可能非常大)。接着,在第二阶段,信息被传送到下一个配置中任意一个完整的put-quorum。

 

GeoQuorums实现了一个改进的读写操作,允许一些操作只在一个阶段完成。对于写操作,这是通过全球定位系统(GPS)时钟为写入值产生的标签来实现的,这个标签可以将写入排序。这避免了在其它实现中确定最高的标签的阶段,而且写入协议执行只是一个单一的put阶段,该操作与当前的配置中的任意put-quorum进行交互。如果写操作检测到并行重配置操作,将会等待每个配置的响应(包括至多一次与每个focal point 的关联)。

 

一旦写入完成,标签就被确定。对于读取,该协议涉及一个或两个阶段。第一,获取(get),从一个完整的get-quorum获得标签最大的值;如果一个并行重构操作被监测到,这个阶段也会等待每个配置中的一个get-quorum进行响应(每个focal point最多一次消息交换)。一旦获得阶段完成,并确定了获得最大的标签,读(read)操作将终止。否则,读(read)操作使用放入(put)阶段将最大标签值发送到一些put-quorum。关于已确认标签上的信息会被系统广播以实现单阶段读操作。

 

使用分离的get-quorum和put-quorum使得我们能够根据系统读写操作的平衡来调优系统。当有更多的写操作时,系统可以重新配置以使用较大的get-quorum和较小的put-quorum,因为write只有使用put-quorums。反之亦然

 

GeoQuorums使用实时时间戳(由GPS提供)以加快读写操作,允许写和读在一个阶段内完成。最后,假设有一套固定的focal points限制了系统的可发展性,但允许算法在重构操作时不使用共识。

从理论到实践

(DSM)精确的一致性保证、对故障的容忍、在动态的环境中工作的能力促使研究人员构建了一些探索性的实现,比如我们已经提到过的,探索性的Rambo变种实现。在这里,我们介绍另外两个实现。第一个是一个从兰博算法思想派生的分布式的磁盘阵列36 。第二个是基于dynastore算法的实现37 。

 

联合砖块阵列(FAB)36 是由HP实验室开发和评估的存储系统。FAB主要是磁盘存储方案,存储的基本对象的逻辑块(logical blocks)。FAB的实现目标是从工作负载、故障处理、无干扰的请求恢复等各个方面超越传统的主从(Master-Slave)副本分配模式,FAB系统中使用的计算机被称为“砖块”(brick),通常配备普通商用的磁盘和网络接口。为实现分布,FAB将存储切为若干逻辑存储块,使用擦除码(erasure-coding)算法复制每个逻辑块到bricks子集。

该系统是基于多数仲裁(majority quorum【译者:超过半数的提议将成为最终提议】)制度,在确保服务寿命的同时,一个重构算法在砖块添加或删除时移动数据。客户端通过发送一个请求到砖块指定的逻辑块实现读写操作。砖块使用Rambo算法确定存储块被存储在那些砖块上,并进行读写操作,同时使用标记-值对实现对写操作的排序。写操作需要两个阶段完成,读操作需要一个或者两个阶段:当所有砖块返回的值标签都相同,只需一个阶段就能完成读操作。为了提高效率reader向已激活的空闲砖块请求存储块,只向quorum中的其他成员获取tag。

 

对FAB实现的评估结果表明:Fab性能类似于集中式的解决方案,同时能提供连续的服务和高可用性。

 

Dynadisk 37是用于评估的dynastore算法实现。测试在局域网环境下进行,采用以数据为中心的方法,在副本主机采用被动网络存储设备。

 

DynaDisk的设计支持在无共识的条件下重新配置服务,或采用共识进行部分同步。

 

评估结果表明,在无重构的情况下,两个版本算法有着类似的读写延迟,当多个重构同时发生时,异步的无共识方法的延迟有明显增加。这是由于无共识算法必须检查多种可能的配置,而整合了共识的重构算法通常只需要检查一个所有节点一致认可的配置。在无共识的DynaDisk版本中,重构延迟有少量下降,当多个重构操作同时发生时。在这种情况下,有共识的DynaDisk需要更长时间来获得共识。

讨论

我们提出了动态分布式系统中实现自动一致的存储服务的几种方法。在这样的系统中参与节点集合的变化可能由于节点故障、自愿退出或计划的节点更新。我们专注于原子一致性,因为这个概念足够直观和自然,它对内包装多个副本,对外提供了如同操作单一副本一样的串行操作。这样的服务更适合构建分布式应用程序,特别是考虑到使用共享内存的编程范式比使用消息传递范式更容易设计分布式算法。

 

本文中提到的方法只是众多分布式存储服务实现方法的代表。这些方法,每一个都有优点和缺点。例如,基于共识的解决方案,虽然概念上很简单,但通常在特定的阶段需要协调员的参与【译者:协调以获取共识】,其性能可能会严重依赖于可用的协调员。即使大多数主机无故障,协调员故障时,也会出现大量的延迟。组通信服务(GCS)是构建高可用性低延迟网络中的有效工具,但是当视图(view)发生变化时开销很高,即使故障的数目较小但持续时间较长。理论上更加优雅的方法,比如dynastore实现增量节点修改、Rambo实现quorum增量替换、GeoQuorums实现focal point,虽然实现更复杂,但具有更大的灵活性,并允许系统进行特定的优化。能否实现良好的性能,同时也取决于部署平台稳定,故障率的假设,以及一些其他因素,比如FAB中容忍性的考量。

 

不管如何对副本主机集合进行重构,何时进行重构也是必须解决的挑战。决定权可以被交个单个主机,例如,在任意一个节点加入或退出服务都要求进行重构。虽然在当扰动不多且副本集合较小时,这种方法简单有效,但当节点不断加入和离开服务时,这种方法可能会造成不必要的开销,即使一些核心的主机是稳定的足以提供良好的服务。另一种方法是把决策何时重新配置的权利交给另一个分布式服务,通过观察和推理性预判,找到重构的最佳时机。这是一个更复杂的解决方案,但它具有提供优质的服务的潜力。另外,还可以考虑选择一组合适的主机。系统并不需要同意每一个希望成为复制主机的节点的申请。在这里,决定何时重新配置的外部服务,同时也可以用来选择设置节点的目标。请注意,目标集的不要求共识,所有的存储服务都能够处理多个目标集同时被提交的的情况。

无论副本主机故障的幅度和频率,动态原子共享内存服务都需要保证所有执行的一致性。但是读写操作能否终止,就需参考故障的条件了。对于静态的系统,一般来说这个限制很容易描述:在这里,主机的任何少数子集都是是允许失败。动态系统的故障模式的约束更加复杂,取决于具体的算法的方法;读者可以参考引用的文章以了解详情。

 

随着云服务的出现,分布式存储服务将继续吸引人们的注意。技术挑战和性能方面的开销,造成了现有的分布式存储解决方案回避原子一致性保证。商业解决方案,如谷歌的文件系统(GFS22 ),亚马逊的Dynamo15 ,和facebook的Cassandra,28 提供直观性和保证性稍弱的服务。但是在本问中讨论的概念也得到了一些实际系统的回应。例如,共识技术在GFS22 中使用,以保证系统的正确配置,正如在Rambo中实现的一样;Spanner13 中的全球时间用,原理类似GeoQuorums;Dynamo15 的副本访问协议,采用了quorum,正如我们在本文中给出的一样。这些例子为我们追寻动态系统中的数据一致性算法提供了动力。

 

一致性存储系统一直是活跃的研究领域和开发领域,有充分的理由相信,一旦出现具有优越的容错性,同时又具备高性能的动态存储系统,将在构建复杂的分布式应用程序中发挥重要的作用。分布式应用程序对一致性和高性能的需求将不断催生对原子的读/写存储器需求。

Leave a Reply

  • (will not be published)