分布式开发必须了解的Zookeeper的Leader选举机制(源码解析)

分布式开发必须知道的Zookeeper知识及其的Leader选举机制(ZAB原子广播协议)

  ZooKeeper是Hadoop下的一个子项目,它是一个针对大型分布式系统的可靠协调系统,提供的功能包括:配置维护、名字服务、分布式同步、组服务等; 它的目标就是封装好复杂易出错的关键服务,将简单易用的接口和性能高效、功能稳定的系统提供给用户。

ZooKeeper系统架构

  下图就是Zookeeper的架构图:

  从上面的架构图中,我们需要了解的主要的信息有:

  ①ZooKeeper分为服务器端(Server)和客户端(Client),客户端可以连接到整个ZooKeeper服务的任意服务器上(Leader除外)。

  ②ZooKeeper 启动时,将从实例中选举一个Leader,Leader 负责处理数据更新等操作,一个更新操作成功的标志是当且仅当大多数Server在内存中成功修改数据(Quorom机制)。每个Server 在内存中存储了一份数据。

  ③Zookeeper是可以集群复制的,集群间通过Zab协议(Zookeeper Atomic Broadcast)来保持数据的一致性;

  ④Zab协议包含两个阶段:Leader Election阶段和Atomic Brodcast阶段。群中将选举出一个Leader,其他的机器则称为Follower,所有的写操作都被传送给Leader,并通过Brodcast将所有的更新告诉给Follower。 当Leader被选举出来,且大多数服务器完成了和leader的状态同步后,Leadder Election 的过程就结束了,就将会进入到Atomic Brodcast的过程。Atomic Brodcast同步Leader和Follower之间的信息,保证Leader和Follower具有形同的系统状态。


Quorom机制简介

  在分布式系统中,冗余数据是保证可靠性的手段,因此冗余数据的一致性维护就非常重要。一般而言,一个写操作必须要对所有的冗余数据都更新完成了,才能称为成功结束。比如一份数据在5台设备上有冗余,因为不知道读数据会落在哪一台设备上,那么一次写操作,必须5台设备都更新完成,写操作才能返回。

  对于写操作比较频繁的系统,这个操作的瓶颈非常大。Quorum算法可以让写操作只要写完3台就返回。剩下的由系统内部缓慢同步完成。而读操作,则需要也至少读3台,才能保证至少可以读到一个最新的数据。


Zookeeper中的四种角色

①Leader:领导者,负责进行投票的发起和决议,更新系统状态。

②Learner:学习者

③Follower(Learner的子类):跟随者,用于接受客户端请求并向客户端返回结结果,在选主过程中参与投票,Follower可以接收Client请求,如果是写请求将转发给Leader来更新系统状态。

④Observer:观察者,可以接收客户端连接,将写请求转发给Leader节点,但是不参与投票过程,只是同步Leader状态,因为Follower增多会导致投票阶段延迟增大,影响性能。Observer的目的是为了扩展系统,提高读取数据。


为什么Zookeeper中的Server数目一般为基数?

  我们知道在Zookeeper中 Leader 选举算法采用了Quorom算法。该算法的核心思想是当多数Server写成功,则任务数据写成功。假设有3个Server,则最多允许一个Server挂掉;如果有4个Server,则同样最多允许一个Server挂掉。既然3个或者4个Server,同样最多允许1个Server挂掉,那么它们的可靠性是一样的,所以选择奇数个ZooKeeper Server即可,这里选择3个Server。


Zookeeper用于Leader选举的算法

①基于UDP的LeaderElection

②基于UDP的FastLeaderElection

③基于UDP和认证的FastLeaderElection

④基于TCP的FastLeaderElection(默认值)


FastLeaderElection机制

  接下来要说的就是Zookeeper的Leader选举机制核心算法FastLeaderElection类。FastLeaderElection实现了Election接口,其需要实现接口中定义的lookForLeader(核心的选举算法入口)方法和shutdown方法FastLeaderElection选举算法是标准的Fast Paxos算法实现,可解决LeaderElection选举算法收敛速度慢的问题。

术语介绍

sid(myid)

  每个Zookeeper服务器,都需要在数据文件夹下创建一个名为myid的文件,该文件包含整个Zookeeper集群唯一的ID(整数)。例如某Zookeeper集群包含三台服务器,hostname分别为zoo1、zoo2和zoo3,其myid分别为1、2和3,则在配置文件中其ID与hostname必须一一对应,如下所示。在该配置文件中,server.后面的数据即为myid(Leader选举时用的sid或者leader)。

server.1=zoo1:2888:3888
server.2=zoo2:2888:3888
server.3=zoo3:2888:3888
复制代码

zxid

  类似于RDBMS中的事务ID,用于标识一次更新操作的Proposal ID。为了保证顺序性,该zkid必须单调递增。因此Zookeeper使用一个64位的数来表示,高32位是Leader的epoch,从1开始,每次选出新的Leader,epoch加一。低32位为该epoch内的序号,每次epoch变化,都将低32位的序号重置。这样保证了zxid的全局递增性。


