zk集群--集群同步

1.概述

前面一章分析了集群启动阶段的选举过程,运行中重新开始选举和启动时的选举流程也是一致的.
一旦完成选举,通过执行QuorumPeersetPeerState将设置好选举结束后自身的状态。然后,将再次执行QuorumPeerrun的新的一轮循环,

QuorumPeerrun的每一轮循环,先判断自身当前状态:
(1). 自身为LOOKING
则需在本轮循环开启选举,并完成选举。
(2). 自身为FOLLOWING

protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException {
    return new Follower(this, new FollowerZooKeeperServer(logFactory, this, this.zkDb));
}

try {
   LOG.info("FOLLOWING");
   setFollower(makeFollower(logFactory));
   follower.followLeader();
} catch (Exception e) {
    LOG.warn("Unexpected exception", e);
} finally {
    follower.shutdown();
    setFollower(null);
    updateServerState();
}

则应转换为从节点,先通过follower.followLeader();与主同步,再履行起从节点的角色任务。
(3). 自身为LEADING

protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException, X509Exception {
    return new Leader(this, new LeaderZooKeeperServer(logFactory, this, this.zkDb));
}
    
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();
}

则应转换为主节点,先通过leader.lead();完成集群同步后,再履行起主节点的角色任务。

本部分讨论集群同步过程。集群同步是一个完成选举的主和从相互协作最终大家达成一致的过程。

2.集群同步过程最初的两次同步

2.1.利用同步确定新集群的轮次

2.1.1.主节点的leader.lead()

self.setZabState(QuorumPeer.ZabState.DISCOVERY);
self.tick.set(0);
zk.loadData();
leaderStateSummary = new StateSummary(self.getCurrentEpoch(), zk.getLastProcessedZxid());
cnxAcceptor = new LearnerCnxAcceptor();
cnxAcceptor.start();

上述动作里,先是设置ZabStateDISCOVERY。选举尚未结束时ZabStateELECTION来着。
其中zk.loadData();由于在启动节点选举前已经执行过了一次基于快照+redo的数据实体恢复,所以这里啥也不用做。
构建一个StateSummary实例,这个实例反映了节点数据实体对应的轮次,lastZxid信息.
通过cnxAcceptor.start();使得主节点开始作为一个服务端,允许其他集群成员来连接以便执行集群同步及后续的请求处理。
对每个接入的集群成员,在主节点方面将通过accept得到通信套接字,并分配一个LearnerHandler来维护和集群成员的通信。

LearnerHandler fh = new LearnerHandler(socket, is, Leader.this);
fh.start();

fh.start();将开启一个线程,在此线程中将接收来自连接对端的包,并对其执行处理。
接下来,主节点执行的是:

long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());

主节点执行getEpochToPropose的目的是为了获得新产生的集群的轮次要设置为何值?
此处将产生同步等待,直到算上主节点自身,有半数以上集群成员连接到的主节点并执行了getEpochToPropose才能获得继续。继续时,将主节点的acceptedEpoch设置为所有执行getEpochToPropose的节点中acceptedEpoch最大值+1
对主节点,此处的同步等待超时下,将引发主节点停止。并设置自身状态为LOOKING。这样,在QuorumPeerrun的新一轮循环里,将重新开始选举过程。

2.1.2.从节点的follower.followLeader()

self.setZabState(QuorumPeer.ZabState.DISCOVERY);
QuorumServer leaderServer = findLeader();
connectToLeader(leaderServer.addr, leaderServer.hostname);
connectionTime = System.currentTimeMillis();                

上述动作里,先是设置ZabStateDISCOVERY。选举过程中ZabStateELECTION来着。
然后,执行findLeader找到主节点。借助自身的投票和集群全局配置很容易定位出来。
connectToLeader将同步方式发起到服务端连接。
若指定时间或指定次数内连接未建立,将引发从节点停止。并设置自身状态为LOOKING。这样,在QuorumPeerrun的新一轮循环里,将重新开始选举过程。若连接成功,在异步发包下会启动一个线程用于异步发包。
从节点接下来执行:

long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO);

这里面从节点先向主节点发一个包

