ZooKeeper Leader选举

ZooKeeper Leader选举

  ZooKeeper集群中有三种服务器角色:Leader、Follower和Observe。Leader选举是ZooKeeper中最重要的技术之一,也是保证分布式数据一致性的关键所在。

1.ZooKeeper的Leader选举的过程

服务器启动时期的Leader选举

  以3台机器组成的服务器集群为例,在服务器集群初始化阶段,当有一台服务器启动的时候,它是无法进行Leader选举的。当第二台机器也启动后,此时这两台机器已经能够互相通信,每台机器都试图找到一个Leader,于是便进入了Leader选举流程。

1.每个Server会发出一个投票

  由于是初始情况,因此对于Server1和Server2来说,都会将自己作为Leader服务器来进行投票,每次投票包含的最基本的元素包括:所推举的myid和ZXID,以(myid,ZXID)的形式来表示。此时Server1和Server2都会投给自己,即Server1的投票为(1,0),Server2的投票为(2,0),然后各自将这个投票发给集群中其他所有机器。

2.接收来自各个服务器的投票

  每个服务器都会接收来自己其他服务器的投票,集群中的每个服务器在接收到投票后,首先会判断该投票的有效性,包括检查是否是本轮投票、是否来自LOOKING状态的服务器。

3.处理投票

  在接收到来自其他服务器的投票后,针对每一个投票,服务器都需要将别人的投票和逐渐的投票进行PK,PK的规则如下:
  (1).优先检查ZXID,ZXID较大的服务器优先作为Leader。
  (2).如果ZXID相同的话,那么就比较myid,myid较大的服务器作为Leader服务器。

  对于Server1来说,它自己的投票是(1,0),而接收到的投票为(2,0)。首先对比两者的ZXID,发现相同。然后去比较myid,发现接收到的投票的myid大于自己的,于是便会更新自己的投票为(2,0),然后重新投票发出去。对于Server2来说,不需要更新自己的投票信息,只是再一次向集群中所有机器发出上一次投票信息即可。

4.统计投票

  每次投票后,服务器都会统计所有投票,判断是否已经有过半的机器接收到相同的投票信息。对于Server1和Server2来说,都统计出集群已经有两台机器接收了(2,0)这个投票信息,也就是Server1和Server2这两台机器。这个时候就认为已经选出了Leader。

5.改变服务器状态

  一旦确定了Leader,每个服务器都会更新自己的状态:如果是Follower,那么就变更为了FOLLOWING,如果是Leader,那么就变更为LEADING。
ZooKeeper Leader选举_第1张图片

服务器运行期间的Leader选举

  一旦Leader所在的机器挂了,会进行新一轮的Leader选举。服务器运行期间的Leader选举和启动使其的Leader选举基本过程是一致的。这里假设Leader还是Server2,此时它挂了,要开始重新进行Leader选举。

1.变更状态

  当Leader挂了之后,余下的非Observer服务器都会将自己的服务器状态变更为LOOKING,然后开始进入Leader选举流程。

2.每个Server会发出一个投票

  在这个过程中,还是需要生成投票信息(myid,ZXID)。因为是运行期间,此时每个服务器上的ZXID可能不同,这里假设Server1的ZXID为123,Server3的ZXID为122。在第一轮投票中,Server1和Server3都会投自己,即分别产生投票(1,123)和(3,122),然后各自将这个投票发给集群中所有机器。

3.接收来自各个服务器的投票

4.处理投票

5.统计投票

6.改变服务器状态
ZooKeeper Leader选举_第2张图片

2.Leader选举的实现细节

  在ZooKeeper中,提供了三种Leader选举算法,分别是LeaderElection、UDP版本的FastLeaderElection和TCP版本的FastLeaderElection,可以通过在配置文件zoo.cfg中使用electionAlg属性来指定,分别使用数组0,1,2,3来表示。0代表LeaderElection;1代表UDP版本的FastLeaderElection,并且是非授权模式;2也代表UDP版本的FastLeaderElection,使用授权模式;3代表TCP版本的FastLeaderElection。从3.4.0版本开始,ZooKeeper废弃了0、1、2这三种选举算法,只保留了TCP版本的FastLeaderElection选举算法。

  下面看看ZooKeeper中对TCP版本的FastLeaderElection的实现。

服务器状态

  为了能够清楚地对ZooKeeper集群中每台机器的状态进行标识,在org.apache.zookeeper.server.quorum.QuorumPeer.ServerState类中列举了4种服务器状态,分别是:LOOKING、FOLLOWING、LEADING和OBSERVING。
LOOKING: 寻找Leader状态
FOLLOWING: 跟随者状态,表明当前服务器角色是Follower
LEADING: 领导者状态,表明当前服务器角色是Leader
OBSERVING: 观察者状态,表明当前服务器角色是Observe

投票数据结构

  Leader选举过程是通过投票来实现的,同时每个投票包含两个最基本的信息:所推举服务器的SID和ZXID。下图是投票Vote数据结构
ZooKeeper Leader选举_第3张图片
 下表是对Vote数据结构中各个属性的说明:

属性 说明
id 被推举的Leader的SID值
zxid 被推举的Leader的事务ID
electionEpoch 逻辑时钟,用来判断多个投票是否在同一轮选举周期中
peerEpoch 被推举的Leader的epoch
state 当前服务器的状态

