一 CAP 理论 和BASE 理论
1.1 CAP理论
CAP: 指的是分布式系统中一致性,可用性和分区容错性的缩写,即Consistency, Availablity和Partition Tolerance的缩写。
一致性:在分布式环境中,一致性是指数据在多个副本之间是否能够保持一致的特性。当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。
可用性:可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。
分区容错性:即分布式系统在遇到任何网络分区或者分裂故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务。
网络分裂或者网络分区:
是指两个系统所有网络连接同时发生故障后所出现的情况。发生这种情况时,分裂的系统双方都会从对方一侧重新启动应用程序,进而导致重复服务或者脑裂。
一般的分布式系统基本上难以满足所有特性,只能满足其中2个特性。但是分区容错性一般是关键,分布式系统一般都会保留这个特性。
CA: 不允许分区,其实是不现实,因此CA的系统更多的是允许分区后个子系统依然保持CA
常见的产品: Consule 和 RMDBS
CP: 不要求可用,也就是说只要求强一致性和允许分区容错。
常见的产品:Zookeeper,MongoDB, HBase, Redis
AP: 高可用并允许分期容错性,一旦分区发生,节点间可能会失去联系,为了高可用,每一个节点只能用本地数据提供服务,这样会导致全局数据不一致,不一致总比没有数据好。
常见产品:Eureka,CouchDB, Cassandra 等
1.2 BASE理论
BASE:是Basically Availability(基本可用),Soft State(软状态)和
Eventually Consistency(最终一致性)
基本可用:分布式系统在出现不可预知故障的时候,允许损失部分可用性:
响应时间上的损失:正常情况下,一个在线搜索引擎需要0.5秒内返回给用户相应的查询结果,但由于出现异常(比如系统部分机房发生断电或断网故障),查询结果的响应时间增加到了1~2秒。
功能上的损失:正常情况下,在一个电子商务网站上进行购物,消费者几乎能够顺利地完成每一笔订单,但是在一些节日大促购物高峰的时候,由于消费者的购物行为激增,为了保护购物系统的稳定性,部分消费者可能会被引导到一个降级页面。
软状态:也叫弱状态,是指允许系统中的数据存在中间状态,允许系统在不同节点的数据副本之间进行数据同步的过程存在延时
最终一致性:是系统中所有的数据副本,在经过一段时间的同步后,最终能够达到一个一致的状态。因此,最终一致性的本质是需要系统保证最终数据能够达到一致,而不需要实时保证系统数据的强一致性
二 2PC 和 3PC
在分布式系统中,每一个机器虽然都能够知道自己在进行事务的过程中的结果是成功或者失败,但却无法直接获取其他分布式节点的操作结果。因此当一个事务需要跨越多个分布式节点的时候,为了保持事务处理的ACID特性,就需要引入一个称为协调者的组件来统一调度所有分布式节点的执行逻辑,这些被调度的分布式节点称为参与者。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务真正进行提交。基于这个思想衍生出来二阶段和三阶段提交两种协议。
2.1 2PC
2PC是Two-Phrase Commit的缩写,即二阶段提交。为了使基于分布式系统架构下的所有节点在进行事务处理过程中保持原子性和一致性而设计的一种算法。通常,2PC也被认为是一种一致性协议,用来保证分布式系统数据的一致性。目前,绝大部分的关系型数据库都是二阶段提交协议来完成分布式事务处理的。
阶段一:请求事务提交
# 事务询问
协调者向所有的参与者发送事务内容,询问是否可以执行事务提交操作,并等待各个参与者的响应
# 执行事务
各个参与者节点执行事务操作,并将UNDO和REDO信息计入事务日志中
# 各个参与者向协调者反馈事务询问的响应
如果参与者成功执行了事务操作,那么就反馈给协调者Yes响应,表示事务可以执行;如果参与者,没有成功执行事务,那么就反馈给协调者No响应,表示事务不可用执行
这个阶段也被称之为投票阶段,即各个参与者投票表明是否要继续执行接下去的事务提交操作
阶段二:执行事务提交
协调者会根据参与者的反馈情况来决定是否最终是否可以进行事务提交操作,正常情况下,包含以下2种情况:
# 执行事务提交
假如协调者从所有的参与者获取到的反馈信息都是Yes,那么就表示可以进行事务提交。事务提交的流程如下:
1 协调者向所有的参与者节点发送commit请求
2 各个参与者节点收到协调者的commit请求之后,开始进行事务
提交,事务提交完成之后,释放整个事务执行期间所占用的事务资源。
3 各个参与者在事务完成之后,会向协调者发送ack消息
4 协调者收到所有参与者的ack消息,事务完成
# 中断事务
如果参与者有一个反馈了No的消息,或者在等待超时之后,协调者无法接受到参与者任何反馈信息,那么就会中断事务。
1 协调者向所有的参与者发送rollback请求
2 各个参与者在收到协调者的rollback请求之后,开始进行事务的回滚,会利用在阶段一种记录的Undo信息来执行回滚,回滚完成之后,会释放整个事务执行期间占用的资源
3 各个参与者在回滚完成之后,会向协调者发送ack消息
4 协调者收到所有参与者的ack消息,回滚完成
以上就是二阶段提交过程,前后两个阶段的处理逻辑。简而言之,就是将一个事务过程分为了投票和执行两个阶段,其核心是对每一个事务都采用先尝试后提交处理方式。
2.2 2PC优缺点
优点:
原理简单,实现方便
缺点:
同步阻塞问题:执行过程中,所有参与节点都是事务阻塞型的。当参与者占有公共资源时,其他第三方节点访问公共资源不得不处于阻塞状态。
单点问题:协调者存在单点问题
数据不一致性:如果执行事务提交的时候,当协调者发送事务请求之后,发生了局部的网络异常或者在尚未完成提交请求之前发生了崩溃,导致最终只有部分参与者收到提交请求,于是这一部分进行事务提交,另外的无法进行事务提交
2.3 3PC
3PC 是指 Three-Phrase Commit缩写,即三段提交,是2PC改进版。将两阶段提交协议的请求事务提交的过程一分为二,形成了由CanCommit, PreCommit和 DoCommit三个阶段组成的事务处理协议。
阶段一:canCommit阶段
# 事务询问
协调者向所有者的参与者发送包含事务内容的canCommit请求,询问是否可以执行事务提交操作,并开始等待各个参与者的响应
# 参与者反馈事务询问的响应
参与者在接受到来自协调者的canCommit请求后,正常情况下,如果其自身认为可以顺利执行事务,那么会反馈Yes响应之后,并进入预备状态,否则反馈No
阶段二:preCommit阶段
协调者会根据各个参与者的反馈情况决定是否可以事务的preCommit操作,正常情况下,包含2种可能:
# 执行事务预提交
如果协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务预提交。流程如下:
1. 发送预提交请求:协调者向所有参与者节点发出preCommit的请求,并进入prepared阶段
2. 事务预提交: 参与者接受到preCommit阶段请求后,会执行事务操作,并将undo和redo信息记录到事务日志中
3. 各个参与者向协调者反馈事务执行的响应:如果参与者成功执行了事务操作,那么就会反馈给协调者Ack消息,同时等待最终的提交请求或者中断事务请求
# 中断事务
假设任何一个参与者向协调者反馈了No响应,或者等待超时之后,协调者无法收到所有参与者的反馈响应,那么就会中断事务
阶段三:doCommit
该阶段进行真正的事务提交,会存在以下2种情况:
# 执行提交
1. 发送提交请求:假设协调者工作正常,并且接受到了来自所有参与者的Ack响应之后,它将预提交状态转化为提交状态,并向所有的参与者发送doCommit请求
2. 事务提交
参与者接受到doCommit请求后,会正式实行事务提交操作,并在完成提交之后,释放整个事务期间占用的事务资源
3. 反馈事务结果
参与者在完成事务提交之后,向协调者发送Ack消息
4 完成事务
协调者接受到所有参与者反馈的Ack响应之后,完成事务
# 中断事务
假设协调者处于正常状态,并且有任意一个参与者向协调者反馈了No响应,或者在等待超时之后,协调者尚无法接收到所有参与者的反馈响应,那么就会中断事务。
1. 发送中断请求
协调者向所有参与者发送事务中断请求
2. 事务回滚
参与者接收到事务中断请求之后,会利用七在阶段二记录的undo信息来执行事务回滚,完成之后吗,释放资源
3. 反馈事务回滚结果
参与者在完成事务回滚之后,向协调者发送Ack消息
4 中断事务
协调者接收到所有参与者反馈的Ack消息之后,中断事务
2.4 3PC优缺点
优点:降低了二阶段参与者的阻塞范围
缺点:参与者接收到预提交的请求后,如果出现网络分区,此时协调者所在节点和参与者无法进行正常的通信,该参与者依然会进行事务提交,会出现数据不一致
三 Paxos协议 和 ZAB协议
3.1 Paxos 协议
Paxos协议是一种基于消息传递且具有高度容错性的一种分布式一致性算法。常见的分布式系统中,总会发生诸如机器宕机或者网络异常的情况,Paxos算法需要解决的问题就是如何在一个可能发生上述异常的分布式系统中,快速且正确的在集群内部对某个数据值达成一致,并且保证不论发生任何异常,都不会破坏整个系统的一致性。
Paxos一些概念:
Proposal Value: 提议的值
Proposal Number: 提议号,要求提议编号不能冲突
Proposal: 提议,由提议编号和提议值组成
Proposer: 提议发起者,处理客户端请求,将客户端的请求发送到集群中,以便决定这个值是否可以被批准
Acceptor: 提议接收者
Learner: 提议学习者
Paxos原则:
# 安全原则:保证不能做错的事
1 只能有一个值被批准,不能出现第二个值把第一个值覆盖的情况
2 每一个节点只能学习到已经被批准的值,不能学习没有被批准的值
# 存活原则:只要多数服务器存活并且彼此之间可以通信
如果只有一个Acceptor的时候,可能存在单点问题,而且也违反了存活原则。为了解决这个问题,就必须使用多个Acceptor,然后只要其中的多数批准了一个值,这个值才可以被最终批准。
批准第一个到达的值
首先必须规定,每一个Acceptor必须批准第一个到达的值,哪一个值达到多数的批准就是最终批准的值。
但是如果有多个Proposer同时提出,这可能会导致虽然每一个Acceptor都批准它收到的第一个提案,但是没有一个提案是由多数人都批准的,如图示:
或者一个Proposer同时提出多个提议场景,是无法选定一个提案的。如图示:
另外即使只有2个提案被提出,但是每一个被差不多一般Acceptor批准了,此时即使只有一个Acceptor出错,都有可能导致无法确定该选定哪一个提案,如图示:
因此,如果有多个提案被不同的Proposer同时提出,那么我们需要使用一个全局的编号来唯一标示每一个被Acceptor批准的提案,当一个具有某Value值的提案被半数以上的Acceptor批准以后,我们就认为这个Value被选定了,这里的提案指的是[编号,Value]
Proposer生成提案
# Proposer选择一个新的提案编号M,然后向某个Acceptor集合的成员发送请求,要求该集合中的Acceptor做出如下回应:
1 向Proposer承诺保证不再批准任何编号小于M的提案
2 如果Acceptor已经批准过任何提案,那么就向Proposer反馈当前该Acceptor已经批准的编号小于M但为最大编号的那个提案的值
我们将该请求称为编号为M的提案的Prepare请求
# 如果Proposer收到了来自半数以上的Acceptor的相应结果,那么他就可以产生编号为M,Value为V的提案
再确认提案后,Proposer就会将该提案再次发送给Acceptor集合,并期望获得他们的批准,我们称此请求为Accept请求。
Acceptor批准提案:
一个Acceptor只要尚未响应过任何编号大于M的Prepare请求,那么它就可以接收这个编号为M的提案。
活锁问题:
Proposer P1提出来一个编号为M1的提议,然后向Acceptor集合中过半数的Acceptors成员发送编号为M1的Prepare请求;如果一个Acceptor收到一个编号为的Prepare请求,且编号大于该Acceptor已经响应的所有Prepare请求编号,那么他就会将它已经批准过的最大编号的提案作为响应反馈给Proposer,同时该Acceptor会承诺不再批准任何编号小于的提案。
但是此时,Proposer P2提出了一个编号为M2的提案,且M2 > M1,同样也完成了阶段一的流程,于是Acceptor已经承诺不再批准小于M2的提案了。因此当P1进入阶段二的时候,发出的Accept请求将被Acceptor忽略,于是P1再次进入阶段一,并提出M3的提议,M3>M2,而这又导致P2在第二阶段的Accept请求被忽略,如此往复,一直循环,陷入一个类似于死循环的状态。称之为活锁。
3.2 ZAB协议
ZAB: 是Zookeeper Atomic Broadcast, 即zookeeper原子消息广播。是为分布式协调服务zookeeper专门设计的一种支持崩溃和恢复的算法。
ZAB协议并不是像Paxos一样是通用的分布式一致性算法。
3.2.1 zookeeper为什么不使用paxos保持分布式数据一致性
# 活锁的问题: 我们知道基础的Paxos算法中,有可能出现活锁的问题,所以会造成很多协议都是Paxos协议的退化或者变异,而不是直接使用Paxos协议
# 复杂度问题:由于基础的Paxos协议存在很多问题,导致变异的Paxos协议出现,比如为了解决活锁问题,有了Multi-Paxos;为了解决通信次数问题,有了Fast-Paxos等,再加之考虑性能等问题,很多分布式一致性框架并不是直接基于原生的Paxos协议来实现
# 全局序列问题:对于Paxos来说,不能保证两次提交最终的顺序
所以,zookeeper也没有使用Paxos协议来作为自己的实现协议。
基础Paxos存在活锁问题,所以zookeeper引入了Leader机制,但是Leader有单点问题,zookeeper就引入了Leader选举的机制,这就必然有数据不一致的情形,于是就有了同步的流程。
3.2.2 ZAB协议的核心
所有的事务请求,必须由一个全局的唯一的服务器来协调处理,这个服务器叫做Leader,而其他的服务器叫做Follower。
Leader服务器负责将一个客户端的事务请求转换成事务Proposal,即事务提议,并将该事务提议分发给集群中所有的Follower服务器。
之后Leader服务器需要等待Follower服务器的反馈,一旦超过半数以上的Follower服务器进行了正确的反馈,那么Leader服务器就会再次向集群中所有Follower服务器分发Commit请求,要求其将刚才的事务提议进行提交
3.2.3 ZAB 协议详细分析
ZAB协议包括两种基本的模式:分别是崩溃恢复和消息广播。
当Zookeeper集群启动时,或者是Leader服务器网络中断或者宕机或者重启等异常情况,ZAB协议就会自动进入崩溃恢复模式并选举产生新的Leader服务器。当选举产生了新的Leader服务器,同时集群中已有过半的的机器与该Leader进行了数据同步,ZAB协议退出恢复模式。
当集群中有过半的Follower完成了Leader的数据同步,那么整个集群就可以进入消息广播模式。它收到客户端的事务请求,会生辰对应 事务提议并发起一轮广播协议,而如果是集群中Follower收到客户端的事务请求,则会先将这请求转发给Leader。
如果一台新的服务器加入集群,如果此时集群存在一个Leader进行广播消息,那么新加入的服务器就会自觉进入数据恢复模式:找到Leader,进行数据同步,然后一起参与消息广播流程。
消息广播:
ZAB协议的消息广播过程使用的是一个原子广播协议,类似于一个二阶段提交过程。针对客户端的事务请求,Leader服务器会为其生成对应的事提案,并将其发送给集群中其他的Follower服务器,然后再分别收集各个Follower的响应,类似于选票。如果超过半数的Follower服务器进行了正确的响应,就可以进行事务提交,向集群中所有的Follower服务器发送Commit请求。
整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,很容易保证消息广播过程中消息接收与发送的顺序性。
# Leader服务器首先将客户端的事务请求转换成事务提案,然后为这个事务提案分配一个全局唯一的单调递增的ID,称之为事务ID,即ZXID. 由于ZAB协议需要保证每一个消息严格的因果关系,因此必须将每一个事物提案按照事务ID的顺序来进行排序和处理。
# 消息广播过程中,Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务提案一次放入队列,并且根据FIFO策略进行发送。每一个Follower服务器在接收到这个事务提案的时候,都会首先将其以日志的形式写入本地磁盘中去,并且成功写入后反馈给Leader一个Ack响应。当Leader服务器接受到过半的Follower的Ack响应之后,就会广播一个Commit消息给所有的Follower,已通知其进行事务提交,同时Leader自身也会进行事务提交。
二阶段提交协议与Paxos不一样
崩溃恢复:
# ZAB协议规定:如果一个事务提案在一台机器上处理成功,那么就该在所有机器上处理成功,哪怕机器出现故障崩溃。
假设一个事务在Leader服务器上已经被提交,但是在将Commit消息发送给其他Follower服务器的时候,服务器挂掉了,那么也需要确保其他Follower服务器也被提交成功,否则出现数据不一致的情况。
# ZAB协议规定:需要确保丢弃那些只是在Leder服务器上提出的还并没有提交该事务的那些提案。
假设一个事物请求发送Leader服务器,Leader服务器将其分发给各个Follower服务器,收到过半数的Follower的请求确认,但是Leader自身还没来得及进行事务提交或者压根还没有等到过半数Follower的确认就挂了,我们就该是得这些提案被丢弃。
基于以上两点,决定了ZAB协议必须设计这样的一个Leader选举算法:既能够确保提交已经被Leader提交的事务,又可以丢弃已经被跳过的事务。如果让Leader选举算法能够保证新选举出来的Leader服务器拥有集群中所有机器最高编号(ZXID)的事务提案,那么就可以保证这个新选举出来的Leader一定具有已经提交的提案。
3.3 ZAB 与 Paxos协议的联系与区别
# 就基本的Paxos而言,存在活锁的问题;ZAB协议由于引入了Leader,由它来负责处理所有客户端事务请求,类似于Paxos中有一个主的Proposer ,只要主的Proposer提出一个编号高的提案,该提案将会被批准。故Zookeeper解决了活锁的问题。至于Leader的单点问题,则选举算法可以弥补。
# 对于提交的多个事务,Paxos并不能保证他们的顺序,但是ZAB可以,因为ZAB Leader服务器会为每一个Follower服务器都各自分配一个单独的队列,然后将需要广播的事务提案一次放入队列,并且根据FIFO策略进行发送
四 Zookeeper
Zookeeper是Google开源的分布式协调服务框架,分布式应用程序可以基于它实现服务同步(一致性),配置维护和命名服务等。
4.1 Zookeeper典型的应用场景
4.1.1 数据发布、订阅
数据的发布与订阅,即所谓的配置中心,就是发布者将数据发布到zookeeper的一个或者一系列节点上,供订阅者进行数据订阅,进而动态获取数据,实现配置信息的集中式管理和数据的动态更新。
发布、订阅系统一般有两种模式:push 和 pull模式。在push模式中,服务端主动将数据更新发送到所有订阅的客户端;而pull模式则是由客户端主动发起请求来获取最新数据。
Zookeeper采用的是推拉相结合的方式:
客户端向服务端注册自己需要关注的节点,一旦该节点数据发生变更,那么服务端就会向相应的客户端发送Watcher事件通知,客户端接受到这个消息通知后,需要主动到服务端获取最新的数据
3.1.2 命名服务
分布式应用中,通常需要有一套完整的命名规则,既能够产生唯一的名称又便于别人识别和记住。有点类似于JNDI,都是将有层次的目录结构关联到特定资源上。但是zookeeper的命名服务可能并不需要关联特定资源,只是需要一个不会重复的名称而已。
命名服务是zookeeper内置的功能,你只要调用zookeeper的API就能够实现。如调用create接口就可以很容易创建一个目录节点。
4.1.3 分布式协调/通知
分布式协调通知服务时分布式必不可少的一个部分,是将不同分布式组件有机结合起来关键所在。对于一个在多台机器上部署运行的应用而言,通常需要一个协调者来控制整个系统的 运行流程,例如分布式事务的处理,机器之间的相互协调。
Zookeeper的Watcher注册与异步通知机制,能够很好的实现分布式环境下不同机器甚至不同系统之间的协调与通知,从而实现对数据变更的实时处理。基于Zookeeper实现分布式协调与通知功能,通常的做法是不同的客户端都对Zookeeper上同一个数据节点进行Watcher注册,监听数据节点变化,如果数据节点变化,那么所有订阅的客户端都可以接受到Watcher的通知
4.1.4 集群管理
集群管理就是指集群监控和集群控制。我们经常有以下的一些需求:
# 当前集群有多少机器在工作
# 集群中每台机器的运行状态进行数据收集
# 集群中机器进行上下线操作
传统的方式可能是在集群中每一台节点上部署一个Agent,主动向指定的一个监控中心系统汇报自己所在机器状态。但是会遇到大规模升级困难,编程语言多样性等需求。
Zookeeper具有以下两大特性:
# 客户端对zookeeper的一个数据节点注册了Watcher监听,那么当该数据节点内容或是其子节点列表发生变更时,Zookeeper就会发送变更通知
# 对在Zookeeper上创建的临时节点,一旦客户端与服务器之间的会话失效,那么该临时节点也被自动清除
利用这些特性,监控系统可以在/clusterServers节点上注册一个Watcher监听,那么但凡进行动态添加机器的操作,就会在这个节点下创建临时节点:/clusterServers/[HostName],这样一来,监控系统就能够实时检测到机器的变动情况,至于后续的处理就是监控系统的业务了
4.1.5 Master选举
Master选举时分布式系统中常见的业务场景,Master往往用来协调集群中其他系统单元,具有对分布式系统状态变更的决定权,不如读写分离的场景,或者Master常常负责一些复杂的逻辑,并将结果处理同步给其他Slave机器
4.1.6 分布式锁
分布式锁时分布式系统之间同步访问共享资源的一种方式。如果是不同系统或是同一个系统的不同主机之间共享一个或一组资源,那么访问这些资源的时候,往往需要通过一些互斥的手段来防止彼此的干扰。
这时候就需要使用分布式锁了。
排它锁:
排它锁又称为写锁或者独占锁,是一种基本的锁类型。如果事务T1对数据对象O1加上了排它锁,那么在整个加锁期间,只允许事务T1对O1进行读取和和更新操作。其他任何事务都不能对这个数据对象进行操作
定义锁:在Java开发中,我们可以使用synchronized和ReentrantLock。
但是zookeeper没有这样的API,我们可以通过zookeeper上的数据节点来表示一个锁,比如/exclusive_lock/lock节点就可以被定义为一个锁。
获取锁: 需要获取排它锁的时候,所有的客户端都会尝试通过调用create接口,在/exclusive_lock节点下创建临时子节点/exclusive_lock/lock, zookeeper只会保证其中一个客户端可以创建成功,那么就可以认为该客户端获取了锁。同时所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听lock的变更情况
释放锁:如果当前获取锁的节点宕机或者,那么Zookeeper上的这个临时节点就会被移除;正常执行完业务逻辑后,客户端就会主动将自己临时节点释放掉
4.1.7 分布式队列
一种是常规的先进先出队列,另一种是则要等到队列元素集聚之后才统一安排的Barrier模型
FIFO: 所有客户端都会到某一个节点下创建临时节点,比如:
/queue_fifo
/queue_fifo/host1-00001
/queue_fifo/host1-00003
/queue_fifo/host1-00004
创建完节点之后,根据以下4个步骤来确定执行顺序:
# 通过调用getChildren()接口来获取/queue_fifo节点下所有子节点,即获取队列中所有的元素
# 确定自己节点序号在所有子节点中的顺序
# 如果自己不是序号最小的子节点,那么就需要进入等待,同时向比自己序号小的最后一个节点注册Watcher监听。
# 接受到Watcher通知后,重复步骤1
Barrier: 是对FIFO的增强,即开始的时候,对于/queue_barrier节点的数据内容赋值为为一个数字n,比如n=10. 表示该节点创建的子节点数量为10个的时候,才会打开Barrier。
创建完节点之后,根据如下5个步骤确定执行顺序:
# 通过调用getData接口获取/queue-barrier的数据内容
# 通过调用getChildren()接口获取/queue_barrier节点下所有子节点,也就是获取队列中的元素,同时注册对子节点列表变更的Watcher监听
# 统计子节点个数
# 如果子节点个数还不足10个,那么就需要进入等待
# 接受到Wacther通知后,重复步骤2
4.2 Leader选举算法
4.2.1 服务器启动时的选举
当第一台服务器启动的时候,他是无法完成Leader选举的,因为选举的条件是至少要2台机器,所以一般第一台启动的机器都是Follower。启动第第二台的时候,此时两台机器能够互相通信,每台机器都试图找到Leader,于是便进入了Leader的选举流程:
# 每一个Server会发出一个投票
由于每一个Server都是将自己作为Leader服务器来进行投票,每次投票的包括的基本元素有(myid,zxid)的形式表示。然后各自奖投票发给集群中其他机器
# 接收来自其他机器的投票
接收其他机器的投票,并判断该投票的有效性,包括检查是否本轮投票,是否来自LOOKING状态的的服务器
# 处理投票
在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和自己的投票进行选择,规则如下:
优先检查ZXID,ZXID大的服务器优先作为Leader
如果ZXID相同,就比较mxid,mxid比较大的服务器作为Leader
# 统计投票
判断是否有过半的机器接收到相同的投票信息
# 改变服务器状态,Leader变为LEADING状态,Follower变为FOLOOWING状态
4.2.2 Leader服务器宕机或者网络故障或重启
如果Leader服务器挂掉了,那么整个集群将暂时无法对外服务,而是进入新一轮的Leader选举,选举过程和启动时的选举基本是相同的。
# 变更状态
余下的机器都会将自己的状态变更为LOOKING,然后开始进入Leader选举流程
# 每一个Server会发出一个投票
这个过程需要生成投票信息(myix,zxid)。因为是运行期间,所以每一个服务器上的ZXID可能不同
# 接收来自各个机器的投票
# 处理投票
# 统计投票
# 改变服务器状态
4.3 各个角色介绍
4.4 请求处理
4.5 session的创建过程
4.5.1 初始化阶段
# 初始化zookeeper对象,初始化过程中,会创建一个客户端的Watcher管理器:ClientWatcherManager
# 设置会话默认Watcher, 如果构造方法传入了一个Watcher对象,那么客户端将这个对象作为默认Watcher保存在ClientWatcherManager中
# 构造zookeeper服务器地址列表管理器:HostProvider
# 创建并初始化客户端网络连接:ClientCnxn,用来管理客户端和服务端的网络交互
# 初始化SendThread 和 EventThread。SendThread用于管理客户端和服务端之间的所有网络IO,后者用于进行客户端的事件处理
4.5.2 会话创建阶段
# 启动SendThread 和 EventThread
# 获取一个服务器地址
# 创建TCP连接
# 构造ConnectRequest请求
# 发送请求
4.5.3 响应阶段
# 接受服务端的响应
# 处理响应
# 连接成功
# 生成事件:SyncConnect-None
# 查询Watcher
# 处理事件