在zk服务器集群启动过程当中,经QuorumPeerMain中,不光会建立ZooKeeperServer对象,同时会生成QuorumPeer对象,表明了ZooKeeper集群中的一台机器。在整个机器运行期间,负责维护该机器的运行状态,同时会根据状况发起Leader选举。下图是 《从PAXOS到ZOOKEEPER分布式一致性原理与实践》的服务器启动流程算法
QuorumPeer是一个独立的线程,维护着zk机器的状态。服务器
@Override public synchronized void start() { loadDataBase(); cnxnFactory.start(); startLeaderElection(); super.start(); }
本次主要介绍的是选举相关的内容,至于其余操做能够看其余博客。以后的行文都是从startLeaderElection中衍生出来的。网络
SID:服务器ID,用来标示ZooKeeper集群中的机器,每台机器不能重复,和myid的值一直 ZXID:事务ID Vote: 选票,具体的数据结构后面有 Quorum:过半机器数 选举轮次:logicalclock,zk服务器Leader选举的轮次
服务器类型:数据结构
在zk中,引入了Leader、Follwer和Observer三种角色。zk集群中的全部机器经过一个Leader选举过程来选定一台被称为Leader的机器,Leader服务器为客户端提供读和写服务。Follower和Observer都可以提供读服务,惟一的区别在于,Observer机器不参与Leader选举过程,也不参与写操做的过半写成功策略。所以,Observer存在的意义是:在不影响写性能的状况下提高集群的读性能。架构
服务器状态:分布式
+ LOOKING:Leader选举阶段 + FOLLOWING:Follower服务器和Leader保持同步状态 + LEADING:Leader服务器做为主进程领导状态。 + OBSERVING:观察者状态,代表当前服务器是Observer,不参与投票
选举的目的就是选择出合适的Leader机器,由Leader机器决定事务性的Proposal处理过程,实现类两阶段提交协议(具体是ZAB协议)ide
QuorumPeer的职责就是不断地检测当前的zk机器的状态,执行对应的逻辑,简单来讲,就是根据服务所处的不一样状态执行不一样的逻辑。删除了一部分逻辑后,代码以下:性能
@Override public void run() { setName("QuorumPeer" + "[myid=" + getId() + "]" + cnxnFactory.getLocalAddress()); try { while (running) { switch (getPeerState()) { case LOOKING: LOG.info("LOOKING"); try { setBCVote(null); setCurrentVote(makeLEStrategy().lookForLeader()); } catch (Exception e) { LOG.warn("Unexpected exception", e); setPeerState(ServerState.LOOKING); } break; case OBSERVING: try { LOG.info("OBSERVING"); setObserver(makeObserver(logFactory)); observer.observeLeader(); } catch (Exception e) { LOG.warn("Unexpected exception",e ); } finally { observer.shutdown(); setObserver(null); setPeerState(ServerState.LOOKING); } break; case FOLLOWING: try { LOG.info("FOLLOWING"); setFollower(makeFollower(logFactory)); follower.followLeader(); } catch (Exception e) { LOG.warn("Unexpected exception",e); } finally { follower.shutdown(); setFollower(null); setPeerState(ServerState.LOOKING); } break; case LEADING: LOG.info("LEADING"); try { setLeader(makeLeader(logFactory)); leader.lead(); setLeader(null); } catch (Exception e) { LOG.warn("Unexpected exception",e); } finally { if (leader != null) { leader.shutdown("Forcing shutdown"); setLeader(null); } setPeerState(ServerState.LOOKING); } break; } } } finally { LOG.warn("QuorumPeer main thread exited"); } }
当机器处于LOOKING状态时,QuorumPeer会进行选举,可是具体的逻辑并非由QuorumPeer来负责的,总体的投票过程独立出来了,从逻辑执行的角度看,整个过程设计到两个主要的环节:this
而QuorumPeer中默认使用的选举算法是FastLeaderElection,以后的分析也是基于FastLeaderElection而言的。线程
在集群启动的过程当中,QuorumPeer会根据配置实现不一样的选举策略 this.electionAlg = createElectionAlgorithm(electionType);
protected Election createElectionAlgorithm(int electionAlgorithm){ Election le=null; switch (electionAlgorithm) { case 3: QuorumCnxManager qcm = new QuorumCnxManager(this); QuorumCnxManager.Listener listener = qcm.listener; if(listener != null){ listener.start(); le = new FastLeaderElection(this, qcm); } else { LOG.error("Null listener when initializing cnx manager"); } break; default: assert false; } return le; }
若是ClientCnxn是zk客户端中处理IO请求的管理器,QuorumCnxManager是zk集群间负责选举过程当中网络IO的管理器,在每台服务器启动的时候,都会启动一个QuorumCnxManager,用来维持各台服务器之间的网络通讯。
对于每一台zk机器,都须要创建一个TCP的端口监听,在QuorumCnxManager中交给Listener来处理,使用的是Socket的阻塞式IO(默认监听的端口是3888,是在config文件里面设置的)。在两两相互链接的过程当中,为了不两台机器之间重复地建立TCP链接,zk制定了链接的规则:只容许SID打的服务器主动和其余服务器创建链接。实现的方式也比较简单,在receiveConnection中,服务器会对比与本身创建链接的服务器的SID,判断是否接受请求,若是本身的SID更大,那么会断开链接,而后本身主动去和远程服务器创建链接。这段逻辑是由Listener来作的,且Listener独立线程,receivedConnection,创建链接后的示意图:
QuorumCnxManager是链接的管家,具体的TCP链接交给了Listener,可是对于选票的管理,内部还维护了一系列的队列:
new ConcurrentHashMap<Long, ArrayBlockingQueue
>();
按照SID分组,分别为每台机器分配一个单独队列,保证各台机器之间的消息发放互不影响基本的通讯流程以下:
以上内容主要是创建各台zk服务器之间的链接通讯过程,具体的选举策略zk抽象成了
public interface Election { public Vote lookForLeader() throws InterruptedException; public void shutdown(); }
上面说过QuorumPeer检测到当前服务器的状态是LOOKING的时候,就会进行新一轮的选举,经过 setCurrentVote(makeLEStrategy().lookForLeader());
也就是FastLeaderElection的lookForLeader来进行初始选择,实现的方式也很简单,主要的逻辑在FastLeaderElection.lookForLeader
中实现:
基本流程先说明一下:
根据上面的流程,能够大概说明一下FasterLeaderElection肯定选票更优的策略:
以上就是zk的默认选票流程,按照ZAB协议的两种状态分析: