zookeeper是Apache开源的顶级项目,用于提供一个高性能、高可用,且具有严格的顺序访问控制能力(主要是写操作的严格顺序性)的分布式协调服务。可用于在分布式环境中保证数据的一致性,拥有非常广泛地使用场景。
主要的使用场景如下:
? 领导者(leader),负责进行投票的发起和决议,更新系统状态
? 学习者(learner),包括跟随者(follower)和观察者(observer),follower用于接受客户端请求并想客户端返回结果,在选主过程中参与投票
? Observer可以接受客户端连接,将写请求转发给leader,但observer不参加投票过程,只同步leader的状态,observer的目的是为了扩展系统,提高读取速度
? 客户端(client),请求发起方
LOOKING:当前Server不知道leader是谁,正在搜寻
LEADING:当前Server即为选举出来的leader
FOLLOWING:leader已经选举出来,当前Server与之同步
zk3.3开始引入的角色,观察最新状态,并变更。与Follower不同只是不参与投票、选举,只提供非事务服务。
? Zookeeper需保证高可用和强一致性;
? 为了支持更多的客户端,需要增加更多Server;
? Server增多,投票阶段延迟增大,影响性能;
? 权衡伸缩性和高吞吐率,引入Observer
? Observer不参与投票;
? Observers接受客户端的连接,并将写请求转发给leader节点;
? 加入更多Observer节点,提高伸缩性,同时不影响吞吐率
Zookeeper 客户端会随机的连接到 zookeeper 集群中的一个节点。
注意,ZK集群会有很多节点, 客户端在建立连接时,会随机挑选一个节点。
ZK集群对客户端的请求,按照类型(读、写两类)分开处理:
读请求
客户端直接从当前节点(其建立连接的节点)中读取数据;
写请求
这里涉及到了分布式事务。 客户端就会向 Leader 提交事务,Leader 接收到事务提交,会广播该事务,只要超过半数节点写入成功,该事务就会被提交。
Zookeeper的核心是原子广播,这个机制保证了各个Server之间的同步。实现这个机制的协议叫做Zab协议。Zab协议 的全称是 Zookeeper Atomic Broadcast (Zookeeper原子广播)。Zookeeper 是通过 Zab 协议来保证分布式事务的最终一致性。
ZAB与Paxos联系&区别
两者设计目标不一样,ZAB主要用于构建高可用分布式系统,Paxos 算法用于构建一致性状态机器。所有会有细微差别。但是ZAB就是在Paxos保证一致性基础上设计出高可用的协议。
epoch周期值(比喻:年号)
acceptedEpoch(比喻:接受的年号):follower已经接受leader更改年号的(newepoch)提议。
currentEpoch(比喻:当前的年号):当前的年号
history:当前节点接受到事务提议的log
lastZxid:history中最近接收到的提议zxid(最大的值)
年号如何递增:
当产生新Leader的时候,就从这个Leader服务器上取出本地log中最大事务Zxid,从里面读出epoch+1,作为一个新epoch,并将低32位置0(保证id绝对自增)
ZXID : 事务的Proposal(提议) 的id,可以简单理解为事务id
ZXID 是一个 64 位的数字,其中低 32 位可看作是计数器,Leader 服务器每产生一个新的事务 Proposal 的时候,都会该计数器进行加 1 操作。
ZXID 的高 32 位表示 Leader 周期 epoch 的编号,每当选举一个新的 Leader 服务器,就会从该服务器本地的事务日志中最大 Proposal 的 ZXID 中解析出对应的 epoch 值,然后对其加 1 操作,这个值就作为新的 epoch 值,并将低 32 位初始化为 0 来开始生成新的 ZXID。
Zookeeper 集群的模式包括两种基本的模式:崩溃恢复 和 消息广播
当整个集群启动过程中,或者当 Leader 服务器出现网络中弄断、崩溃退出或重启等异常时,Zab协议就会 进入崩溃恢复模式,选举产生新的Leader。
一但出现崩溃,会导致数据不一致,ZAB的崩溃恢复开始起作用。有如下两个确保:
针对上两个要求,如果Leader选举算法保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID最大)的事务Proposal,那么就能保证新的Leader 一定具有已提交的所有提案,更重要是,如果这么做,可以省去Leader服务器检查Proposal的提交和丢弃工作的这一步。
一旦Leader服务器出现崩溃,或者说网络原因导致Leader服务器失去了与过半的Follower的联系,那么就会进入崩溃恢复模式。为了保证程序的正常运行,整个恢复过程后需要选举一个新的Leader服务器。因此,ZAB协议需要一个高效可靠的Leader选举算法,从而确保能够快速的选举出新的Leader。同时,新的Leader选举算法不仅仅需要让Leader自己知道其自身已经被选举为Leader,同时还需要让集群中所有的其他机器也能够快速的感知选举产生的新的Leader服务器。
ZAB协议规定了如果一个事务Proposal在一台机器上被处理成功,那么应该在所有的机器上都被处理成功,哪怕机器出现崩溃。
当新的Leader出来了,同时,已有过半机器完成同步之后,ZAB协议将退出恢复模式。进入消息广播模式。这时,如果有一台遵守Zab协议的服务器加入集群,因为此时集群中已经存在一个Leader服务器在广播消息,那么该新加入的服务器自动进入恢复模式:找到Leader服务器,并且完成数据同步。同步完成后,作为新的Follower一起参与到消息广播流程中。
如果集群中其他机器收到客户端事务请求后,那么会先转发Leader服务器,由Leader统一处理。
ZAB协议中涉及的二阶段提交和2pc有所不同。在ZAB协议的二阶段提交过程中,移除了中断逻辑,所有Follower服务器要么正常反馈Leader提出的事务Proposal,要么就抛弃Leader服务器。ZAB协议中,只要集群中过半的服务器已经反馈ACK,就开始提交事务了,不需要等待集群中所有的服务器都反馈响应。这种模型是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题。此外,整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易保证消息广播过程中消息接受与发送的顺序性。
在整个消息广播过程中,Leader服务器会为每个事务请求生成对应的Proposal来进行广播,并且在广播事务Proposal之前,Leader服务器会首先为这个事务分配一个全局单调递增的唯一ID,我们称之为事务ID(即ZXID)。由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个事务Proposal按照其ZXID的先后顺序来进行排序与处理。
在消息广播过程中,Leader服务器会为每一个Follower服务器各自分配一个单独的队列,然后将需要广播的事务Proposal依次放入这些队列中,并且根据FIFO策略进行消息发送。每一个Follower服务器在接受到这个事务Proposal之后,都会首先将其以事务日志的形式写入到本地磁盘中去,并且在成功写入后反馈给Leader服务器一个ACK响应。当Leader服务器接收到超过半数Follower的ACK响应后,就会广播一个Commit消息给所有Follower服务器以通知其将事务进行提交,同时Leader自身也会完成事务的提交,而每一个Follower服务器收到Commit消息之后,也会完成对事务的提交。
崩溃恢复:在正常情况下运行非常良好,一旦Leader出现崩溃或者由于网络原因导致Leader服务器失去了与过半Follower的联系,那么就会进入崩溃恢复模式。为了程序的正确运行,整个恢复过程后需要选举出一个新的Leader,因此需要一个高效可靠的选举方法快速选举出一个Leader。
消息广播:类似一个两阶段提交过程,针对客户端的事务请求, Leader服务器会为其生成对应的事务Proposal,并将其发送给集群中的其余所有机器,再分别收集各自的选票,最后进行事务提交。
启动过程或Leader出现网络中断、崩溃退出与重启等异常情况时。
当选举出新的Leader后,同时集群中已有过半的机器与该Leader服务器完成了状态同步之后,ZAB就会退出恢复模式。
ZAB协议中存在着三种状态,每个节点都属于以下三种中的一种:
Looking /election:系统刚启动时或者Leader崩溃后正处于选举状态
Following :Follower节点所处的状态,Follower与Leader处于数据同步阶段;
Leading :Leader所处状态,当前集群中有一个Leader为主进程;
在ZooKeeper的整个生命周期中,每个节点都会在Looking、Following、Leading状态间不断转换.
启动之初,ZooKeeper所有节点初始状态为Looking,这时集群会尝试选举出一个Leader节点,选举出的Leader节点切换为Leading状态;当节点发现集群中已经选举出Leader则该节点会切换到Following状态,然后和Leader节点保持同步;当Follower节点与Leader失去联系时Follower节点则会切换到Looking状态,开始新一轮选举;
? 半数通过
– 3台机器 挂一台 2>3/2
– 4台机器 挂2台 2!>4/2
? A提案说,我要选自己,B你同意吗?C你同意吗?B说,我同意选A;C说,我同意选A。(注意,这里超过半数了,其实在现实世界选举已经成功了。
但是计算机世界是很严格,另外要理解算法,要继续模拟下去。)
? 接着B提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;C说,A已经超半数同意当选,B提案无效。
? 接着C提案说,我要选自己,A你同意吗;A说,我已经超半数同意当选,你的提案无效;B说,A已经超半数同意当选,C的提案无效。
? 选举已经产生了Leader,后面的都是follower,只能服从Leader的命令。而且这里还有个小细节,就是其实谁先启动谁当头。
分析以上两种细分场景:
?Leader选举算法采用了Paxos协议;
?Paxos核心思想:当多数Server写成功,则任务数据写成功如果有3个Server,则两个写成功即可;如果有4或5个Server,则三个写成功即可。
?Server数目一般为奇数(3、5、7)如果有3个Server,则最多允许1个Server挂掉;如果有4个Server,则同样最多允许1个Server挂掉由此,
我们看出3台服务器和4台服务器的的容灾能力是一样的,所以为了节省服务器资源,一般我们采用奇数个数,作为服务器部署个数。
ZAB 默认采用 TCP 版本的 FastLeaderElection 选举算法。在选举投票消息中包含了两个最基本的信息:所推举的服务器 SID 和 ZXID,分别表示被推举服务器的唯一标识(每台机器不一样)和事务 ID。假如投票信息为 (SID, ZXID)的形式。在第一次投票的时候,由于还无法检测集群其他机器的状态信息,因此每台机器都将自己作为被推举的对象来进行投票。每次对收到的投票,都是一个对投票信息(SID, ZXID)对比的过程,规则如下:
1 逻辑时钟clock表示第几轮选举,重启时为0,选举时加1
2 启动时处于looking状态
3 选举细节
4 重新发出选票
经过第二次投票后,集群中每台机器都会再次收到其他机器的投票,然后开始统计,如果一台机器收到超过了半数的相同投票,那么这个投票对应的 myid机器即为 Leader。
简单来说,通常哪台服务器上的数据越新,那么越有可能成为 Leader。原因很简答,数据越新,也就越能够保证数据的恢复。当然,如果集群中有几个服务器具有相同的 ZXID,那么 myid较大的那台服务器成为 Leader。
选举出Leader节点后ZAB进入原子广播阶段,Leader与Follower使用心跳检测来感知对方的存在:
1 当Leader节点在超时时间内能正常收到来自Follower的心跳, 那Follower节点会一直与该节点保持连接;
2 若超时时间内Leader没有接收到来自过半Follower节点的心跳检测或TCP连接断开,那Leader会结束当前周期的领导,切换到Looking状态,所有Follower节点也会放弃该Leader节点切换到Looking状态,然后开始新一轮选举;
节点在一开始都处于选举阶段,只要有一个节点得到超半数节点的票数,它就可以当选准 leader。只有到达 Phase 3 准 leader 才会成为真正的 leader。这一阶段的目的是就是为了选出一个准 leader,然后进入下一个阶段。
协议并没有规定详细的选举算法,后面我们会提到实现中使用的 Fast Leader Election。
在这个阶段,followers 跟准 leader 进行通信,同步 followers 最近接收的事务提议。这个一阶段的主要目的是发现当前大多数节点接收的最新提议,并且准 leader 生成新的 epoch,让 followers 接受,更新它们的 accepted Epoch
一个 follower 只会连接一个 leader,如果有一个节点 f 认为另一个 follower p 是 leader,f 在尝试连接 p 时会被拒绝,f 被拒绝之后,就会进入 Phase 0。
同步阶段主要是利用 leader 前一阶段获得的最新提议历史,同步集群中所有的副本。只有当 quorum 都同步完成,准 leader 才会成为真正的 leader。follower 只会接收 zxid 比自己的 lastZxid 大的提议。
到了这个阶段,Zookeeper 集群才能正式对外提供事务服务,并且 leader 可以进行消息广播。同时如果有新的节点加入,还需要对新节点进行同步。
值得注意的是,ZAB 提交事务并不像 2PC 一样需要全部 follower 都 ACK,只需要得到 quorum (超过半数的节点)的 ACK 就可以了。
完成Leader选举之后,在正式开始工作(即接收客户端的事务请求,然后提出新的提案)之前,Leader服务器会首先确认事务日志中的所有Proposal都已经被集群中过半的机器提交了,即是否完成数据同步。
集群中所有的正常运行的服务器,要么成为Leader,要么成为Follower并和Leader保持同步。Leader服务器需要确保所有的Follwer服务器能够接收到每一条事务Proposal,并且能够正确地将所有已经提交了的事务Proposal应用到内存数据库中。Leader服务器会为每一个Followe服务器都准备一个队列,并将没有被各Follower服务器同步的事务以Proposal消息形式逐个发送到Follower服务器,并在每一个Proposal消息后紧跟着再发送一个Commit消息,以表示这个事务已经被提交。等到Follower服务器将所有尚未同步的事务Proposal都从Leader服务器上同步过来并成功应用到本地数据库中后,Leader服务器就会将改Follower服务器加入到真正可用的Follower列表中,并开始之后的其他流程。
ZAB协议如何处理那些需要被丢弃的事务Proposal的?
在ZAB协议的事务编号ZXID设计中,ZXID是一个64位数字,其中低32位可以看做一个简单的单调递增的计数器,针对客户端的每一个事务请求,Leader服务器在产生一个新的事务Proposal的时候,都会对改计数器进行加一操作;而高32位则代表了leader周期epoch的编号,每当选举产生一个新的Leader服务器,就会从这个Leader服务器取出本地日志中最大事务Proposal的ZXID,并从ZXID中解析出对应的epoch值,然后再对其进行加1操作,之后的编号就会作为新的epoch,并将低32位 置0来开始生成新的ZXID。ZAB协议中的这一通过epoch编号来区分Leader周期变化的策略,能够有效避免不同的Leader服务器错误的使用相同的ZXID编号提出不一样的事务的情况。
基于这样的策略,当一个包含了上一个Leader周期中尚未提交过的事务服务器启动时,肯定无法成为leader。因为当前集群中肯定包含一个Quorum集合,该集合中机器一定包含了更高的epoch的事务Proposal。
分布式事务就是指事务的参与者、支持事务的服务器、资源服务器以及事务管理器分别位于不同的分布式系统的不同节点之上。简单的说,就是一次大的操作由不同的小操作组成,这些小的操作分布在不同的服务器上,且属于不同的应用,分布式事务需要保证这些小操作要么全部成功,要么全部失败。本质上来说,分布式事务就是为了保证不同数据库的数据一致性。
为了保证事务的顺序一致性,zookeeper采用了递增的事务id号(zxid)来标识事务。所有的提议(proposal)都在被提出的时候加上了zxid。实现中zxid是一个64位的数字,它高32位是epoch(有点类似年代、年号)用来标识 leader关系是否改变,每次一个leader被选出来,它都会有一个新的epoch,标识当前属于那个leader的统治时期。低32位用于递增计数。
ZooKeeper状态的每一次改变, 都对应着一个递增的Transaction id, 该id称为zxid. 由于zxid的递增性质, 如果zxid1小于zxid2, 那么zxid1肯定先于zxid2发生.
创建任意节点, 或者更新任意节点的数据, 或者删除任意节点, 都会导致Zookeeper状态发生改变, 从而导致zxid的值增加.
Zab协议的核心:定义了事务请求的处理方式
1)所有的事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被叫做 Leader服务器。其他剩余的服务器则是 Follower服务器。
2)Leader服务器 负责将一个客户端事务请求,转换成一个 事务Proposal,并将该 Proposal 分发给集群中所有的 Follower 服务器,也就是向所有 Follower 节点发送数据广播请求(或数据复制)
3)分发之后, Leader服务器需要等待所有Follower服务器的反馈(Ack请求),在Zab协议中,只要超过半数的Follower服务器进行了正确的反馈后, 那么 Leader 就会再次向所有的 Follower服务器发送 Commit 消息,要求其将上一个 事务proposal 进行提交。 (注意:有点像2PC)
广播模式下只有主节点可以发送广播消息,如果某个从节点需要发送广播信息,也需要通过主节点进行。
消息广播阶段的数据写入策略,通过事务完成,有以下特点:
1)在zookeeper集群中,数据副本的传递策略就是采用消息广播模式。zookeeper中农数据副本的同步方式与二段提交相似,但是却又不同。二段提交要求协调者必须等到所有的参与者全部反馈ACK确认消息后,再发送commit消息。要求所有的参与者要么全部成功,要么全部失败。二段提交会产生严重的阻塞问题。
2)Zab协议中 Leader 等待 Follower 的ACK反馈消息是指“只要半数以上的Follower成功反馈即可,不需要收到全部Follower反馈”
消息广播具体步骤如下:
1)客户端发起一个写操作请求。
2)Leader 服务器将客户端的请求转化为事务 Proposal 提案,同时为每个 Proposal 分配一个全局的ID,即zxid。
3)Leader 服务器为每个 Follower 服务器分配一个单独的队列,然后将需要广播的 Proposal 依次放到队列中取,并且根据 FIFO 策略进行消息发送。
4)Follower 接收到 Proposal 后,会首先将其以事务日志的方式写入本地磁盘中,写入成功后向 Leader 反馈一个 Ack 响应消息。
5)Leader 接收到超过半数以上 Follower 的 Ack 响应消息后,即认为消息发送成功,可以发送 commit 消息。
6)Leader 向所有 Follower 广播 commit 消息,同时自身也会完成事务提交。Follower 接收到 commit 消息后,会将上一条事务提交。
zookeeper 采用 Zab 协议的核心,就是只要有一台服务器提交了 Proposal,就要确保所有的服务器最终都能正确提交 Proposal。这也是 CAP/BASE 实现最终一致性的一个体现。
Leader 服务器与每一个 Follower 服务器之间都维护了一个单独的 FIFO 消息队列进行收发消息,使用队列消息可以做到异步解耦。 Leader 和 Follower 之间只需要往队列中发消息即可。如果使用同步的方式会引起阻塞,性能要下降很多。
1、主节点广播发送事务提交提议
包括以下步骤:
a.针对客户端的事务请求,leader服务器会先将该事务写到本地的log文件中
b.然后,leader服务器会为这次请求生成对应的事务Proposal并且为这个事务Proposal分配一个全局递增的唯一的事务ID,即Zxid
c.leader服务器会为每一个follower服务器都各自分配一个单独的队列,将需要广播的事务Proposal依次放入队列中,发送给每一个follower
2、从节点接收到提议后,回复确认信息通知主节点
包括以下步骤:
d. 每一个follower在收到队列之后,会从队列中依次取出事务Proposal,写入本地的事务日志中。如果写成功了,则给leader返回一个ACK消息
3、主节点接收到超过法定数量从节点确认信息后,广播发送事务提交命令到从节点
e. 当leader服务器接收到半数的follower的ACK相应之后,就会广播一个Commit消息给所有的follower以通知其进行事务提交,同时leader自身也进行事务提交
当然,在这种简化了的二阶段提交模型下,是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题
整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易地保证消息广播过程中消息接收与发送的顺序性
zookeeper 服务实现中很重要的一点:顺序性。顺序请求,顺序响应;主节点事务顺序提交,从节点按顺序响应事务等等。
客户端对ZK的更新操作都是永久的,不可回退的,也就是说,一旦客户端收到一个来自server操作成功的响应,那么这个变更就永久生效了。为做到这点,ZK会将每次更新操作以事务日志的形式写入磁盘,写入成功后才会给予客户端响应。明白这点之后,你就会明白磁盘的吞吐性能对于ZK的影响了,磁盘写入速度制约着ZK每个更新操作的响应。
事务日志的写性能确实对ZK性能,尤其是更新操作的性能影响很大,为了尽量减少ZK在读写磁盘上的性能损失,可以考虑使用单独的磁盘作为事务日志的输出(使用单独的挂载点用于事务日志的输出)
ZK的事务日志输出是一个顺序写文件的过程,本身性能是很高的,所以尽量保证不要和其它随机写的应用程序共享一块磁盘,尽量避免对磁盘的竞争。
在分布式系统中有个CAP理论,对于P(分区容忍性)而言,是实际存在 从而无法避免的。因为,分布系统中的处理不是在本机,而是网络中的许多机器相互通信,故网络分区、网络通信故障问题无法避免。因此,只能尽量地在C 和 A 之间寻求平衡。对于数据存储而言,为了提高可用性(Availability),采用了副本备份,比如对于HDFS,默认每块数据存三份。某数据块所在的机器宕机了,就去该数据块副本所在的机器上读取(从这可以看出,数据分布方式是按“数据块”为单位分布的)
但是,问题来了,当需要修改数据时,就需要更新所有的副本数据,这样才能保证数据的一致性(Consistency)。因此,就需要在 C(Consistency) 和 A(Availability) 之间权衡。
副本协议的定义:
副本按特定的协议流程控制副本数据的读写行为,使得副本满足一定的可用性和一致性要求的分布式协议。
副本协议分为两类:中心化副本控制协议和去中心化副本控制协议
由一个中心节点协调副本数据的更新、维护副本之间的一致性。
优点:协议相对简单,所有副本相关的控制交由中心节点完成。
缺点:系统的可用性依赖于中心化节点,当中心节点异常或者中心节点通信中断时,系统将失去某些服务。
中心化副本控制协议主要有primary-secondary协议、2PC、MVCC。
primary-secondary又称为primary-backup,在协议中副本被分为两类,有且只有一个primary副本,其他的全部是secondary副本。
primary-secondary包括以下四大内容:
数据更新流程
数据读取方式
Primary副本的确定和切换
数据同步
? (1):数据更新都有primary节点协调完成。
? (2):外部节点将更新操作发送给primary节点。
? (3):primary节点进行并发控制即确定并发更新操作的先后顺序。
? (4):primary节点将更新操作发送给secondary节点。
? (5):primary根据secondary节点的完成情况决定更新是否成功并将结果返回给外部节点。
? 如果只需要最终一致性,则读取任何副本都可以满足需求。如果需要会话一致性,则可以为副本设置版本号,每次更新后递增版本号,则用户读取副本时验证版本号,从而保证用户读到的数据都在会话范围内递增。想要最终一致性比较困难,由一下几种思路参考.
? (1):只读primary节点。
? (2):由primary控制节点secondary的可用性,更新成功为可用,更新不成功为不可用。
? (3): 基于Quorum机制。
? 通常在primary-secondary类型的分布式系统中,哪个副本是Primary是属于元信息,由专门的元数据服务器维护。执行更新操作前先查询元数据管理服务器获取primary信息,再进行操作。
切换副本的难度在于两个方面:
(1):如何确定节点的状态以及发现原primary节点异常时一个较为复杂的问题。
基于Lease机制(心跳机制)、基于Quorum机制(过半机制)。
(2):在原primary以及死机,如何确定一个secondary副本使得该副本上面的数据与原primary一致成为新的问题。
由于分布式系统中可靠的发现节点异常需要一点的探测时间的,探测时间通常在10秒级别。 primary-secondary类型的分布式系统的最大缺点就是primary切换带来的一定时间的停服务时间。
? 通常的不一致情况:
? (1): 由于网络分化等异常,secondary上面的数据落后primary上的数据。
? (2):在某些协议下面可能有脏数据,需要被丢弃。
? (3):secondary是新增的副本,完全没有数据,需要从其他副本拷贝数据。
? 解决方法
? 对于(1),常用的方法是回放primary上面的操作日志(通常是redo日志),从而追上primary的更新进度。
? 对于(2),较好的方法就是在设计分布式协议不产生脏数据。也可以基于undo日志的方法删除脏数据。
? 对于(3),直接拷贝primary数据,或者使用快照。
去中心化协议没有因为中心节点异常带来的停服务问题,缺点是协议过程通常比较复杂。
去中心化副本控制协议主要有:Paxos
约定:
更新操作是一系列顺序的过程,每次更新记录操作记为Wi,i为更新操作单调递增的序号,每个Wi执行成功后副本数据都变化,称为不同数据版本,记为Vi。
WARO是一种简单的副本控制协议,当 Client 请求向某副本写数据时(更新数据),只有当所有的副本都更新成功之后,这次写操作才算成功,否则视为失败。这样的话,只需要读任何一个副本上的数据即可。但是WARO带来的影响是写服务的可用性较低,因为只要有一个副本更新失败,此次写操作就视为失败了。
需要一种机制当某次更新操作Wi一旦所有副本都成功,全局都能知道这个消息,此后读取操作指定读取数据版本为Vi的数据。
写服务而言,记录更新成功的版本号Vi的操作将成为关键操作,通常方法就是将版本号信息存放在某个或某组元数据服务器上,容易成为瓶颈。因为更新需要全部副本成功才能成功,所以当任意一个副本不可用的情况,更新服务就不可用。在工程实践中,这种机制往往比较难实现或者效率较低。
读服务而言系统可以容忍N-1个副本异常。
从这里可以看出两点:①写操作很脆弱,因为只要有一个副本更新失败,此次写操作就视为失败了。②读操作很简单,因为,所有的副本更新成功,才视为更新成功,从而保证所有的副本一致。这样,只需要读任何一个副本上的数据即可。假设有N个副本,N-1个都宕机了,剩下的那个副本仍能提供读服务;但是只要有一个副本宕机了,写服务就不会成功。
WARO牺牲了更新服务的可用性,最大程度地增强了读服务的可用性。
Quorum在WARO之上做了限制而来。
Quorum是一种简单的副本管理机制。WARO牺牲了更新服务的可用性,最大程度的增强了读服务的可用性。
在Quorum机制下面,当某次更新操作Wi一旦在所有的N个副本都成功,则认为更新操作为“成功提交的更新操作”,对于的数据为“成功提交的数据”。
Quorum机制是“抽屉原理”的一个应用。定义如下:假设有N个副本,更新操作wi 在W个副本中更新成功之后,才认为此次更新操作wi 成功。称成功提交的更新操作对应的数据为:“成功提交的数据”。对于读操作而言,至少需要读R个副本才能读到此次更新的数据。其中,W+R>N ,即W和R有重叠。一般,W+R=N+1
W+R>N ,得到 R>N-W
由于更新在W个副本上面是成功的,所有最多读取R个副本则一定能读到Wi更新后的数据Vi。
当W=N,R=1,W+R=N+R>N , 就得到WARO。
假设系统中有5个副本,W=3,R=3。初始时数据为(V1,V1,V1,V1,V1)--成功提交的版本号为1
当某次更新操作在3个副本上成功后,就认为此次更新操作成功。数据变成:(V2,V2,V2,V1,V1)--成功提交后,版本号变成2
因此,最多只需要读3个副本,一定能够读到V2(此次更新成功的数据)。而在后台,可对剩余的V1 同步到V2,而不需要让Client知道。
对于强一致性系统,应该始终读取返回最新的成功提交的数据,在Quorum机制下面条件需要进一步限制。
1:限制提交的更新操作必须严格递增,即只有在前一个更新操作成功提交之后才可以提交后一个更新操作,从而保证提交的数据版本是连续增加的。
2: 读取R个副本,对于R个副本中版本号最高的数据,
2.1 若已经存在W个,则该数据是最新的成功提交的数据
2.2 若存在个数少于W个,为X个,继续读取其他副本,直到读取到W个该版本的副本,则认为是最新提交的数据。
国内多个厂商发生数据灾难,导致服务中断,关于异地多活的容灾讨论重新被激活,引起业界的广泛关注。所以数据往往有多个副本,从而,使用Quorum机制保障多副本数据的一致性,变得非常普遍。
腾讯的NoSQL存储系统经过精心设计,已经能够应对海量数据的高并发、高可用、低延迟的应用场景,由几百台存储机组成的存储集群可以轻松扛起数百万的读写并发。集群的性能具有线性扩展,集群最大可以容纳到几千台存储服务器。当然如此庞大的集群,相应地也会带来管理上的复杂,因此的一个集群不会超过1千台服务器
微信使用的NoSQL存储名称为Quorum_KV,是基于LSMTree研发的强一致性、持久化KV分布式存储,支持key-table 和 string-value两种存储模型。
Quorum_KV通过Quorum协议实现双向可写功能,一个最小的存储单元由二台存储机和一台仲裁机组成,写存储机时经过仲裁决定被写的机器。
数据的读写都在内存中进行,内存的Key采用多阶HASH来索引,读写性能极高,但受数据容量受内存的限制。数据在主、备上都落盘,保证了数据的持久性。一个存储集群可以支持业务混合存储,业务Key采用一致性HASH方式分布。
写数据时直接写内存的Memtable表。Memtable写满后转换成Immutable。Immutable定期Dump到本地磁盘变成数据文件,数据文件不断递增形成不同level级别数据文件,不同level级别的数据文件会定期合并。
内存表使用Skiplist来做内存的Key索引。
读数据时先读内存表,如果找不到记录再寻找Immutable,然后是level 0磁盘文件、level 1……直至寻找到记录返回结果。所以可以看到,Quorum_KV的设计是面向写量大,读量小的业务场景,很好地适应了微信消息写量大的特点。
一个最小的存储单元由二台或多台存储主、备组成,主备可组成一主多备的链状结构。数据写入主后同步到备。客户端可同时读取主备,因此读到备的客户端会有一定的数据延迟,适合对数据一致性要求不高的业务。
Zookeeper的选举机制是遵循了Quorum的,这也是为什么我们部署Zookeeper必须要求有奇数个Cluster可用的原因。这样一是能保证Leader选举时不会出现平票的情况,避免出现脑裂。二是Leader在向Follower同步数据的时候,必须要超过半数的Follower同步成功,才会认为数据写入成功。
ES 在 Master 被选举之前是一个 P2P 的系统,但是当 Master 被选取后,它的管理本质上是 Master 和 slave的模式。
Elasticsearch 看名字就能大概了解下它是一个弹性的搜索引擎。首先弹性隐含的意思是分布式,单机系统是没法弹起来的,然后加上灵活的伸缩机制,就是这里的 Elastic 包含的意思。它的搜索存储功能主要是 Lucene 提供的,Lucene 相当于其存储引擎,它在之上封装了索引,查询,以及分布式相关的接口。
Elasticsearch是一个实时(索引数据到能被搜索大概1s左右)的分布式搜索和分析引擎,主要用于全文搜索,结构化搜索以及分析。Elasticsearch使用Lucene作为内部引擎,但是在使用它做全文搜索时,只需要使用统一开发好的API即可,而不需要了解其背后复杂的Lucene的运行原理,可以说是一个开箱即用的分布式实现,其内部定义了大量的默认值。Elasticsearch并不仅仅是Lucene这么简单,它不但包括了全文搜索功能,还可以进行以下工作:
除主从(Leader/Follower)模式外,另一种选择是分布式哈希表(DHT),可以支持每小时数千个节点的离开和加入,其可以在不了解底层网络拓扑的异构网络中工作,查询响应时间大约为4到10跳(中转次数)。例如,Cassandra就是使用这种方案。但是在相对稳定的对等网络中,主从模式会更好。
ES的典型场景中的另一个简化是集群中没有那么多节点。通常,节点的数量远远小于单个节点能够维护的连接数,并且网络环境不必经常处理节点的加入和离开。这就是为什么主从模式更适合ES。
原文:https://www.cnblogs.com/crazymakercircle/p/14339702.html