分布式系统副本复制和一致性

来源:互联网 发布:js调用手机浏览器 编辑:程序博客网 时间:2024/05/02 08:21

多副本的存在是提升一个分布式系统可靠性、可用性、性能以及可扩展性的必要手段,有点像“狡兔三窟”,一个出口堵上了,还有其它的备选出口可供逃生。复制可以提高系统的可靠性显而易见,多个副本可以用于分流(如数据库的一主多从结构)也可以用于加快响应时间(如cdn),这使得复制具有增强系统可用性和扩展性的效用。实现数据复制,不仅会涉及到副本的管理(包括副本的存放位置、多副本之间内容的分发),还包括如何保持多个副本之间的一致性,而复制的一致性要求同复制的可用性、可扩展性要求之间是矛盾的,对一致性的要求越高,响应的速度就会越慢,在实践中需要根据具体的应用需求进行折中。

从整个系统的角度出发来实现系统级的一致性,称为系统级一致性;从单个用户的角度出发来实现单用户自身的一致性,称为用户一致性。根据一致性的强弱程度又可分为严格一致性、强一致性和弱一致性,典型的是放宽了数据传播时间要求和一致性强度的最终一致性。

一、系统级一致性 
顺序一致性SC (Sequential Consistency) 模型是一个最严格的也是程序员最乐于接受的存储器一致性模型,对于满足顺序一致性的多处理机中的任一执行,总可以找到同一程序在单机多进程环境下的一个执行与之对应,并且两者的结果一样。 
而弱一致性模型,其主要思想是通过硬件和程序员之间建立某种约定,由程序员来负担一些维护数据一致性的责任,从而放松硬件对访存事件发生次序的限制。具体做法是把同步操作和普通访存操作区分开来,程序员必须用硬件可识别的同步操作把对可写共享数据的访问保护起来,以保证多个处理器对可写共享单元的访问是互斥的。 
(1)严格一致性:一般是指按照所有读写操作实际发生的绝对时间执行操作,读操作返回的必须是最新的写操作写入的数据。这就要求全局时钟的存在,而在一个分布式系统中很难做到将所有进程的操作按照按绝对时钟进行排序,由此引发的恶性竞争、单点故障、消息排序等不仅极大的增加了系统实现的复杂性,还会降低系统的可用性和可靠性,使得这种方案得不偿失,不适合具有一定规模的系统。 
(2)顺序一致性:所有进程看到一个相同的总体读写操作顺序,且每个进程上的操作也依序出现在这个总体操作顺序中,属于强一致性。在共享存储多处理机中,若任一处理机都严格按照访存指令在进程中出现的次序执行访存指令,且在当前访存指令彻底完成之前不能开始执行下一条访存指令,则此共享存储系统是顺序一致性的。不得不介绍顺序一致性概念的最初来源,以便于理解其原本的含义,详见本博文章翻译:http://blog.csdn.net/csq_year/article/details/46744333。顺序一致性包含两层含义<1>进程必须按照程序指定的顺序执行程序,不能重排操作;<2>所有进程上的操作就好像以某种总体顺序执行且所有进程看到的总体顺序均相同,每个进程的操作在总体顺序中出现的顺序和它自身的相同。显然,顺序一致性并未从绝对时间的角度要求对操作进行排序,因此绝对时间上最后的写操作并不一定是进程能看到的最后一个写操作,对于所有进程的任何交叉执行顺序都是可以接受的。进程必须要看到(读)其它进程的写操作,但只能看到自身的读操作,看不到其它进程的读操作。在这里有一个暗含的理想化假定:即硬件的执行环境是顺序一致性的,而事实上现代计算机为了优化性能在硬件层并不是顺序一致性的,但提供了一些机制用于支持所有进程实现顺序一致性。如果硬件层不支持顺序一致性,那么要达到顺序一致性就必须要对所有的操作进行顺序化,可以按FIFO的方式处理请求,这就需要一个全局的FIFO队列(串行化是实现顺序化的一种最简单的方法);也可以把所有访问同一数据的请求进行顺序化,而访问不同数据的请求则可以乱序执行。 
(3)因果一致性:所有进程中有因果关系的读写操作必须是顺序一致性的,没有因果关系的操作可以乱序执行,这种一致性强度要弱于顺序一致性。但是在工程实践中确定所有操作间的因果关系并不是一件容易的事情。 
(4)同步化操作:顺序一致性和因果一致性是在读写操作层面上定义的,这些模型最初是为了共享内存的多处理器系统开发的,在硬件层面上真正实现的。这些一致性模型的粒度和应用程序提供的粒度在很多情况下并不匹配,共享数据的应用程序之间的并发往往是通过互斥和事务的同步化机制来控制的,这种机制一般是将读写共享数据的一系列操作封装成一组操作单元,并通过使用同步变量来互斥的执行这组操作单元中的操作。 
(5)最终一致性:当没有更新产生时,在一段时间后,所有进程最终都能看到存储对象的最新值。一个达到最终一致性的系统通常被称为是收敛的或者复制集收敛的;在到达收敛之前,系统可能返回之前的任何值。