QuorumCnxManager:网络I/O

  ClientCnxn是ZooKeeper客户端中用于处理网络I/O的一个管理器,在Leader选举的过程中也有类型的角色,那就是QuorumCnxManager。每台服务器启动的时候,都会启动一个QuorumCnxManager,负责各台服务器之间的底层Leader选举过程中的网络通信。

  在uorumCnxManager这个类内部维护了一系列的队列,用于保存接收到的、待发送的消息,以及消息的发送器。这些队列按SID分组形成队列集合,假设集群中除自身外还有4台机器,那么当前服务器就会为这4台服务器分别创建一个发送队列,互不干扰。

recvQueue: 消息接收队列,用于存放那些从其他服务器接收到的消息
queueSendMap: 消息发送队列,用于保存那些待发送的消息
senderWorkerMa: 发送器集合,负责消息的发送
lastMessageSent: 最近发送过的消息

建立连接

  QuorumCnxManager在启动的时候,会创建一个ServerSocket来监听Leader选举的通信端口,默认为3888。开启端口监听后,ZooKeeper就能够不断地接收到来自其他服务器的“创建连接”请求,在接收到其他服务器的TCP连接请求时,会交由receiveConnection函数来处理。为避免两台机器之间重复地创建TCP连接,ZooKeeper设计了一种建立TCP连接的规则:只允许SID大的服务器主动和其他服务器建立连接,否则断开连接。
  在ReceiveConnection函数中,服务器通过对比自己和远程服务器的SID值,来判断是否接受连接请求。如果当前服务器发现自己的SID值更大,会断开当前连接,然后自己主动去和远程服务器建立连接。
  一旦建立起连接,就会根据远程服务器的SID来创建相应的消息发送器SendWorker和消息接收器RecvWorker,并启动它们。

消息接收与发送

  消息接收由消息接收器RecvWorker来负责,每个RecvWorker需要不断地从这个TCP连接中读取消息,并将其保存到recvQueue队列中。
  消息的发送由消息发送器SendWorker负责,每个SendWorker需要不断地从对应的消息发送队列中获取出一个消息来发送,同时将这个消息放入lastMessageSent中来作为最近发送过的消息。当ZooKeeper发现针对当前远程服务器的消息发送队列为空时,就需要从lastMessageSent中取出一个最近发送过的消息来进行再次发送。

选票管理

  下图为选票管理过程中相关组件之间的协作:
ZooKeeper Leader选举_第4张图片
Leader选举算法实现的流程

  当ZooKeeper服务器检测到当前服务器状态变成LOOKING时,就会触发Leader选举,即调用lookForLeader方法来进行Leader选举。

1.自增选举轮次

  在FastLeaderElection实现中,有一个logicalclock属性,用于标识当前Leader的选举轮次,ZooKeeper规定了所有有效的投票都必须在同一轮次中。ZooKeeper在开始新一轮的投票时,会首先对logicalclock进行自增操作。

2.初始化选票

  在开始新一轮的选票之前,每个服务器都会首先初始化自己的选票,也就是对Vote数据结构的属性进行初始化。

3.发送初始化选票

  在完成选票的初始化之后,服务器就会发起第一次投票。ZooKeeper会将刚刚初始化好的选票放入sendqueue队列中,由发送器WorkerSender负责发送出去。

4.接收外部选票

  每台服务器不断地从recvqueue队列中获取外部投票

5.判断选举轮次

  在处理外部投票的时候,会根据选举轮次来进行不同的处理。

外部投票的选举轮次大于内部投票
  此时服务器会,立即更新自己的选举轮次,清空所有已经收到的投票,然后使用初始化的投票来进行PK以确定是否变更内部投票,最终再将内部投票发送出去。

外部投票的选举轮次小于内部投票
  此时会直接忽略该外部投票,不做任何处理,并返回步骤4

外部投票的选举轮次和内部投票一致
  此时可以开始进行选票PK

6.选票PK

  依次进行判断:
  (1)如果外部投票中被推举的Leader服务器的选举轮次大于内部投票,那么就需要进行投票变更。
  (2)如果选举轮次一致的话,那么就对比两者的ZXID。如果外部投票的ZXID大于内部投票,那么就需要进行投票变更。
  (3)如果两者的ZXID一致,就对比两者的SID。如果外部投票的SID大于内部投票,就需要进行投票变更。

7.变更投票

  通过选票PK后,如果确定了外部投票优于内部投票,就进行投票变更,使用外部投票的选票信息来覆盖内部投票。待变更完成后,将这个变更后的内部投票发送出去。

8.选票归档

  无论是否进行了投票变更,都会将刚刚收到的那份外部投票放入“选票集合”recvset中进行归档。recvset用于记录当前服务器在本轮次的Leader选举中收到的所有外部投票——按照服务器对应的SID来区分。

9.统计投票

  完成了选票归档之后,就可以开始进行统计投票了。如果确定已经有过半的服务器认可了该内部投票,则终止投票,否则返回步骤4.

10.更新服务器状态

  统计投票后,如果已经确定可以终止投票,那么就开始更新服务器状态。服务器首先判断选出来的Leader是否是自己,如果是自己,就将自己状态更新为LEADING。如果不是,就要根据具体情况确定自己是FOLLOWING还是OBSERVING。

你可能感兴趣的:(Zookeeper,zookeeper,分布式,大数据)