long lastLoggedZxid = self.getLastLoggedZxid();
QuorumPacket qp = new QuorumPacket();
qp.setType(Leader.FOLLOWERINFO);// 包类型
qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));// 包里的zxid
LearnerInfo li = new LearnerInfo(self.getId(), 0x10000, self.getQuorumVerifier().getVersion());
ByteArrayOutputStream bsid = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
boa.writeRecord(li, "LearnerInfo");
qp.setData(bsid.toByteArray());// 包的数据体是一个LearnerInfo实例。包含从节点sid,0x10000,集群全局信息。
writePacket(qp, true);

现在从节点等待主节点的回复。

2.1.3.主节点实现从节点接入及收包处理
主节点会为每个接入到其的从节点,分配一个LearnerHandler实例,此实例将占据一个独立的线程来执行对应从节点的数据包收取和处理。

LeaderHandlerrun一开始执行:

learnerMaster.addLearnerHandler(this);

这样将向leaderlearners集合中加入此LearnerHandler实例。
然后将开始收取首个包。并对首个包进行合法性检测(对从节点首个包类型必须是Leader.FOLLOWERINFO)。
若检测失败,服务端方面会停止线程,关闭被动连接,从leaderlearners,forwardingFollowers,observingLearners集合移除此LearnerHandler实例。客户端方面则将引发从节点停止,并设置自身状态为LOOKING。这样,在QuorumPeerrun的新一轮循环里,将重新开始选举过程。后续分析过程失败过程处理类似。

检测通过后,对首个包进行解析

QuorumPacket qp = new QuorumPacket();
// 反向序列化获得包
ia.readRecord(qp, "packet");
byte[] learnerInfoData = qp.getData();// 获得包中数据体
ByteBuffer bbsid = ByteBuffer.wrap(learnerInfoData);
this.sid = bbsid.getLong();
this.version = bbsid.getInt(); // protocolVersion--0x10000
String followerInfo = learnerMaster.getPeerInfo(this.sid);
// 这样将获得从节点首个包里的zxid,从中提取出epoch
long lastAcceptedEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
long peerLastZxid;
StateSummary ss = null;
long zxid = qp.getZxid();
long newEpoch = learnerMaster.getEpochToPropose(this.getSid(), lastAcceptedEpoch);

2.1.4. 集群同步阶段确定新集群的epoch
通过2.1.12.1.3我们知道。
选举结束后,主节点作为服务端允许集群从节点的接入。
然后,集群主节点将阻塞等待。
对每个结束选举的从节点,将连接主节点,成功后,发出首个Leader.FOLLOWERINFO包。然后等待回复。
主节点为每个接入的从节点分配LearnerHandler实例,并启动线程,处理收包和收包处理。
每个LearnerHandler的线程收取首个包Leader.FOLLOWERINFO后,通过learnerMaster.getEpochToPropose陷入与主节点一样的阻塞等待。

当算上主节点自身有半数以上成员陷入上述阻塞等待后,将基于所有等待成员中acceptedEpoch最大值+1作为新集群的epoch设置到leaderepoch。若上述过程某个成员连接上出现错误或等待超时,则将执行连接断开。此成员将停止其角色。重新变为LOOKING状态,重新进入选举过程。主节点自己等待超时或出错,则主节点停止作为主,重新变为LOOKING状态,并进入选举流程。主节点停止时会引发每个连到主节点的从节点也停止,并重新变为LOOKING状态。

2.2.利用同步再次确认选出的主是所有参与选举成员中状态最靠前的

我们分别针对主节点,从节点分析完成getEpochToPropose等待后的处理流程
2.2.1.对主节点

zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
synchronized (this) {
    lastProposed = zk.getZxid();
}
newLeaderProposal.packet = new QuorumPacket(NEWLEADER, zk.getZxid(), null, null);
newLeaderProposal.addQuorumVerifier(self.getQuorumVerifier());
waitForEpochAck(self.getId(), leaderStateSummary);

主节点,后续执行waitForEpochAck
主节点执行此步骤会阻塞,在waitForEpochAck中收集到半数以上的成员及其StateSummary后,且满足:
(1). 完成收集未引发超时
(2). 不存在从节点StateSummary领先主节点的情况。
上述两个条件,主节点将继续。并设置electionFinishedtrue
若某个条件不满足,主节点将停止,并断开与连到其的从的连接,这样将引发从节点也停止。最终主,从均停止。并发起下一轮选举。

