带着问题学习分布式系统之中心化复制集

  倘若我说有三个节点(计算机)要维护同一分数据,若是你对分布式系统并不了解,那么你可能会有什么问题呢,我想可能有两个最基本的问题:html

  1.   为何同一份数据要保存多分?
  2.   这些节点数据要一致吧,不然同时从多个节点读的时候数据不同?

  第一个问题,为何要同一分数据要保存多分,是由于分布式系统中的节点都有必定的几率发生故障,虽然单个节点的故障几率比较小,但当系统规模不断上升,故障的几率就变大了许多。节点的故障会对系统的可用性、可靠性产生影响。当数据在系统中只有一份存储时,若是发生断电、主机crash、网络故障,那么致使的是数据的暂时不可用;若是发生磁盘损坏,则会致使数据丢失,也就是系统不可靠。所以数据冗余(复制集、副本集),即同一分数据在系统中不一样节点保存多分,能够有效提升系统的可用性和数据的可靠性。固然,数据冗余也有一些缺点,好比占用额外的带宽和存储资源。算法

  第二个问题一下就命中了复制集的要害,也是分布式系统中对复杂的问题之一: 副本的一致性问题,即从系统外部读取系统内部各个副本间的数据在必定的约束条件下是一致的。在这篇文章中介绍过CAP理论,即对于分布式数据存储,最多只能同时知足一致性(C,Consistency)、可用性(A, Availability)、分区容错性(P,Partition Tolerance)中的二者。而分布式系统中网络分割必定是存在的,因此CAP -- “it is really  just A vs C!"mongodb

  同时必须注意的是,A和C都是一个度的问题,而不是0和1两个极端,所以能够在知足必定的可用性同时保证必定的一致性。不一样的需求决定了究竟是一致性重要仍是可用性更重要,好比在购物网站中,可用性很是重要,分分钟的不可访问都会带来巨大损失;而在《带着问题学习分布式系统之数据分片》中提到的元数据管理,一致性就很是重要,否则就乱套了。数据库

  为了解决第二个问题,人们提出了不少不一样的副本控制协议,即在分布式系统中,按照特定的流程规范来读写副本数据,使得副本知足必定的可用性和一致性的要求。缓存

  副本控制协议有不少,也有不一样的分类标准,好比:同步与异步、强一致性与弱一致性、中心化与去中心化。本文主要介绍中心化副本控制协议,在讲解不一样的系统实现时,也分提到在同步与异步、强一致性与弱一致性方面的选择。服务器

  本文地址:http://www.cnblogs.com/xybaby/p/7153755.html网络

中心化副本控制协议

  所谓的中心化,就是对于副本集的更新操做有一个中心节点来协调管理,将分布式的并发操做转化为单点的并发操做,从而保证副本集内各节点的一致性。其优势在于逻辑简单,将复杂的问题(分布式并发)转换成一个有成熟解决方案的问题(单点并发)。但缺点在于,副本集的可用性依赖于中心节点,若是中心节点故障,即便有中心节点自动切换机制,也会出现数10秒的不可用。并发

  

  大多数的分布式存储都会采用中心化副本控制协议,好比GFS,TFS,MongoDB负载均衡

 

  而去中心化则是说副本集中没有中心节点,全部节点的地位是平等的,你们均可以接受更新请求,相互经过协商达成数据的一致。去中心化副本控制协议的最大好处在于可用性比较强,只要有大多数节点存活就能提供服务。但缺点时协议流程复杂,尤为是须要强一致性保证的时候。异步

  在业界中,Dynamo,cassandra就是基于去中心化协议,虽然 Dynamo 尝试经过引入 Quorum 机制和 vector clock 机制解决读取数据的一致性问题,但其一致性模型依旧是一个较大的问题。

  

  本文主要对中心化副本控制协议进行详细介绍。

  中心化副本控制协议在分布式存储系统中使用很是规范,但各家实现又有不一样。这里主要集合分布式文件系统与分布式数据库来作对比分析

  回顾《带着问题学习分布式系统》一文,对于中心化副本控制协议,提出了如下疑问,本章节试图给出解答。

  (3.1)写节点怎么将变动的数据同步到其余节点,同步仍是异步;

  (3.2)非写节点可否提供读数据,若是可以容许,会不会读取到过期的数据。
  (3.3)主节点是怎么产生的,当主节点宕机的时候,怎么选择出新的主节点。是有统一的复制集管理中心(记录谁主谁次,各自的状态),仍是复制集本身选举出一个主节点?