Zookeeper节点的四种状态

  截图为Zookeeper定义的四种服务器节点状态:

  • LOOKING: 不确定Leader状态。该状态下的服务器认为当前集群中没有Leader,会发起Leader选举。

  • FOLLOWING: 跟随者状态。表明当前服务器角色是Follower,并且它知道Leader是谁。

  • LEADING: 领导者状态。表明当前服务器角色是Leader,它会维护与Follower间的心跳。

  • OBSERVING: 观察者状态。表明当前服务器角色是Observer,与Folower唯一的不同在于不参与选举,也不参与集群写操作时的投票。


FastLeaderElection内部类

FastLeaderElection的内部类的情况如下图:

  • **Notification:**表示收到的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息。
  • **ToSend:**表示发送给其他服务器的选举投票信息(其他服务器发来的选举投票信息),其包含了被选举者的id、zxid、选举周期等信息。
  • Messenger:包含了WorkerReceiverWorkerSender两个内部类。WorkerReceiver实现了Runnable接口,是选票接收器。WorkerSender也实现了Runnable接口,为选票发送器

Notification(收到的投票信息)

  • **leader:**被推选的leader的id。
  • **zxid:**被推选的leader的事务id。
  • **electionEpoch:**推选者的选举周期。
  • **state:**推选者的状态。
  • **sid:**推选者的id。
  • **peerEpoch:**被推选者的选举周期。

ToSend(发送的投票信息)

  • **leader:**被推选的leader的id。
  • **zxid:**被推选的leader的事务id。
  • **electionEpoch:**推选者的选举周期。
  • **state:**推选者的状态。
  • **sid:**推选者的id。
  • **peerEpoch:**被推选者的选举周期。

WorkerSender(选票发送器)

  WorkerSender也实现了Runnable接口,为选票发送器,其会不断地从sendqueue中获取待发送的选票,并将其传递到底层QuorumCnxManager中。

  • 获取选票

  • 发送选票


WorkerReceiver(选票接收器)

  WorkerReceiver实现了Runnable接口,是选票接收器。其会不断地从QuorumCnxManager中获取其他服务器发来的选举消息中。先会从QuorumCnxManager中的pollRecvQueue队列中取出其他服务器发来的选举消息,消息封装在Message数据结构中。然后判断消息中的服务器id是否包含在可以投票的服务器集合中,若不是,则会将本服务器的内部投票发送给该服务器,其流程如下:

  若包含该服务器,则根据消息(Message)解析出投票服务器的投票信息并将其封装为Notification,然后判断当前服务器是否为LOOKING,若为LOOKING,则直接将Notification放入FastLeaderElection的recvqueue。然后判断投票服务器是否为LOOKING状态,并且其选举周期小于当前服务器的逻辑时钟,则将本(当前)服务器的内部投票发送给该服务器,否则,直接忽略掉该投票。其流程如下:

  若本服务器的状态不为LOOKING,则会根据投票服务器中解析的version信息来构造ToSend消息,放入sendqueue,等待发送,起流程如下:


核心函数分析

sendNotifications函数

  其会遍历所有的参与者投票集合,然后将自己的选票信息发送至上述所有的投票者集合,其并非同步发送,而是将ToSend消息放置于sendqueue中,之后由WorkerSender进行发送。

totalOrderPredicate函数

  该函数将接收的投票与自身投票进行PK,查看是否消息中包含的服务器id是否更优,其按照epoch、zxid、id的优先级进行PK。

  • 判断消息里的epoch是不是比当前的大,如果大则消息中id对应的服务器就是leader。
  • 如果epoch相等则判断zxid,如果消息里的zxid大,则消息中id对应的服务器就是leader。
  • 如果前面两个都相等那就比较服务器id,如果大,则其就是leader。

termPredicate函数

  该函数用于判断Leader选举是否结束,即是否有一半以上的服务器选出了相同的Leader,其过程是将收到的选票与当前选票进行对比,选票相同的放入同一个集合,之后判断选票相同的集合是否超过了半数。

checkLeader函数

  该函数检查是否已经完成了Leader的选举,此时Leader的状态应该是LEADING状态。

lookForLeader函数

  该函数就是leader选举的核心方法,代码行数有点多,这里仅分析其中的一部分。

  • 更新逻辑时钟、更新选票、发送选票

  • 获取投票数据、连接服务器

  • 选举Leader

  • LEADING状态处理

  以上就是关于zookeeper的所有基本知识与Leader选举机制的讲解。喜欢的话记得转发额。

  欢迎大家关注我的微信公众号,不定期分享各类面试题、踩坑记录、高阶知识分享。

转载于:https://juejin.im/post/5d00c019e51d4577770e7370

你可能感兴趣的:(分布式开发必须了解的Zookeeper的Leader选举机制(源码解析))