2.2.2.对从节点
分析LeaderHandlerrun中后续动作

long newLeaderZxid = ZxidUtils.makeZxid(newEpoch, 0);
byte[] ver = new byte[4];
ByteBuffer.wrap(ver).putInt(0x10000);
QuorumPacket newEpochPacket = new QuorumPacket(Leader.LEADERINFO, newLeaderZxid, ver, null);
oa.writeRecord(newEpochPacket, "packet");
messageTracker.trackSent(Leader.LEADERINFO);
bufferedOutput.flush();// 立即发送
QuorumPacket ackEpochPacket = new QuorumPacket();
ia.readRecord(ackEpochPacket, "packet");

即给从节点回复一个Leader.LEADERINFO包,其中包含基于新集群epoch得到的zxid,版本信息(0x10000)。

从节点这边:

readPacket(qp);
final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());// 取得回复包里的zxid
leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();// 取得ver
byte[] epochBytes = new byte[4];
final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
wrappedEpochBytes.putInt((int) self.getCurrentEpoch());// 放入自身currentEpoch
self.setAcceptedEpoch(newEpoch);// 用回复包里zxid导出的epoch设置自身acceptedEpoch
QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
writePacket(ackNewEpoch, true);// 向主节点发包
return ZxidUtils.makeZxid(newEpoch, 0);

即首先采用新集群的epoch设置自身的acceptedEpoch,再构造一个反映自身数据实体轮次,lastZxidLeader.ACKEPOCH回复包执行回复.

继续分析LeaderHandlerrun

QuorumPacket ackEpochPacket = new QuorumPacket();
ia.readRecord(ackEpochPacket, "packet");// 等待从节点的回复
messageTracker.trackReceived(ackEpochPacket.getType());
ByteBuffer bbepoch = ByteBuffer.wrap(ackEpochPacket.getData());// 
ss = new StateSummary(bbepoch.getInt(), ackEpochPacket.getZxid());// 从节点的currentEpoch,从节点的zxid
learnerMaster.waitForEpochAck(this.getSid(), ss);

waitForEpochAck这个执行中将陷入等待。直到满足:
(1). 算上主自身有半数以上成员均针对主节点执行了waitForEpochAck
(2). 收集到半数以上waitForEpochAck,未引发等待超时。
(3). 未出现某个从节点的ss领先于主节点的ss
上述三者满足时,等待者和主节点继续运行。

LearnerHandler中执行waitForEpochAck超时,将引发对应的从节点停止,并重新进入下一轮选举。
主节点完成waitForEpochAck这一同步后,将electionFinished设置为true

到目前为止,我们分析了成员完成选举确认自身身份后,经历过了两个由主节点这边主导的阻塞等待。
第一个阻塞等待是getEpochToPropose,通过此等待基于所有连主成员中acceptedEpoch最大值+1作为新集群epoch
此阶段,对从节点等待超时或出错,会使得其停止作为从,并重新进入选举。
此节点,对主节点等待超时或出错,会使得其自身和所有连到其的从均停止自身角色,并重新进入选举。
成功则,使得我们获得新集群的epoch,及基于epochzxid

第二个阻塞等待是waitForEpochAck,通过此等待主要是再一次确认主节点的合法性。即主节点自身的进度确实是参与选举的各个节点中最靠前的。
比如一个1,2,3三个成员的集群。
(1). 1,2,3启动,并运行很长时间。
(2). 全部停止。
(3). 手动删除1,2的快照,redo
(4). 启动1,2。使得1,2各自完成选举,预期2为主,1为从。
(5). 启动3,假设此时1,2各自处于waitForEpochAck3选举结束,依据收到的Notification确定2为主。并连接2,也进入waitForEpochAck。此时就会产生某个从节点的状态领先主节点的情况。这时,就要使得主和从全部停止角色,并再次选举。再次选举,将选择3为主。此时,waitForEpochAck将获得通过。一旦主和从的waitForEpochAck结束。此时即使有新的从连到主,且领先于主,也不会被考虑了。