主从节点数据更新流程

  第一个问题:复制集之间数据的同步是同步模式仍是异步模式。

  在中心化副本控制协议中,主节点(primary)提供写入操做,数据会同步到其余节点。注意,上面语句中第一个同步是指复制集中节点间数据趋于一致的过程。

  所谓同步(Synchronous replication),就是说对于客户端请求,系统阻塞到复制集中全部节点都更新完成,才能向客户端返回,即write all。而异步(Asynchronous replication)模式,只要一个或者部分节点更新则算写入操做成功,一般是write one。

    

  上图(来源于Distributed systems for fun and profit,下同)即为同步模式,客户端的请求被发送到s1这个副本集,s1将请求转发给s二、s3,等s二、s3都操做完成以后再向客户端返回结果。

  在同步模式下,系统的可靠性很是好,只要有一个节点正常,就能保证数据不丢失。可是系统的更新可用性很是差,只要有一个节点异常,就没法完成更新;并且,响应延迟比较大,取决于副本集中网络延时最大、处理速度最慢的节点。

    

  上图则是异步模式,客户端的写请求只要在一个节点上完成就当即向客户端返回结果。在上图中,数据被写入到s1即认为写入成功,向客户端返回,系统在后台由s1向s二、s3节点同步数据。

  异步模式下,系统的吞吐量会比较好,可是存在数据丢失的风险,好比上图中,若是在数据同步到s2 s3以前s1挂掉,那么刚才客户端的更新就丢失了,关键在于客户端认为已经写入成功了。另外,异步模式下,客户端在写入成功以后,马上从系统读取数据,有可能读不到最新的数据,好比上图中,客户端写入s1以后马上从s2 读取。

 

  在数据同步的时候选择同步模式仍是异步模式呢,这个取决于系统对一致性、可用性、响应延迟的要求。

  好比在分布式文件系统GFS中,须要保证复制集内副本的强一致性,而单次读写的响应延迟并无那么重要,所以选择了同步模式,即primary须要等到全部的secondary都写入成功才会向客户端返回。

  而在分布式数据库MongoDB中,决定权交给了用户,用户能够决定使用同步模式仍是异步模式。在《CAP理论与MongoDB一致性、可用性的一些思考》一文中详细介绍了writeconcern这个写入选项。若是w:1.那么只会写入到primary节点就当即返回,系统会在后台向secondary节点同步数据,即为异步模式。若是w:N(N为复制集节点数目),那么primary节点须要等到全部secondary都更新到最新的数据以后(或者等待超时)才向客户端返回,也就是同步模式。即便在去中心化副本控制协议,如cassandra,也提供给用户自行设定一致性等级。

  

  前面已经提到了同步模式、异步模式各自的优劣,这里以MongoDB为例具体讨论,看看同步、异步模式对系统一致性、可用性的影响。

  在异步模式(w: 1)下,系统的响应延迟很低,可用性很是好,但存在两个问题。第一:同一个客户端在获得成功写入的返回以后当即从secondary节点读取,有可能读不到最新的数据;第二:在主从切换的时候(后面会详细讲解这一过程),可能发生rollback,简单来讲,数据只持久化到了primary,secondary节点还未更新到最新的数据,此时若是primary故障,系统会选举出新的primary,即便旧的primary恢复正常后以secondary身份从新加入复制集,新的primary不会认可其数据,这就致使了更新丢失的问题。

  同步模式下(w:N或者w:Majority),须要等待全部节点都写入成功,响应延迟会比较高,在数据库应用中通常很难接受,以前基于《经过一步步建立sharded cluster来认识MongoDB》中的复制集(一个primary、一个secondary、一个arbiter)作过实验,若是将secondary shutdown(db.shutdownServer),而后用writeConcern: {w: "majority”, wtimeout: 10}写入数据,客户端会阻塞到超时,而后给出超时信息。不过,w:majority保证了写入的数据不会丢失,即不会出现rollback的问题。

  

  第二个问题:数据的流向

  即数据是如何从Primary节点到secondary节点的。

  首先是比较有意思的GFS,GFS写入流程以下:

  

  GFS的写入将控制流与数据流分开,客户端会把数据流链式推送到各个节点,推送的过程并不关心谁是primary、谁是secondary。须要注意的是,各节点(GFS中称之为chunkserver)只是缓存数据,等到Primary向secondary发送持久换指令(step5)的时候再回真正持久化写入。

  更通常的,数据的流向有两种,链式与主从模式。

  链式就是指从一个节点推送到最近的节点,好比GFS,“最近” 能够用IP地址或者节点间心跳TTL来衡量,如图所示(图片来源于清华阿里-大数据课程的PPT):

  

  不难看出,写入过程当中每一个节点的带宽利用都比较均衡,能够充分利用网络资源,也不会有单点压力;可是须要通过多个节点,写入延迟会比较大。

  而主从模式则是指数据同时从primary节点到secondary节点,如图所示(来源

  

  默认状况下MongoDB也是采用的链式模式,可是能够经过设置 settings.chainingAllowed = false 来采用主从模式。在主从模式下,Secondary会从Primary拉取OPLOG并应用到本地。显然,在这种模式下Primary节点的带宽压力比较大,可是写入延迟会小一些。

   

  第三个问题:部分节点写入失败了怎么办

  无论是同步模式仍是异步模式,也无论是链式推送仍是主从模式推送,复制集中数据的写入都是1PC(1 phase commit)。即数据的更新只有一个commit阶段,而没有prepare阶段,若是某些节点发生故障,那么提交在故障节点上会失败,甚至是提交了部分、不完整的数据。

  复制集中多个节点的更新本质上来讲应该是分布式事务问题,理论上应该保证原子行:要么都更新成功,要么都不更新,而不会出现部分节点更新成功的状况。但经典解决方案如两阶段提交代价太大,所以分布式存储中的复制集更新大多采用best effort 1pc,只不过不一样的系统对更新失败的处理有所区别。

  好比MongoDB,以第一个问题中提到的例子,writeconcern为w: majority,因为其中一个secondary挂掉,写入操做是不可能成功的。所以,在超时时间到达以后,会向客户端返回出错信息。可是在这个时候直接链接到rs的primary节点,数据是持久化到了primary节点,不会被回滚。

  另外,对于分布式图片存储haystack,若是更新失败,会重试流程,直到成功或超时,重试的话全部节点都会重试。那么可能出现两个问题,某个节点上数据部分写入(写入部分数据就崩溃了);因为重试是对复制集中全部节点重试,所以某个节点上同一份数据可能写入了多份。对于第一个问题,因为有checksum,所以不怕部分写入失败;第二个问题,因为有offset和元数据,重复写入也不是问题。haystack(以及GFS)经过这种巧妙的方式解决了分布式更新问题。

 

主从节点数据读取

  复制集中,不一样的系统在数据读取方面有两个问题。第一:secondary节点是否提供读服务;第二,若是能够从Secondary读取,那么这个接口是否开放给用户

  第一个问题,若是secondary节点提供数据读取服务,那么是否会读取到过时的数据(即不是最新成功写入的数据) ?好比在异步写入的时候,客户端获得成功写入的返回以后,当即去secondary上读取,那么有可能读到过期的数据,这对于强一致性的状况是不能容许的。咱们知道,元数据的管理通常也是复制集,而元数据须要保证强一致性,所以,元数据的写入通常都是同步的。好比GFS中,master由一个active(也就是primary节点)、多个standby(也就是secondary节点)组成,在元数据写入到active的时候,要保证本地和远程机器都写入成功才能返回;并且只有active提供读取服务。

  第二个问题,若是复制集中的节点都能提供读取服务,那么接口是否提供给最终用户呢?在haystack中,多个在不一样机器上的物理卷组成一个逻辑卷,一个逻辑卷就是一个复制集。当读取请求到达的时候,是由haystack的元数据服务器(directory )根据负载均衡的原则选出提供服务的物理卷,即用户是不知道读取请求是落地到哪一个物理节点的。而对于mongodb,用户能够在查询语句里面指定是从Primary读取,仍是从Secondary读取,或者让系统来选择(Nearest)。

  读取方式与用户角度的一致性很是相关,好比在MongoDB中,不一样的readrefence致使一致性、可用性的差别,具体可见《CAP理论与MongoDB一致性、可用性的一些思考

 

主节点选举

  在中心化副本控制协议中,这个中心(primary)是怎么选出来的呢?是上级指定仍是民主选举呢?

  GFS系统中,Primary节点是由master(GFS中的元数据服务器)经过lease机制选择的,关于Lease机制的,能够参见《带着问题学习分布式系统之数据分片》一文中相关章节的介绍。简单说来,GFS给某个节点颁发Lease,该节点就成为了Primary节点,Primary节点也能够在过时以前从新申请Lease,并且Lease的颁发、申请信息都是在chunkserver与master的心跳中,所以也不会带来过多额外的开销。使用Lease机制能很好的避免在复制集中出现双主(同时有两个节点认为本身是Primary)现象。

  而在Zookeeper、TFS、MongoDB中,都是经过去中心化的协议选举出Primary节点,选举出Primary节点以后,就变成了中心化的副本控制协议,当Primary出现故障以后,会从新选举过程。对于民主选举,两个因素很是重要:第一是强一致性,只能选举出一个Primary;第二个是可用性,选举过程要越快越好。

  为了达到强一致性,须要使用分布式一致性协议,目前较为常见的协议有Paxos协议,该协议能够实现全部备份都可以提供对外服务,而且保证强一致性,经过理论和实践检验能够达到分布式的要求。Raft协议则是Paxos的一种特化,在这个协议的实现中,备份间须要区分主从角色,只有主节点能够提供对外服务,协议实现简单高效,能很容易的同各类分布式数据一致性同步场景相结合,是工程实现最好的选择。

  在mongodb3.2中,Primary选举算法改为了raft-like算法,此举的目的也是为了缩短选举的时间,具体可见Replica Set Protocol Version

  在TFS中,meta server(元数据服务器)也是经过raft协议选举primary的,下面两个gif形象展现了primary初始选举以及当primary故障以后的从新选举(图片来源于清华阿里-大数据课程的PPT)。

  

  上图展现了Primary选举过程

  

  上图展现了在原来的Primary挂掉以后(也可能仅仅是失去响应),剩余的节点是如何选举出新的Primary。

  为了防止出现双主的状况,在投票过程当中至少要有超过半数的节点赞成才能选出Primary,这也是为何复制集中节点数目都是奇数个的缘由。

  

总结

  本文介绍了在分布式存储领域使用得比较普遍的中心化副本控制协议,经过不一样的系统回答了在具体实现方便上的一些选择。不过,对于具体的系统以及相关的协议,并无很深刻介绍,感兴趣的读者能够查阅相关连接。

references

刘杰:分布式原理介绍

Distributed systems for fun and profit

CAP理论与MongoDB一致性、可用性的一些思考

带着问题学习分布式系统

The Google file system

经典论文翻译导读之《Google File System》

经典论文翻译导读之《Finding a needle in Haystack: Facebook’s photo storage》

大数据平台核心技术(里面有TFS的介绍)