二、用户级一致性 
用户级一致性定义中的“进程”可以认为是具体应用场合中的一个会话或客户等参与者。 
(1)单调读一致性(monotonic-read):如果一个进程读取数据项X的值,那么该进程后续任何读取X值的操作都将得到那个值或者更新的值。单调读一致性从时间的维度上保证了如果进程在t时刻看到了X的值,那么以后不会在看到较老版本的值。 
(2)单调写一致性(monotonic-write):一个进程对数据项X执行的写操作必须在该进程对数据项X执行的任何后续写操作之前完成。单调写一致性同以数据为中心的FIFO一致性类似,本质是同一进程上执行的写操作必须在任何地方以正确的顺序执行。单调写一执行保证在一个副本上执行数据更新时,在此之前(其它副本上执行的)的所有数据更新都将首先执行。 
(3)写后读一致性(read-your-write):一个进程对数据项X执行的写操作总是被该进程后续对X的读操作看见。一个写操作总是在同一进程执行的后续读操作之前完成,不管这个读操作是发生在哪个副本。 
(4)读后写一致性(write-following-read):同一个进程对数据项X执行的读操作后的写操作,保证发生在与读取的X值相同或更新的值上。

三、副本复制和内容分发 
(1)副本在哪里? 
这里包含两个层面的问题,一是副本服务器该放置在什么位置;二是副本的内容该如何组织存放在哪些服务器。有多种不同的方法可以度量副本服务器的最佳放置位置,比如到达一定区域内所有用户的平均响应时间最短的地理位置或者简单的按idc机房放置。而内容如何放置在副本服务器上不仅会涉及到数据的组织问题,还涉及到系统性能、可用性和容灾等问题。常见的是把源数据进行分片放置在不同idc下不同的机器上,并保证同一副本的同一分片数据不会出现在同一idc下的同一物理机上。 
(2)传播什么? 
分发的内容有三种形式,一是仅传送发生了更新的消息通知:可以使用无效化协议(invalidation protocol)使用传播通知这种方式通知其他拷贝已经发生了更新,可以指定数据存储的哪部分发生了更新,不至于使得副本中的全部数据均失效;由于它仅传送关于哪些数据不再有效的信息,因此对带宽的消耗很小,适用于读/更新(写)比率比较小的应用场景;二是传送更新了的实际数据:适用于读/更新(写)比率比较大的应用场景,这时更新的有效性比较大,被修改的数据很可能在下一次更新之前被读取,不传播所有相关的数据而将发生修改的数据记入日志,传送这些日志可以节约带宽;三是传送更新的操作而不是数据:不传送任何修改的数据,而是告诉其它副本该执行什么操作,也被称为主动复制(active replication ),这种情况一般只适用于那些更新是由易回放低耗时的操作引起的应用场景,当被更新的数据量比较大时比较有效,这样可以减少数据传送造成的带宽消耗和时延。 
(3)谁来发起传播? 
根据更新传播是由谁发起的,可以分为推式和拉式。一是推式的方法(push-based approach)也被成为基于服务器的协议(server-based protocol ),能够主动的将更新传送至其它副本而不必等待其它副本请求更新。适用于多个副本之间需要保持较强一致性的情形,这时副本上的读/更新(写)比率一般比较高,传递的更新有效性比较高,它使得一致的数据在请求时能尽可能快的有效。二是拉式的方法(pull-based approach)也被称为基于客户的协议(client-base protocol ),主动向其它服务器请求该服务器所持有的任何更新,通常被用户客户的高速缓存,适用于读/更新(写)比例比较低的应用场景。基于推式协议中的一个重要问题是服务器需要跟踪所有的客户高速缓存,状态较多的服务器不但通常缺少容错能力,而且跟踪客户的高速缓存在服务器端的代价可能相当大。由于基于推式和拉式的方法在网络通信、更新时延以及实现代价等方面各有利弊,导致出现了一种更新传播的混合方式-基于租用的更新传播。租用(lease)是服务器所做的承诺,承诺在给定的时间里把更新推送给用户。当租用到期时,客户被迫轮询服务器并在必要的时候拉取更新。另一种方法是在上一个租用到期时客户及时请求一个新的租用以实现更新的推入。租用为基于推式的策略和基于拉式策略之间的动态转换提供了一种灵活机制,持有更新数据的一方可以根据自身的负载情况调整租期的长短和发放的租用的数量,以便在服务性能和数据一致性之间取得合理的折中。 
(4)如何传播? 
传播方式可以分为单播和多播。在局域网环境下多播是可用的,对于推式协议,通过多播来推送更新的效率要明显高于通过单播推送数据。而对于拉式协议,发起方一般是单一的客户或服务器,采用单播可能是唯一可行的方法。