下面,将进入真正的集群同步阶段。

3. 真正的集群同步

我们继续分别从主节点,从节点角度分析其后续流程。
3.1. 主节点

self.setCurrentEpoch(epoch);
self.setLeaderAddressAndId(self.getQuorumAddress(), self.getId());
self.setZabState(QuorumPeer.ZabState.SYNCHRONIZATION);
waitForNewLeaderAck(self.getId(), zk.getZxid());

只有完成前述两个阶段的同步,且未出错。主节点才将新集群的epoch设置到其currentEpoch中。
注意,对主节点和从节点,只要其完成了前述中第一个同步,就会各自设置自己的acceptedEpoch
所以,accepted反映的是集群完成选举且形成新的集群轮次的次数。(一个完成选举,形成集群轮次的集群并不一定可以成为对外服务的集群。必须继续完成前述第二个同步,并完成这里要讨论的实际的集群同步才可以。)

主节点执行waitForNewLeaderAck是我们遇到的新集群形成中主节点主导的第三次同步。
为了理解这个同步的意义,我们需要先分析,从节点方面在第二个同步完成后的后续动作.

3.2.从节点
先分析LearnerHandlerrun中后续处理
这部分处理可以描述为:
(1). 从节点与主节点数据实体同步.
这一步是同步的关键所在,按从节点中数据实体lastZxid和主节点中数据lastZxid的情况.
有三种同步类型:
a. 快照同步
b. 差异化同步
c. 截断同步

我们下面分别对其分析.
对每种同步类型,我们分别分析此类型下的同步模式,然后给出一种会采用此种同步类型的实际场景.

a. 快照同步
a.1.同步模式
先发送一个new QuorumPacket(Leader.SNAP, zxidToSend, null, null)这样的包,其中zxidToSend是主节点数据实体的lastZxid
在此包的数据体中发送主节点数据实体序列化后的内容.
a.2.何时采用
若从节点数据实体的lastZxid落后于主节点数据实体的lastZxid,且落后的较多时,采用快照同步.

b.差异同步
b.1.同步模式
先发送一个new QuorumPacket(Leader.DIFF, lastZxid, null, null);这样的包,其中lastZxid是主节点数据实体的lastZxid
然后针对主节点领先于从节点的每个redo项,先发送一个此redo项对应的QuorumPacket,再发送一个new QuorumPacket(Leader.COMMIT, packetZxid, null, null);
b.2.何时采用
若从节点数据实体的lastZxid落后于主节点数据实体的lastZxid,但落后的不多时,采用差异同步.

c.截断同步
c.1.同步模式
发送一个new QuorumPacket(Leader.TRUNC, lastZxid, null, null);这样的包,其中lastZxid是主节点数据实体的lastZxid
c.2.何时采用
若从节点数据实体的lastZxid领先于主节点数据实体的lastZxid,采用截断同步.
会出现此场景的一个实例:
假设1,2,3是集群三个节点.
时刻一,1,2,3分别启动构成集群并处理10个请求.
时刻二,1,2,3分别停止.
时刻三,手动清理1,2
时刻四,启动1,2构成集群并处理个请求.
时刻五,启动,并加入集群,在的集群同步阶段即触发此场景.

(2). 将主节点中已经提交但尚未落入数据实体的各个请求,按发送一个此请求对应的QuorumPacket,再发送一个new QuorumPacket(Leader.COMMIT, packetZxid, null, null);的方式实现请求在从节点上的提交.
(3). 将主节点中提议但尚未提交的请求中领先于从节点的提议发送给从节点.
(3). 给从节点发送Leader.NEWLEADER包.

然后执行:

qp = new QuorumPacket();
ia.readRecord(qp, "packet");
learnerMaster.waitForNewLeaderAck(getSid(), qp.getZxid());

我们接着分析从节点对收到的这些包的处理。

3.3.从节点对集群同步包的处理
从节点在registerWithLeader后执行的流程为:

// 取得新集群epoch
long newEpoch = ZxidUtils.getEpochFromZxid(newEpochZxid);
if (newEpoch < self.getAcceptedEpoch()) {
    throw new IOException("Error: Epoch of leader is lower");
}
long startTime = Time.currentElapsedTime();
self.setLeaderAddressAndId(leaderServer.addr, leaderServer.getId());
self.setZabState(QuorumPeer.ZabState.SYNCHRONIZATION);
syncWithLeader(newEpochZxid);
self.setZabState(QuorumPeer.ZabState.BROADCAST);

从节点点在syncWithLeader里,会具体处理来自主节点的用于集群同步的包,实现和主节点的同步.
类似前面分析过程,我们以下分别分析采用DIFF,TRUNC,SNAP三种同步方式下,从节点在syncWithLeader中所作的针对性处理.
(1). DIFF同步
首先DIFF下,主节点给从节点发的包按按顺序分为以下几类:
a. 首个DIFF
b. 后续若干和DIFF配套的PROPOSAL+COMMIT
c. 后续若干主节点中已经提交但尚未落地到数据实体的PROPOSAL+COMMIT
d. 主节点中处于提议阶段的若干PROPOSAL
e. 标志同步最后一个包的NEWLEADER

从节点的处理策略是:
a. 用packetsCommitted收集上述b,c类型里的每个PROPOSAL
b. 用packetsNotCommitted收集上述b,c,d类型里的每个PROPOSAL
这里值得注意的是b,c类型的包,既出现在packetsCommitted容器,又出现在packetsNotCommitted容器.
c. 针对NEWLEADER包的处理为:

self.setCurrentEpoch(newEpoch);// 只有同步结束,才能设置自身currentEpoch
writeToTxnLog = true;
isPreZAB1_0 = false;
sock.setSoTimeout(self.tickTime * self.syncLimit);
self.setSyncMode(QuorumPeer.SyncMode.NONE);
zk.startupWithoutServing();
if (zk instanceof FollowerZooKeeperServer) {
    FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
    for (PacketInFlight p : packetsNotCommitted) {
        fzk.logRequest(p.hdr, p.rec, p.digest);
    }
    packetsNotCommitted.clear();
}
writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);

主要的处理是将packetsNotCommitted中的每个包执行持久化到日志文件的处理,且给主节点发送Leader.ACK

(2). TRUNC同步
首先TRUNC下,主节点给从节点发的包按按顺序分为以下几类:
a. 首个TRUNC
b. 后续若干主节点中已经提交但尚未落地到数据实体的PROPOSAL+COMMIT
c. 主节点中处于提议阶段的若干PROPOSAL
d. 标志同步最后一个包的NEWLEADER

从节点的处理策略是:
a. 针对TRUNC包的处理为:
按包中lastZxid,截断自身redo日志,使得自身领先部分被移除.
重新基于redo和快照加载数据实体,并设置其lastZxid.这样就实现了数据实体的同步效果.
b. 针对上述b类型的PROPOSAL+COMMIT每对包处理策略是:
PROPOSAL,将其放入packetsNotCommitted
COMMIT,将其从packetsNotCommitted移除,并直接在数据实体上迭代这个包.
c. 针对上述c类型的PROPOSAL包处理策略是:
PROPOSAL,将其放入packetsNotCommitted
d. 针对dNEWLEADER包处理策略是:

zk.takeSnapshot(syncSnapshot);
self.setCurrentEpoch(newEpoch);
writeToTxnLog = true;
isPreZAB1_0 = false;
sock.setSoTimeout(self.tickTime * self.syncLimit);
self.setSyncMode(QuorumPeer.SyncMode.NONE);
zk.startupWithoutServing();
if (zk instanceof FollowerZooKeeperServer) {
    FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
    for (PacketInFlight p : packetsNotCommitted) {
        fzk.logRequest(p.hdr, p.rec, p.digest);
    }
    packetsNotCommitted.clear();
}
writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);

主要的处理为:为数据实体生成快照来实现已经处理部分持久化存储,将提议部分序列化到日志文件,给主节点发送ACK

(3). SNAP同步
首先SNAP下,主节点给从节点发的包按按顺序分为以下几类:
a. 首个SNAP
b. 后续若干主节点中已经提交但尚未落地到数据实体的PROPOSAL+COMMIT
c. 主节点中处于提议阶段的若干PROPOSAL
d. 标志同步最后一个包的NEWLEADER

