深入剖析Zookeeper原理(四)ZK集群数据交互与同步原理
1. 集群数据交互处理流程基于Leader的写操作流程:处理流程:客户端向Leader发起写请求Leader将写请求以Proposal的形式发给所有Follower并等待ACKFollower收到Leader的Proposal后返回ACKLeader得到过半数的ACK(Leader对自己默认有一个ACK)后向所有的Follower和Observer发送CommmitLeader将处理结果返回给客户
1. 集群数据交互处理流程
-
基于Leader的写操作流程:
处理流程:
-
客户端向Leader发起写请求
-
Leader将写请求以Proposal的形式发给所有Follower并等待ACK
-
Follower收到Leader的Proposal后返回ACK
-
Leader得到过半数的ACK(Leader对自己默认有一个ACK)后向所有的Follower和Observer发送Commmit
-
Leader将处理结果返回给客户端
源码分析:
源码处理流程:
-
-
基于Follower/Observer的写操作流程:
Follower/Observer均可接受写请求,但不能直接处理,而需要将写请求转发给Leader处理,然后按照基于Leader写操作流程处理数据。
源码分析:
foller节点流程: FollowerZooKeeperServer -> FollowerRequestProcessor->zks.getFollower().request(request) ->leaderOs.writeRecord
Observer节点流程: ObserverZooKeeperServer -> ObserverRequestProcessor ->zks.getFollower().request(request) ->leaderOs.writeRecord
-
读操作流程:
Leader/Follower/Observer都可直接处理读请求,从本地内存中读取数据并返回给客户端。
源码分析:
FollowerZooKeeperServer (内部调用的是FinalRequestProcessor) -> nextProcessor.processRequest() ->FinalRequestProcessor.processRequest() ->case OpCode.getData -> zks.getZKDatabase().getNode
2. ZK集群数据同步原理
前面章节讲解了ZK的选举以及ZAB协议一致性的实现原理,那么集群之间的数据是如何实现同步的?可以支持哪些数据同步模式?
ZK集群在选举结束之后,leader 节点会进入 LEADING 的状态,而follower 节点会进入 FOLLOWING 状态;集群中节点之间将进行数据同步操作,Learner节点会向Leader服务器进行注册,当Learner服务器向Leader完成注册后,就进入数据同步环节。只有数据同步完成之后 zk集群才真正具备对外提供服务的能力。
-
数据同步流程:
Leader服务器根据peerLastZxid、minCommittedLog、maxCommittedLog的值决定数据同步类型:
- 差异化同步(DIFF同步)
- 回滚同步(TRUNC同步)
- 先回滚再差异化同步(TRUNC + DIFF同步)
- 全量同步(SNAP同步)
参数解析:
- peerLastZxid:Learner服务器(Follower或observer)最后处理的zxid。
- minCommittedLog:Leader服务器proposal缓存队列committedLog中的最小的zxid。
- maxCommittedLog:Leader服务器proposal缓存队列committedLog中的最大的zxid。
follow节点源码:
void followLeader() throws InterruptedException { // 省略 try { QuorumServer leaderServer = findLeader(); try { /** * 1. follower 与 leader 建立连接 */ connectToLeader(leaderServer.addr, leaderServer.hostname); /** * 2. follower 向 leader 提交节点信息用于计算新的 epoch 值 */ long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO); /** * 3. follower 与 leader 数据同步 */ syncWithLeader(newEpochZxid); // 省略 } catch (Exception e) { // 省略 } } finally { // 省略 } }
处理流程:
- 请求与 leader 建立连接。
- 向leader提交节点信息计算新的 epoch 值。
- follow与leader进行数据同步。
-
DIFF(差异化同步)
该图中Follower最后处理的zxid为0x500000004,minCommittedLog为0x500000003,maxCommittedLog为0x500000005,peerLastZxid在minCommittedLog和maxCommittedLog之间,就会走DIFF差异化同步。
Follower和Leader之间同步处理:
- Learner会给Leader发送ACKEPOCH数据包,将当前Learner的currentEpoch和最新事务序号lastZxid发送过去,并告诉Leader自己的状态。
- 在判断确认采用DIFF差异化同步后,Leader会发送DIFF指令给Learner,告诉它即将开始同步差异化的proposal(即Leader已提交但是Learner还未提交的proposal)。
- 对于上图情况,只有一个proposal需要同步,以此为例,Leader服务器会为一个proposal发送两个数据包给Learner来完成同步,分别是PROPOSAL内容数据包和COMMIT指令数据包,这两个一组拥有相同的zxid。如果有多个需要同步的proposal,就重复发送proposal对应的这两个包给Learner来实行同步直到最后完成所有的同步。
- 请求缓存队列中的proposal同步完成后,Leader会发送一个NEWLEADER指令到Learner。
- Learner收到NEWLeader指令后,会反馈一个ACK消息到Leader,表示自己确认完成请求缓存队列中proposal的同步。
- 以上是针对一个Learner的同步,会单独在一个LearnerHandler线程进行处理,其他的Learner也会有对应的LearnerHandler线程来处理,Leader主线程会等待Learner的同步结果。
- 如果是满足“过半策略”后,Leader服务器会向所有完成同步的Learner发送UPTODATE指令,告诉它们数据已经是最新的了,并且集群因为过半达到数据一致可以对外提供服务。
- 最后Learner在接收到UPTODATE指令后,会停止与Leader的数据同步,并再次反馈一个ACK消息。
源码分析:
-
TRUNC+DIFF(回滚与差异化同步)
在实际运行当中, 主节点leader可能会出现故障, 比如重启, 经过一段时间之后, 数据是如何同步呢?当Leader将事务提交到本地事务日志中后,正准备将proposal发送给其他的Follower进行投票时突然宕机,这个时候Zookeeper集群会选取出新的Leader对外服务,并且可能提交了几个事务,
经过一段时间之后,当旧的Leader再次上线,新Leader发现旧Leader身上有自己所没有的事务,就需要回滚抹去旧的Leader上自己没有的事务,再让旧的Leader同步完自己新提交的事务,这个就是TRUNC+DIFF的应用场景。
- 当Leader准备将zxid为0x500000003的proposal发送Learner投票就宕机了,导致Leader上会多出一条未在集群同步的数据。
- 此时选取了新的Leader,并且epoch在上次的基础上加1,Zookeeper集群进入了新一轮时代,并且新Leader提交了两个事务,zxid分别为0x600000001和0x600000002。
- 当旧的Leader重新上线后,新Leader发现它身上有一个0x500000003事务记录是自己没有的,这个时候对于旧的Leader来说,peerLastZxid为0x500000003,而minCommittedLog为0x500000001,maxCommittedLog为0x600000002,peerLastZxid在minCommittedLog和maxCommittedLog之间。这个时候新Leader会发送TRUNC指令给这个旧的Leader(实质上是Learner,旧的Leader是便于理解),让它截取一部分事务记录,这样老Leader会截取到最靠近peerLastZxid同时又存在于提议缓存队列的事务,即截取掉0x500000003的事务记录。
- 截取完成后,后面就是DIFF差异化同步了,流程和前面所讲一致。
-
TRUNC(回滚同步)
在什么场景下采用TRUNC呢?
如果Learner上的peerLastZxid的值,比maxCommittedLog还要大,这样只需要截取多余的部分事务记录,回滚至 maxCommittedLog就可以了,无需进行DIFF差异化同步。
这个就是TRUNC模式,该场景可以认为是 TRUNC+DIFF 的简化模式。
-
SNAP(全量同步)
如果follower 的 peerLastZxid 小于 leader 的 minCommittedLog 或者 leader 节点上不存在提案缓存队列时,将采用 SNAP 全量同步方式。
该模式下 leader 首先会向 follower 发送 SNAP 报文,随后从内存数据库中获取全量数据序列化传输给 follower, follower 在接收全量数据后再进行反序列化加载到内存数据库中。
leader 在SNAP全量数据同步之后,会向 follower 发送 NEWLEADER 报文,在收到过半的 follower 响应的 ACK 之后,说明过半的节点已经完成了数据同步,接下来 leader 就会向 follower 发送 UPTODATE 报文告知 follower 节点可以对外提供服务了,此时 leader 会启动 zk server 开始对外提供服务。
更多推荐
所有评论(0)