1.领导者选举
选举的时机:1.集群启动的时候
选举到leader之前,客户端是无法连接的(会被一直断掉),客户端有重试的机制
zxid是日志文件每增加一个就++,所以zxid比较大代表当前节点的数据比较新
每一个节点内部都有一个属性,记录当前节点选谁当leader,另外内部有一个map被当成投票箱使用(内部存的是被选举的节点myid和zxid),节点1如果最先启动,它选的是自己:
这个时候它的选票发不出去,因为其他节点都还没启动,然后节点2启动,也投给了自己
然后把选票发给节点1,节点1接到选票后与自己投票箱里的进行pk(不管谁先同步,都需要符合pk原则)
比较原则:先比较zxid,zxid大的胜,如果zxid相同,则比较myid,myid大的胜
所以节点1的投票箱如下:
投票箱里两个一样的(2,100)第一个是节点1改票改成的,第二个是节点2的选票同步过来的
这个时候它把自己的选票(第一个(2,100))同步给其他节点(目前只有节点2),节点2收到他的选票,发现一样的,不用改票,但是得加到自己的投票箱里,节点2投票箱里的结果如下:
这个时候3个节点中有两个节点已经选了节点2,已经过半,这个时候leader已经选举成功
这个时候如果节点3启动了,zk1和zk2已经确定了leader是谁,直接同步给它就行了,不用重新选举,但是如果他的数据更加的新(zxid更大)就需要回退,不过旧的话就需要更新,总之会保持跟Leader节点数据同步
取得过半选票的源码
2.两阶段提交(2PC)
读请求是根据本机内存里的值进行查询直接返回,不管是Leader还是follower
写请求就不一样了:
1.如果连的是leader节点
(1)leader几点发起两阶段提交里面的第一步预提交:持久化请求,生成日志
(2)第二阶段的提交
(2.1)把生成的日志信息发送给follower节点,follower节点接收到日志后持久化日志,持久化成功之后返回ack
(2.2)接收到超过一半的节点的ack之后进行下一步
(2.3)真正的提交过程
(2.3.1)更新自己这台服务器的内存里面的数据
(2.3.2)发送commit请求命令告诉其他的从节点进行对应的提交
commit(zxid)是一个异步提交的过程
加到队列里
2.如果连的是follower节点,把写请求转发给leader
3.两阶段提交中的异常情况
3.客户端怎么操作?怎么保证数据一致性
客户端连接leader节点进行写:
1.生成日志,zxid++ 2. 预提交,发送日志给follwer日志,follower节点收到日志后持久化日志并发送给leader节点ack 3.等着接受ack(收到的ack加上自己超过一半就行了)4.本地提交(更新自己内存数据) 5.提交(发送给commit命令给其他的follower节点,follower收到命令后更新自己内存里面的数据)
尽可能的保证数据的一致性
commit命令是异步提交,是先加到一个队列里,然后有个专门的处理类来处理队列里的命令,leader节点是不会管follower节点有没有接收到commit命令的,直接更新内存,如果follower节点没有接收到就会有数据一致性的问题,大多数情况下是没有问题的,所以就能保证一致性问题
follower节点增多会提高读的性能,但是因为leader节点接收的ack要过半,也就意味着时间要变长,另外follower节点增多也会导致选举时间增加,这个时候可以创建观察者节点,观察者节点不参加选举也不用接收ack
观察者节点不参与选举,不参与两阶段提交的同步,可以用来读,是否持久化也是可以配置的
ZK中的main loop(QuorumPeer.run里面)
try { /* * Main loop */ while (running) { if (unavailableStartTime == 0) { unavailableStartTime = Time.currentElapsedTime(); } switch (getPeerState()) { case LOOKING: LOG.info("LOOKING"); ServerMetrics.getMetrics().LOOKING_COUNT.add(1); if (Boolean.getBoolean("readonlymode.enabled")) { LOG.info("Attempting to start ReadOnlyZooKeeperServer"); // Create read-only server but don't start it immediately final ReadOnlyZooKeeperServer roZk = new ReadOnlyZooKeeperServer(logFactory, this, this.zkDb); // Instead of starting roZk immediately, wait some grace // period before we decide we're partitioned. // // Thread is used here because otherwise it would require // changes in each of election strategy classes which is // unnecessary code coupling. Thread roZkMgr = new Thread() { public void run() { try { // lower-bound grace period to 2 secs sleep(Math.max(2000, tickTime)); if (ServerState.LOOKING.equals(getPeerState())) { roZk.startup(); } } catch (InterruptedException e) { LOG.info("Interrupted while attempting to start ReadOnlyZooKeeperServer, not started"); } catch (Exception e) { LOG.error("FAILED to start ReadOnlyZooKeeperServer", e); } } }; try { roZkMgr.start(); reconfigFlagClear(); if (shuttingDownLE) { shuttingDownLE = false; startLeaderElection(); } setCurrentVote(makeLEStrategy().lookForLeader()); } catch (Exception e) { LOG.warn("Unexpected exception", e); setPeerState(ServerState.LOOKING); } finally { // If the thread is in the the grace period, interrupt // to come out of waiting. roZkMgr.interrupt(); roZk.shutdown(); } } else { try { reconfigFlagClear(); if (shuttingDownLE) { shuttingDownLE = false; startLeaderElection(); } 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); updateServerState(); // Add delay jitter before we switch to LOOKING // state to reduce the load of ObserverMaster if (isRunning()) { Observer.waitForObserverElectionDelay(); } } 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); updateServerState(); } 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); } updateServerState(); } break; } } } finally { LOG.warn("QuorumPeer main thread exited"); MBeanRegistry instance = MBeanRegistry.getInstance(); instance.unregister(jmxQuorumBean); instance.unregister(jmxLocalPeerBean); for (RemotePeerBean remotePeerBean : jmxRemotePeerBean.values()) { instance.unregister(remotePeerBean); } jmxQuorumBean = null; jmxLocalPeerBean = null; jmxRemotePeerBean = null; }