从节点的处理策略是:
a. 针对SNAP包的处理为:
对包的数据部分执行反向序列化来重新构建自身的数据实体,从而达到同步效果.
b. 针对上述b类型的PROPOSAL+COMMIT每对包处理策略是:
PROPOSAL,将其放入packetsNotCommitted
COMMIT,将其从packetsNotCommitted移除,并直接在数据实体上迭代这个包.
c. 针对上述c类型的PROPOSAL包处理策略是:
PROPOSAL,将其放入packetsNotCommitted
d. 针对dNEWLEADER包处理策略是:

zk.takeSnapshot(syncSnapshot);
self.setCurrentEpoch(newEpoch);
writeToTxnLog = true;
isPreZAB1_0 = false;
sock.setSoTimeout(self.tickTime * self.syncLimit);
self.setSyncMode(QuorumPeer.SyncMode.NONE);
zk.startupWithoutServing();
if (zk instanceof FollowerZooKeeperServer) {
    FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
    for (PacketInFlight p : packetsNotCommitted) {
        fzk.logRequest(p.hdr, p.rec, p.digest);
    }
    packetsNotCommitted.clear();
}
writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);

主要的处理为:为数据实体生成快照来实现已经处理部分持久化存储,将提议部分序列化到日志文件,给主节点发送ACK

3.4.主节点这边收到Leader.ACK时处理
LearnerHandlerrun中同步后的处理:

qp = new QuorumPacket();
ia.readRecord(qp, "packet");
LOG.debug("Received NEWLEADER-ACK message from {}", sid);
learnerMaster.waitForNewLeaderAck(getSid(), qp.getZxid());
sock.setSoTimeout(learnerMaster.syncTimeout());
learnerMaster.waitForStartup();
queuedPackets.add(new QuorumPacket(Leader.UPTODATE, -1, null, null));

即,先通过waitForNewLeaderAck进入主节点主导的第三次同步.
当算上主节点自身,有半数以上成员执行了learnerMaster.waitForNewLeaderAck时,表示半数以上成员已经达成集群同步.
这意味着,此时集群才算正式启动,可以启动服务端面向外部客户提供接入和请求处理服务.

上述在等待主节点的面向外部客户的服务端启动后,给从节点发送Leader.UPTODATE包.
3.5.从节点收到Leader.UPTODATE包的处理

self.setZooKeeperServer(zk);
self.adminServer.setZooKeeperServer(zk);
ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
writePacket(ack, true);
zk.startServing();
self.updateElectionVote(newEpoch);
if (zk instanceof FollowerZooKeeperServer) {
    FollowerZooKeeperServer fzk = (FollowerZooKeeperServer) zk;
    for (PacketInFlight p : packetsNotCommitted) {
        fzk.logRequest(p.hdr, p.rec, p.digest);
    }
    for (Long zxid : packetsCommitted) {
        fzk.commit(zxid);
    }
} 

上述过程可简要描述为:
a. 从节点面向外部客户的服务端启动,允许外部客户接入并提供请求处理服务
b. 再次给主节点发送Ack
c. 更新自身集群选票中的currentEpoch
d. 对packetsNotCommittedpacketsCommitted中尚且存在的包,分别执行序列化和提交操作.
DIFF模式下,此时完成实际的同步处理.

此后进入从节点常规运行阶段:

self.setZabState(QuorumPeer.ZabState.BROADCAST);
completedSync = true;
QuorumPacket qp = new QuorumPacket();
while (this.isRunning()) {
    readPacket(qp);
    processPacket(qp);
}

这样从节点已经完成集群同步,且集群已经开始对外提供服务了.

3.5.主节点的后续
(1). LearnerHandlerrun()中的后续
LearnerHandlerrun中后续进入常规的事件循环.
在事件循环里先收集来自从节点的包,再对包执行处理.

(2). Leaderlead()的后续