四、一致性的工程实现 
理论终将需要接受实践的洗礼才能落地开花结果,在工程实践中简单易理解的东西总是最受欢迎的,过于复杂的理论或设计即便很优雅,也难以广泛应用且难以证明其正确性。在一个分布式系统中,如果仅有一份共享数据而没有副本数据或本各种缓存数据的存在(即每次访问均直接访问共享数据),那么引起顺序一致性得不到满足的将是对共享数据的访问冲突;在这种情况下,一般通过使用同步变量保证互斥访问共享数据就可以达到顺序一致性。如果有副本或者各种缓存的存在,那么问题就会变得复杂起来,不仅要保证互斥的访问共享数据,还要保证各个副本之间满足某种一致性要求,而整个系统的一致性强度取决于所有环节中最弱的那个一致性强度。 
基本上使用最广泛的还是操作全局串行化的一致性模型,包括顺序一致性模型、使用同步变量的弱一致性以及原子性事务处理。根据是否存在一个数据的主拷贝(即所有的写操作都只能被转发到该拷贝),将顺序一致性协议分为以下两类。当不存在这种主副本时,写操作可以在任何副本上启动。 
(1)基于主备份的协议 
数据存储中的每个数据项x都有一个关联的主拷贝,该主拷贝负责协调x上的写操作。根据主拷贝是被固定在单一服务器上,还是可以切换至发起写操作的副本上可以区分各种主备份协议。 
<1> 远程写协议 
最简单的基于主备份协议的是所有读写操作都在单一服务器上执行,实际上此时根本没有数据被复制,但服务可以是分布式的,退化成简单的C/S模型了。比较常见的是允许进程在可用的副本上进行读操作但是必须将所有写操作转发到一个固定的主拷贝上,常称为主机备份协议(primary-backup protocol)。常见的一种阻塞式更新方式是对数据项x的写操作都转发到主拷贝上(或x的主拷贝上),主拷贝在本地先进行更新,然后再将更新传播至副本,每个副本再完成更新后向主服务器发送确认消息,待所有副本服务器完成更新后,主服务器向请求方发送确认消息,完成一次更新。一种非阻塞的更新方式是主服务器在主拷贝上完成更新后就向请求方返回更新成功的消息,然后再向副本服务器发送更新通知,而不必等待副本服务器完成更新。主机备份协议提供了一种实现顺序一致性的直接方法,由单一的主服务器对所有进程的写操作进行排序。无论读请求落到哪个副本上,所有进程都将以相同的顺序看到所有的写操作。在阻塞协议中,落在各个副本上的读请求均能看到最近执行的写操作的结果。而对于非阻塞协议则必须要采用一定的措施使得更新副本在规定的时间内完成。 
<2> 本地写协议 
第一种协议是每个数据项都只有一个单一的拷贝(即不存在副本)。每当一个进程要对数据项x进行更新时,首先要将数据项x传送到该进程,然后再执行相应操作。这个协议本质上是建立了一个完全分布式的、非复制的数据存储,其一致性是显而易见的,因为没有共享的存在。存在的问题是这种更新方式会不断的移动数据的位置,需要跟踪记录所有数据的位置。 
另一种变种协议是将主拷贝在多个执行写操作的进程之间来回迁移。每当更新一个数据项x时,先定位其主拷贝,然后将该主拷贝迁移到进程的本地位置,主要优点是多个相继的写操作都可以在本地执行,而执行读操作的进程仍然可以访问它们的本地拷贝(但是不能执行更新操作,否则会违背一致性)。这种变种协议可以用于以离线方式操作的移动计算机。离线前移动计算机成为它欲更新的数据的主拷贝,离线时所有操作都在本地执行,而其它进程则可以执行读操作,但不能执行写操作(以免造成数据不一致)。再次连线时,更新从主拷贝传播至所有副本服务器,使数据存储再次达到一致性。 
(2)基于复制的写协议 
在复制的写协议中,写操作可以在多个副本上执行,而不像主备份协议那样只能在一个主拷贝上执行。主动复制和基于多数表决的一致性协议的区别在于:主动复制中的所有操作被转发到所有副本。 
<1> 主动复制 
每个副本有一个相关联的进程,该进程执行更新操作;同时该操作被发送到每个副本,也可以使用前述讨论的方法来发送更新。主动复制时需要在不同的副本上以相同的顺序执行,因而需要一个全序的多播机制,可以使用中心协调器(定序器:sequencer)来实现全序,可以将所有操作转发到定序器,由定序器为每个操作分配一个唯一的序列号,接着将这些操作转发给所有副本,副本按照序列号顺序执行操作。显然,这种全序多播的实现与基于主备份的一致性协议类似。 
<2> 多数表决协议 
基本思想是要求进程在读或写一个复制的数据项之前向多个服务器提出请求,并获得它们的许可。在Gifford的方案中,一个进程要读取一个具有N个副本的数据项,必须获得R个以上任意服务器(读团体:read quorum)的许可;执行写操作之前也必须获得W个以上任意服务器(写团体:write quorum)的许可。R和W的值应该满足以下两个限制条件: 
<1> R+W>N; 
<2> W>N/2; 
第一个限制条件用于防止读写操作冲突;第二个限制条件用于防止写写操作冲突。只有在适当个数的服务器同意参与数据的读写后,进程才能读或写。一般来说,要为每个更新的数据项标记一个新的版本号,以便于进行数据新旧程度之间的比较。 
(3) 高速缓存一致性协议 
高速缓存形成了一类特殊的复制,因为它们通常是由客户而不是服务器控制。高速缓存一致性协议保证高速缓存与服务器启动的副本一致,与之前讨论的两种由服务器方控制的一致性协议在本质上没有多大区别,目的都是要保持数据一致性。不同的高速缓存解决方案在实际检测不一致性的时间上不同。静态解决方法假定编译器在运行前执行必要的分析,并确定哪些数据可能因为被缓存而导致了不一致,对此编译器仅插入一些避免不一致性的指令。动态解决方案是在运行时检测不一致性,比如可以检测服务器查看被缓存的数据自从被缓存后是否被修改过。 
当共享数据可以被缓存时,实现高速缓存一致性有两种方法:一是每当一个数据项被修改后,服务器向所有高速缓存发送无效化消息;二是仅传播更新。当使用只读高速缓存时更新操作只能由服务器执行,然后服务器根据某个分发协议确保更新被传播到各个高速缓存。很多系统使用基于拉式的方法,即如果客户检测到高速缓存已经过时,则向服务器请求更新。另一种情况是允许用户直接修改被缓存的数据,并将更新转发给服务器。直写式高速缓存(write-through)采用这种方法,分布式文件系统常使用这种方法。这种方法实际上类似于基于主备份的本地写协议,客户的高速缓存成为一个临时的主拷贝;为了保证顺序一致性,客户必须获得独占写的权限,否则可能导致写写操作冲突。直写式高速缓存将所有的操作在本地执行,理论上性能较其它方法更好。如果允许在通知服务器更新之前执行多个写操作来推迟传播更新,可以进一步提高性能。这种方式促使回写式高速缓存(write-back)出现了,这种方法也主要应用于分布式文件系统。 
(4) 用户级一致性协议 
在以用户为中心的一致性协议简化实现中,每个写操作W都被分配一个全局唯一的标识符,服务器把该标识符赋给提交写操作的用户。对于每个用户,跟踪两个写操作集:一是用户的读操作集即由用户所执行的读操作相关的写操作组成的,二是用户的写操作集即由用户执行的写操作的标识符组成。

阅读全文
0 0
原创粉丝点击