原文出处:http://rdc.taobao.com/blog/cs/?p=162算法
本文主要介绍zookeeper中zookeeper Server leader的选举,zookeeper在选举leader的时候采用了paxos算法(主要是fast paxos),这里主要介绍其中两种:LeaderElection 和FastLeaderElection.网络
在zookeeper中,一个zookeeper集群有多少个Server是固定,每一个Server用于选举的IP和PORT都在配置文件中数据结构
每个Server都有一个数字编号,并且是惟一的,咱们根据配置文件中的配置来对每个Server进行编号,这一步在部署时须要人工去作,须要在存储数据文件的目录中建立一个文件叫myid的文件,并写入本身的编号,这个编号在处理我提交的value相同颇有用spa
得到n/2 + 1个Server赞成(这里意思是n/2 + 1个Server要赞成拥有zxid是全部Server最大的哪一个Server)线程
zookeeper中选举主要是采用UDP,也一种实现是采用TCP,在这里介绍的两种实现采用的是UDPserver
LOOKING 初始化状态对象
LEADING 领导者状态blog
FOLLOWING 跟随者状态排序
zookeeper中每个Server都有一个ID,这个ID是不重复的,并且按大小排序,若是遇到这样的状况时,zookeeper就推荐ID最大的哪一个Server做为Leader队列
Leader定时向Fllower发ping消息,Fllower定时向Leader发ping消息,当发现Leader没法ping通时,就改变本身的状态(LOOKING),发起新的一轮选举
zookeeer Server: zookeeper中一个Server,如下简称Server
zxid(zookeeper transtion id): zookeeper 事务id,他是选举过程当中可否成为leader的关键因素,它决定当前Server要将本身这一票投给谁(也就是我在选举过程当中的value,这只是其中一个,还有id)
myid/id(zookeeper server id): zookeeper server id ,他也是可否成为leader的一个因素
epoch/logicalclock:他主要用于描述leader是否已经改变,每个Server中启动都会有一个epoch,初始值为0,当 开始新的一次选举时epoch加1,选举完成时 epoch加1。
tag/sequencer:消息编号
xid:随机生成的一个数字,跟epoch功能相同
Client Proposer Acceptor Learner | | | | | | | X-------->| | | | | | Request | X--------->|->|->| | | Prepare(N)//向全部Server提议 | |<---------X--X--X | | Promise(N,{Va,Vb,Vc})//向提议人回复是否接受提议(若是不接受回到上一步) | X--------->|->|->| | | Accept!(N,Vn)//向全部人发送接受提议消息 | |<---------X--X--X------>|->| Accepted(N,Vn)//向提议人回复本身已经接受提议) |<---------------------------------X--X Response | | | | | | |
没有冲突的选举过程
Client Leader Acceptor Learner | | | | | | | | | X--------->|->|->|->| | | Any(N,I,Recovery) | | | | | | | | X------------------->|->|->|->| | | Accept!(N,I,W)//向全部Server提议,全部Server收到消息后,接受提议 | |<---------X--X--X--X------>|->| Accepted(N,I,W)//向提议人发送接受提议的消息 |<------------------------------------X--X Response(W) | | | | | | | |
LeaderElection是Fast paxos最简单的一种实现,每一个Server启动之后都询问其它的Server它要投票给谁,收到全部Server回复之后,就计算出zxid最大的哪 个Server,并将这个Server相关信息设置成下一次要投票的Server
每一个Server都有一个response线程和选举线程,咱们先看一下每一个线程是作一些什么事情
它主要功能是被动的接受对方法的请求,并根据当前本身的状态做出相应的回复,每次回复都有本身的Id,以及xid,咱们根据他的状态来看一看他都回复了哪些内容
LOOKING状态:
本身要推荐的Server相关信息(id,zxid)
LEADING状态
myid,上一次推荐的Server的id
FLLOWING状态:
当前Leader的id,以及上一次处理的事务ID(zxid)
选举线程由当前Server发起选举的线程担任,他主要的功能对投票结果进行统计,并选出推荐的Server。选举线程首先向全部Server发起 一次询问(包括本身),被询问方,根据本身当前的状态做相应的回复,选举线程收到回复后,验证是不是本身发起的询问(验证 xid是否一致),而后获取对方的id(myid),并存储到当前询问对象列表中,最后获取对方提议的leader相关信息(id,zxid),并将这些 信息存储到当次选举的投票记录表中,当向全部Server都询问完之后,对统计结果进行筛选并进行统计,计算出当次询问后获胜的是哪个 Server,并将当前zxid最大的Server设置为当前Server要推荐的Server(有多是本身,也有能够是其它的Server,根据投票 结果而定,可是每个Server在第一次投票时都会投本身),若是此时获胜的Server得到n/2 + 1的Server票数, 设置当前推荐的leader为获胜的Server,将根据获胜的Server相关信息设置本身的状态。每个Server都重复以上流程,直到选出 leader
了解每一个线程的功能之后,咱们来看一看选举过程
当一个Server启动时它都会发起一次选举,此时由选举线程发起相关流程,那么每一个Server都会得到当前zxid最大的哪一个Server是 谁,若是当次最大的Server没有得到n/2+1个票数,那么下一次投票时,他将向zxid最大的Server投票,重复以上流程,最后必定能选举出一 个Leader
只要保证n/2+1个Server存活就没有任何问题,若是少于n/2+1个Server存活就没办法选出Leader
当选举出Leader之后,此时每一个Server应该是什么状态(FLLOWING)都已经肯定,此时因为Leader已经死亡咱们就无论它,其它 的Fllower按正常的流程继续下去,当完成这个流程之后,全部的Fllower都会向Leader发送Ping消息,若是没法ping通,就改变本身 的状态为(FLLOWING ==> LOOKING),发起新的一轮选举
这个过程的处理跟选举过程当中Leader死亡处理方式同样,这里就再也不描述
fastLeaderElection是标准的fast paxos的实现,它首先向全部Server提议本身要成为leader,当其它Server收到提议之后,解决epoch和zxid的冲突,并接受对方的提议,而后向对方发送接受提议完成的消息
本地消息结构:
static public class Notification {
long leader; //所推荐的Server id
long zxid; //所推荐的Server的zxid(zookeeper transtion id)
long epoch; //描述leader是否变化(每个Server启动时都有一个logicalclock,初始值为0)
QuorumPeer.ServerState state; //发送者当前的状态
InetSocketAddress addr; //发送者的ip地址
}
网络消息结构:
static public class ToSend {
int type; //消息类型
long leader; //Server id
long zxid; //Server的zxid
long epoch; //Server的epoch
QuorumPeer.ServerState state; //Server的state
long tag; //消息编号
InetSocketAddress addr;
}
每一个Server都一个接收线程池(3个线程)和一个发送线程池 (3个线程),在没有发起选举时,这两个线程池处于阻塞状态,直到有消息到来时才解除阻塞并处理消息,同时每一个Server都有一个选举线程(能够发起 选举的线程担任);咱们先看一下每一个线程所作的事情,以下:
被动接收消息端(接收线程池)的处理:
notification: 首先检测当前Server上所被推荐的zxid,epoch是否合法(currentServer.epoch <= currentMsg.epoch && (currentMsg.zxid > currentServer.zxid || (currentMsg.zxid == currentServer.zxid && currentMsg.id > currentServer.id))) 若是不合法就用消息中的zxid,epoch,id更新当前Server所被推荐的值,此时将收到的消息转换成Notification消息放入接收队列 中,将向对方发送ack消息
ack: 将消息编号放入ack队列中,检测对方的状态是不是LOOKING状态,若是不是说明此时已经有Leader已经被选出来,将接收到的消息转发成Notification消息放入接收对队列
主动发送消息端(发送线程池)的处理:
notification: 将要发送的消息由Notification消息转换成ToSend消息,而后发送对方,并等待对方的回复,若是在等待结束没有收到对方法回复,重作三次, 若是重作次仍是没有收到对方的回复时检测当前的选举(epoch)是否已经改变,若是没有改变,将消息再次放入发送队列中,一直重复直到有Leader选 出或者收到对方回复为止
ack: 主要将本身相关信息发送给对方
主动发起选举端(选举线程)的处理:
首先本身的epoch 加1,而后生成notification消息,并将消息放入发送队列中,系统中配置有几个Server就生成几条消息,保证每一个Server都能收到此消 息,若是当前Server的状态是LOOKING就一直循环检查接收队列是否有消息,若是有消息,根据消息中对方的状态进行相应的处理。
LOOKING状态:
首先检测消息中epoch是否合法,是否比当前Server的大,若是比较当前Server的epoch大时,更新epoch,检测是消息中的 zxid,id是否比当前推荐的Server大,若是是更新相关值,并新生成notification消息放入发关队列,清空投票统计表; 若是消息小的epoch则什么也不作; 若是相同检测消息中zxid,id是否合法,若是消息中的zxid,id大,那么更新当前Server相关信息,并新生成notification消息放 入发送队列,将收到的消息的IP和投票结果放入统计表中,并计算统计结果,根据结果设置本身相应的状态
LEADING状态:
将收到的消息的IP和投票结果放入统计表中(这里的统计表是独立的),并计算统计结果,根据结果设置本身相应的状态
FOLLOWING状态:
将收到的消息的IP和投票结果放入统计表中(这里的统计表是独立的),并计算统计结果,根据结果设置本身相应的状态
了解每一个线程的功能之后,咱们来看一看选举过程,选举过程跟第一程同样
当一个Server启动时它都会发起一次选举,此时由选举线程发起相关流程,经过将本身的zxid和epoch告诉其它Server,最后每一个 Server都会得zxid值最大的哪一个Server的相关信息,而且在下一次投票时就投zxid值最大的哪一个Server,重复以上流程,最后必定能选 举出一个Leader
只要保证n/2+1个Server存活就没有任何问题,若是少于n/2+1个Server存活就没办法选出Leader
当选举出Leader之后,此时每一个Server应该是什么状态 (FLLOWING)都已经肯定,此时因为Leader已经死亡咱们就无论它,其它的Fllower按正常的流程继续下去,当完成这个流程之后,全部的 Fllower都会向Leader发送Ping消息,若是没法ping通,就改变本身的状态为(FLLOWING ==> LOOKING),发起新的一轮选举
这个过程的处理跟选举过 程中Leader死亡处理方式同样,这里就再也不描述