startZkServer();// 起点服务端,开启外部服务
self.setZooKeeperServer(zk);
self.setZabState(QuorumPeer.ZabState.BROADCAST);
self.adminServer.setZooKeeperServer(zk);
boolean tickSkip = true;
String shutdownMessage = null;
while (true) {
	synchronized (this) {
	    long start = Time.currentElapsedTime();
        long cur = start;
        long end = start + self.tickTime / 2;
        while (cur < end) {
	        wait(end - cur);
            cur = Time.currentElapsedTime();
        }
        if (!tickSkip) {
	        self.tick.incrementAndGet();
        }
        SyncedLearnerTracker syncedAckSet = new SyncedLearnerTracker();
        syncedAckSet.addQuorumVerifier(self.getQuorumVerifier());
        syncedAckSet.addAck(self.getId());
        for (LearnerHandler f : getLearners()) {
        	if (f.synced()) {
	            syncedAckSet.addAck(f.getSid());
            }
        }
        if (!this.isRunning()) {
        	shutdownMessage = "Unexpected internal error";
            break;
        }
        if (!tickSkip 
        	&& !syncedAckSet.hasAllQuorums() 
        	&& !(self.getQuorumVerifier().overrideQuorumDecision(getForwardingFollowers()) 
        	&& self.getQuorumVerifier().revalidateOutstandingProp(this, 
        		new ArrayList<>(outstandingProposals.values()), lastCommitted))) {
	        shutdownMessage = "Not sufficient followers synced, only synced with sids: [ " 
	        	+ syncedAckSet.ackSetsToString() + " ]";
            break;
        }
        tickSkip = !tickSkip;
	}
  
  	for (LearnerHandler f : getLearners()) {
  		f.ping();
  	}
}
if (shutdownMessage != null) {
	shutdown(shutdownMessage);
}

这样,主节点方面也已经完成集群同步,且集群已经开始对外提供服务了.
Leaderlead()后续会定期检测与主同步的节点数量,在节点数量不足半数时,停止主,并与所有连到其的从节点断开连接.这样,这些节点包含主自身将再次进入集群选举阶段.

  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
辽B代驾管理系统对代驾订单管理、用户咨询管理、代驾订单评价管理、代驾订单投诉管理、字典管理、论坛管理、公告管理、新闻信息管理、司机管理、用户管理、管理员管理等进行集中化处理。经过前面自己查阅的网络知识,加上自己在学校课堂上学习的知识,决定开发系统选择小程序模式这种高效率的模式完成系统功能开发。这种模式让操作员基于浏览器的方式进行网站访问,采用的主流的Java语言这种面向对象的语言进行辽B代驾管理系统程序的开发,在数据库的选择上面,选择功能强大的Mysql数据库进行数据的存放操作。辽B代驾管理系统的开发让用户查看代驾订单信息变得容易,让管理员高效管理代驾订单信息。 辽B代驾管理系统具有管理员角色,用户角色,这几个操作权限。 辽B代驾管理系统针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理代驾订单信息,管理公告信息等内容。 辽B代驾管理系统针对用户设置的功能有:查看并修改个人信息,查看代驾订单信息,查看公告信息等内容。 辽B代驾管理系统针对管理员设置的功能有:添加并管理各种类型信息,管理用户账户信息,管理代驾订单信息,管理公告信息等内容。 辽B代驾管理系统针对用户设置的功能有:查看并修改个人信息,查看代驾订单信息,查看公告信息等内容。 系统登录功能是程序必不可少的功能,在登录页面必填的数据有两项,一项就是账号,另一项数据就是密码,当管理员正确填写并提交这二者数据之后,管理员就可以进入系统后台功能操作区。项目管理页面提供的功能操作有:查看代驾订单,删除代驾订单操作,新增代驾订单操作,修改代驾订单操作。公告信息管理页面提供的功能操作有:新增公告,修改公告,删除公告操作。公告类型管理页面显示所有公告类型,在此页面既可以让管理员添加新的公告信息类型,也能对已有的公告类型信息执行编辑更新,失效的公告类型信息也能让管理员快速删除。新闻管理页面,此页面提供给管理员的功能有:新增新闻,修改新闻,删除新闻。新闻类型管理页面,此页面提供给管理员的功能有:新增新闻类型,修改新闻类型,删除新闻类型。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

raindayinrain

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值