接着上篇选举流程。当zk选举成功后,zk会进行(Leader-Follower)数据同步,数据同步成功后,整个集群才开始正常运作。这篇我们就来分析下数据同步流程。
首先我们还是回到org.apache.zookeeper.server.quorum.QuorumPeer的run方法为主入口
@Override
public void run() {
// ....省略一些无关紧要的代码
try {
/*
* 主循环
*/
while (running) {
switch (getPeerState()) {
// 根据zk的状态,执行对应的逻辑
case LOOKING:
// LOOKING,会触发选举逻辑,这个我们上篇已经说过了。
LOG.info("LOOKING");
// ... 省略一些无关紧要的代码
/*
* 当选举结束后。
* 会然后break跳出switch,然后会继续循环,根据选举后的状态
* 执行对应角色的逻辑
*/
break;
case OBSERVING:
// OBSERVING,Observer需要执行的逻辑。我们主要说Leader、Follower
// ... 省略一些无关紧要的代码
break;
case FOLLOWING:
// FOLLOWING,Follower需要执行的逻辑
try {
LOG.info("FOLLOWING");
// 创建Follower对象,并且设置Follower
setFollower(makeFollower(logFactory));
/*
* 这里会进行一个死循环,主要做的逻辑有
* 1、连接Leader
* 2、和Leader进行数据同步
* 3、同步完毕后,正常接收Leader的请求,并且执行对应的逻辑,
* 包括Request、Propose、Commit等请求
*/
follower.followLeader();
} catch (Exception e) {
LOG.warn("Unexpected exception",e);
} finally {
// 如果集群超过半数服务宕机或者Leader宕机,那么首先设置调用shutdown(),然后设置状态为LOOKING,会重新触发选举
follower.shutdown();
setFollower(null);
setPeerState(ServerState.LOOKING);
}
break;
case LEADING:
// LEADING,Leader需要执行的逻辑
LOG.info("LEADING");
try {
// 创建Leader对象,并且设置Leader
setLeader(makeLeader(logFactory));
/*
* 这里会进行一个死循环,主要做的逻辑有
* 1、接受Follower的连接
* 2、进行Follower进行数据同步
* 3、同步完成后,正常接收请求(主要包括客户端发来的请求、
* 集群Follower转发的事务请求等等)
*/
leader.lead();
// 如果集群超过半数服务宕机,那么首先设置Leader对象为null
setLeader(null);
} catch (Exception e) {
LOG.warn("Unexpected exception",e);
} finally {
if (leader != null) {
leader.shutdown("Forcing shutdown");
setLeader(null);
}
// 设置zk状态为LOOKING,需要重新触发选举
setPeerState(ServerState.LOOKING);
}
break;
}
}
} finally {
LOG.warn("QuorumPeer main thread exited");
try {
MBeanRegistry.getInstance().unregisterAll();
} catch (Exception e) {
LOG.warn("Failed to unregister with JMX", e);
}
jmxQuorumBean = null;
jmxLocalPeerBean = null;
}
}
下面分别说一下Leader和Follower进行数据同步的逻辑。
Leader
当节点在选举后角色确认为 leader 后将会进入 LEADING 状态,需要执行的源码简单如下:
setLeader(makeLeader(logFactory));
leader.lead();
类:org.apache.zookeeper.server.quorum.QuorumPeer
protected Leader makeLeader(FileTxnSnapLog logFactory) throws IOException {
/*
* 创建Leader对象,并且创建了LeaderZooKeeperServer对象(该对象主要是处理客户端
* 请求、或者Follower转发的事务请求,采用责任链的方式,后面单独那一篇出来讲解
* 请求的执行)
*
*/
return new Leader(this, new LeaderZooKeeperServer(logFactory,
this,new ZooKeeperServer.BasicDataTreeBuilder(), this.zkDb));
}
Leader(QuorumPeer self,LeaderZooKeeperServer zk) throws IOException {
this.self = self;
try {
if (self.getQuorumListenOnAllIPs()) {
// 创建一个ServerSocket,并且监听端口
ss = new ServerSocket(self.getQuorumAddress().getPort());
} else {
ss = new ServerSocket();
}
ss.setReuseAddress(true);
if (!self.getQuorumListenOnAllIPs()) {
ss.bind(self.getQuorumAddress());
}
} catch (BindException e) {
if (self.getQuorumListenOnAllIPs()) {
LOG.error("Couldn't bind to port " + self.getQuorumAddress().getPort(), e);
} else {
LOG.error("Couldn't bind to " + self.getQuorumAddress(), e);
}
throw e;
}
this.zk=zk;
}
synchronized protected void setLeader(Leader newLeader){
leader=newLeader;
}
类:org.apache.zookeeper.server.quorum.Leader
void lead() throws IOException, InterruptedException {
self.end_fle = System.currentTimeMillis();
LOG.info("LEADING - LEADER ELECTION TOOK - " +
(self.end_fle - self.start_fle));
self.start_fle = 0;
self.end_fle = 0;
zk.registerJMX(new LeaderBean(this, zk), self.jmxLocalPeerBean);
try {
self.tick = 0;
/*
* 从快照和事务日志中加载数据。
*/
zk.loadData();
leaderStateSummary = new StateSummary(self.getCurrentEpoch(), zk.getLastProcessedZxid());
// 创建一个线程,接收Follower/Observer的连接
cnxAcceptor = new LearnerCnxAcceptor();
// 开启线程
cnxAcceptor.start();
readyToStart = true;
// 等待超过一半的(Follower和Observer)连接,这里才会往下执行,返回新的朝代epoch
// 反之,阻塞在这里
long epoch = getEpochToPropose(self.getId(), self.getAcceptedEpoch());
// 根据新的epoch,设置新的起始zxid
zk.setZxid(ZxidUtils.makeZxid(epoch, 0));
synchronized(this){
lastProposed = zk.getZxid();
}
newLeaderProposal.packet = new QuorumPacket(NEWLEADER, zk.getZxid(),
null, null);
if ((newLeaderProposal.packet.getZxid() & 0xffffffffL) != 0) {
LOG.info("NEWLEADER proposal has Zxid of "
+ Long.toHexString(newLeaderProposal.packet.getZxid()));
}
// 等待超过一半的(Follower和Observer)获取了新的epoch,并且返回了Leader.ACKEPOCH
// 这里才会往下执行。 反之,阻塞在这里
waitForEpochAck(self.getId(), leaderStateSummary);
// 设置当前新的朝代epoch
self.setCurrentEpoch(epoch);
try {
// 等待超过一半的(Follower和Observer)进行数据同步成功,并且返回了Leader.ACK
// 这里才会往下执行。 反之,阻塞在这里
waitForNewLeaderAck(self.getId(), zk.getZxid(), LearnerType.PARTICIPANT);
} catch (InterruptedException e) {
shutdown("Waiting for a quorum of followers, only synced with sids: [ "
+ getSidSetString(newLeaderProposal.ackSet) + " ]");
HashSet<Long> followerSet = new HashSet<Long>();
for (LearnerHandler f : learners)
followerSet.add(f.getSid());
if (self.getQuorumVerifier().containsQuorum(followerSet)) {
LOG.warn("Enough followers present. "
+ "Perhaps the initTicks need to be increased.");
}
Thread.sleep(self.tickTime);
self.tick++;
return;
}
// 走到这里说明集群中数据已经同步完成,可以正常运行
// 开启zkServer,并且同时开启请求调用链接收请求执行
startZkServer();
String initialZxid = System.getProperty("zookeeper.testingonly.initialZxid");
if (initialZxid != null) {
long zxid = Long.parseLong(initialZxid);
zk.setZxid((zk.getZxid() & 0xffffffff00000000L) | zxid);
}
if (!System.getProperty("zookeeper.leaderServes", "yes").equals("no")) {
self.cnxnFactory.setZooKeeperServer(zk);
}
boolean tickSkip = true;
// 进行一个死循环,每次休眠self.tickTime / 2,和对所有的(Observer/Follower)发起心跳检测
while (true) {
Thread.sleep(self.tickTime / 2);
if (!tickSkip) {
self.tick++;
}
HashSet<Long> syncedSet = new HashSet<Long>();
// lock on the followers when we use it.
syncedSet.add(self.getId());
for (LearnerHandler f : getLearners()) {
// Synced set is used to check we have a supporting quorum, so only
// PARTICIPANT, not OBSERVER, learners should be used
if (f.synced() && f.getLearnerType() == LearnerType.PARTICIPANT) {
// 将Follower加入该容器
syncedSet.add(f.getSid());
}
f.ping();
}
// 判断是否有超过一半Follower在集群中
if (!tickSkip && !self.getQuorumVerifier().containsQuorum(syncedSet)) {
//if (!tickSkip && syncedCount < self.quorumPeers.size() / 2) {
// Lost quorum, shutdown
// 如果没有,那么调用shutdown关闭一些对象,然后return,重新选举
shutdown("Not sufficient followers synced, only synced with sids: [ "
+ getSidSetString(syncedSet) + " ]");
// make sure the order is the same!
// the leader goes to looking
return;
}
tickSkip = !tickSkip;
}
} finally {
zk.unregisterJMX(this);
}
}
从上面我们可以了解到,主要分为以下几个步骤
- 接收Follower/Observer的连接
- 获取新的朝代epoch
- 与Follower/Observer进行数据同步
- 与所有服务器进行心跳通信
1、接收Follower/Observer的连接
类: org.apache.zookeeper.server.quorum.Leader.LearnerCnxAcceptor
public void run() {
try {
while (!stop) {
try{
Socket s = ss.accept();
s.setSoTimeout(self.tickTime * self.initLimit);
s.setTcpNoDelay(nodelay);
LearnerHandler fh = new LearnerHandler(s, Leader.this);
fh.start();
} catch (SocketException e) {
if (stop) {
LOG.info("exception while shutting down acceptor: "
+ e);
// When Leader.shutdown() calls ss.close(),
// the call to accept throws an exception.
// We catch and set stop to true.
stop = true;
} else {
throw e;
}
}
}
} catch (Exception e) {
LOG.warn("Exception while accepting follower", e);
}
}
这里采用了传统的一个Thread处理一个Socket的模式。
2、Socket的连接处理器
类:org.apache.zookeeper.server.quorum.LearnerHandler
public void run() {
try {
tickOfNextAckDeadline = leader.self.tick
+ leader.self.initLimit + leader.self.syncLimit;
ia = BinaryInputArchive.getArchive(new BufferedInputStream(sock
.getInputStream()));
bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
oa = BinaryOutputArchive.getArchive(bufferedOutput);
// 等待读取Follower/Observer发出的请求 请求包的类型Leader.FOLLOWERINFO或者Leader.OBSERVERINFO
QuorumPacket qp = new QuorumPacket();
ia.readRecord(qp, "packet");
if(qp.getType() != Leader.FOLLOWERINFO && qp.getType() != Leader.OBSERVERINFO){
return;
}
// 读取请求的字节数据
byte learnerInfoData[] = qp.getData();
if (learnerInfoData != null) {
// 根据字节数据的长度判断,默认不等于8
if (learnerInfoData.length == 8) {
ByteBuffer bbsid = ByteBuffer.wrap(learnerInfoData);
this.sid = bbsid.getLong();
} else {
LearnerInfo li = new LearnerInfo(); ByteBufferInputStream.byteBuffer2Record(ByteBuffer.wrap(learnerInfoData), li);
// 获取myid
this.sid = li.getServerid();
// 获取协议版本号,默认为 0x10000
this.version = li.getProtocolVersion();
}
} else {
this.sid = leader.followerCounter.getAndDecrement();
}
// 如果请求包的类型为Leader.OBSERVERINFO,那么说明是OBSERVER
if (qp.getType() == Leader.OBSERVERINFO) {
learnerType = LearnerType.OBSERVER;
}
// 根据传输的zxid,获取它上一个朝代。
long lastAcceptedEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
long peerLastZxid;
StateSummary ss = null;
long zxid = qp.getZxid();
// 获取当前的新的朝代。这里需要等待超过一半的(Follower/Observer)服务器
// 连接并且线程执行到这里,才会往下走。否则 会阻塞在这里
long newEpoch = leader.getEpochToPropose(this.getSid(), lastAcceptedEpoch);
// 根据协议版本号执行对应的逻辑,上面说过默认是0x10000
if (this.getVersion() < 0x10000) {
// we are going to have to extrapolate the epoch information
long epoch = ZxidUtils.getEpochFromZxid(zxid);
ss = new StateSummary(epoch, zxid);
// fake the message
leader.waitForEpochAck(this.getSid(), ss);
} else {
byte ver[] = new byte[4];
ByteBuffer.wrap(ver).putInt(0x10000);
QuorumPacket newEpochPacket = new QuorumPacket(Leader.LEADERINFO, ZxidUtils.makeZxid(newEpoch, 0), ver, null);
// 向Follower/Observer发出的请求,包的类型为Leader.LEADERINFO,传输数据为版本号0x10000
oa.writeRecord(newEpochPacket, "packet");
bufferedOutput.flush();
QuorumPacket ackEpochPacket = new QuorumPacket();
// 等待读取Follower/Observer发出的请求,包的类型为Leader.ACKEPOCH
ia.readRecord(ackEpochPacket, "packet");
if (ackEpochPacket.getType() != Leader.ACKEPOCH) {
return;
}
/*
* 将该请求的数据转换为StateSummary对象,其实主要是最后事务执行的zxid。
* 下面会根据该zxid来判断使用哪一种同步类型
*/
ByteBuffer bbepoch = ByteBuffer.wrap(ackEpochPacket.getData());
ss = new StateSummary(bbepoch.getInt(), ackEpochPacket.getZxid());
// 这里需要等待超过一半的(Follower/Observer)服务器
// 发出了Leader.ACKEPOCH类型的请求并且线程执行到这里,才会往下走。否则 会阻塞在这里
leader.waitForEpochAck(this.getSid(), ss);
}
// 获取(Follower/Observer)最后事务执行的zxid
peerLastZxid = ss.getLastZxid();
// 下面是最重要的数据同步过程
/*
* 主要有4种同步类型
* Leader.DIFF:差异化同步
* Leader.SNAP:快照同步
* Leader.TRUNC:回滚
* Leader.TRUNC + Leader.DIFF:先回滚,在差异化同步
*/
// 默认设置需要发送的同步类型为快照同步
int packetToSend = Leader.SNAP;
// 设置需要发送的zxid=0
long zxidToSend = 0;
long leaderLastZxid = 0;
long updates = peerLastZxid;
// 获取zkDatabase的读写锁
ReentrantReadWriteLock lock = leader.zk.getZKDatabase().getLogLock();
// 获取读锁
ReadLock rl = lock.readLock();
try {
// 加读锁
rl.lock();
// 获取leader内存中最大的提交事务zxid
final long maxCommittedLog = leader.zk.getZKDatabase().getmaxCommittedLog();
// 获取leader内存中最小的提交事务zxid
final long minCommittedLog = leader.zk.getZKDatabase().getminCommittedLog();
// 获取内存中保存的所有的Proposal提议对象
LinkedList<Proposal> proposals = leader.zk.getZKDatabase().getCommittedLog();
// 如果proposals大小不为0
if (proposals.size() != 0) {
// 如果最后事务执行的zxid在maxCommittedLog和minCommittedLog之间
// 那么可以使用Leader.DIFF:差异化同步
if ((maxCommittedLog >= peerLastZxid)
&& (minCommittedLog <= peerLastZxid)) {
long prevProposalZxid = minCommittedLog;
boolean firstPacket=true;
// 将需要发送同步类型设置为Leader.DIFF
packetToSend = Leader.DIFF;
// 将需要发送的zxid设置为maxCommittedLog
zxidToSend = maxCommittedLog;
for (Proposal propose: proposals) {
// skip the proposals the peer already has
if (propose.packet.getZxid() <= peerLastZxid) {
// 跳过zxid小于等于peerLastZxid的Proposal提议
prevProposalZxid = propose.packet.getZxid();
continue;
} else {
if (firstPacket) {
/**
* 此时说明有部分 proposals 提案在 leader 节点上不存在,则需告诉 follower 丢弃这部分 proposals
* 也就是告诉 follower 先执行回滚 TRUNC ,需要回滚到 prevProposalZxid 处,也就是 follower 需要丢弃 prevProposalZxid ~ peerLastZxid 范围内的数据
* 剩余的 proposals 则通过 DIFF 进行同步
*/
firstPacket = false;
if (prevProposalZxid < peerLastZxid) {
packetToSend = Leader.TRUNC;
zxidToSend = prevProposalZxid;
updates = zxidToSend;
}
}
// 将需要DIFF同步的propose加入发送队列,等待传输。
queuePacket(propose.packet);
// 创建该propose的COMMIT包,并且加入发送队列,等待传输
QuorumPacket qcommit = new QuorumPacket(Leader.COMMIT, propose.packet.getZxid(),
null, null);
queuePacket(qcommit);
}
}
// 如果最后的zxid大于maxCommittedLog,那么说明需要回滚Leader.TRUNC
} else if (peerLastZxid > maxCommittedLog) {
packetToSend = Leader.TRUNC;
zxidToSend = maxCommittedLog;
updates = zxidToSend;
// 最后说明小于minCommittedLog,则需要使用Leader.SNAP快照同步
} else {
LOG.warn("Unhandled proposal scenario");
}
} else if (peerLastZxid == leader.zk.getZKDatabase().getDataTreeLastProcessedZxid()) {
// 如果proposals大小为0,而且和leader的最大zxid一样,说明不需要同步
// 这里proposals大小为0,可能leader刚保存了快照
// 将需要发送同步类型设置为Leader.DIFF
packetToSend = Leader.DIFF;
// 将需要发送zxid设置为peerLastZxid
zxidToSend = peerLastZxid;
} else {
// 如果proposals大小为0,而且zxid和leader的最大zxid不一样,使用快照同步
// just let the state transfer happen
LOG.debug("proposals is empty");
}
leaderLastZxid = leader.startForwarding(this, updates);
} finally {
rl.unlock();
}
// 创建一个Leader.NEWLEADER类型的请求包,将请求包加入待发送的队列
QuorumPacket newLeaderQP = new QuorumPacket(Leader.NEWLEADER,
ZxidUtils.makeZxid(newEpoch, 0), null, null);
if (getVersion() < 0x10000) {
oa.writeRecord(newLeaderQP, "packet");
} else {
queuedPackets.add(newLeaderQP);
}
bufferedOutput.flush();
// 如果为快照同步,那么将需要发送的同步zxid设置为leader的zkdatabase的最大zxid
if (packetToSend == Leader.SNAP) {
zxidToSend = leader.zk.getZKDatabase().getDataTreeLastProcessedZxid();
}
// 向(Follower/Observer)发送一个请求包,包的类型为同步方式
oa.writeRecord(new QuorumPacket(packetToSend, zxidToSend, null, null), "packet");
bufferedOutput.flush();
// 如果为快照同步,那么需要将整个zkDasbase序列化传输
if (packetToSend == Leader.SNAP) {
// Dump data to peer
leader.zk.getZKDatabase().serializeSnapshot(oa);
oa.writeString("BenWasHere", "signature");
}
bufferedOutput.flush();
// 创建一个线程来发送请求包。。。。
new Thread() {
public void run() {
Thread.currentThread().setName(
"Sender-" + sock.getRemoteSocketAddress());
try {
sendPackets();
} catch (InterruptedException e) {
LOG.warn("Unexpected interruption",e);
}
}
}.start();
qp = new QuorumPacket();
// 等待Follower/Observer发送的Leader.ACK类型请求包
ia.readRecord(qp, "packet");
if(qp.getType() != Leader.ACK){
return;
}
// 阻塞等待超过一半的Follower/Observer同步完成
leader.waitForNewLeaderAck(getSid(), qp.getZxid(), getLearnerType());
syncLimitCheck.start();
// 设置socket超时时间
sock.setSoTimeout(leader.self.tickTime * leader.self.syncLimit);
// 等待直到leader的zkServer启动完毕
synchronized(leader.zk){
while(!leader.zk.isRunning() && !this.isInterrupted()){
leader.zk.wait(20);
}
}
// leader 向 follower/Observer 发送 UPTODATE,告知其可对外提供服务
queuedPackets.add(new QuorumPacket(Leader.UPTODATE, -1, null, null));
while (true) {
// 略。
}
} catch (IOException e) {
// 略。
}
}
Follower
当节点在选举后角色确认为 follower后将会进入 FOLLOWING 状态,需要执行的源码简单如下:
setFollower(makeFollower(logFactory));
follower.followLeader();
类:org.apache.zookeeper.server.quorum.QuorumPeer
protected Follower makeFollower(FileTxnSnapLog logFactory) throws IOException {
// 创建Follower和FollowerZooKeeperServer对象
return new Follower(this, new FollowerZooKeeperServer(logFactory,
this,new ZooKeeperServer.BasicDataTreeBuilder(), this.zkDb));
}
synchronized protected void setFollower(Follower newFollower){
follower=newFollower;
}
void followLeader() throws InterruptedException {
// 省略一些代码
try {
// 首先找到Leader的地址
InetSocketAddress addr = findLeader();
try {
// 跟Leader建立连接
connectToLeader(addr);
// 从Leader获取新的朝代epoch
long newEpochZxid = registerWithLeader(Leader.FOLLOWERINFO);
long newEpoch = ZxidUtils.getEpochFromZxid(newEpochZxid);
if (newEpoch < self.getAcceptedEpoch())
throw new IOException("Error: Epoch of leader is lower");
}
// 和Leader进行数据同步
syncWithLeader(newEpochZxid);
QuorumPacket qp = new QuorumPacket();
// 进行一个死循环,与Leader进行交互
while (self.isRunning()) {
readPacket(qp);
processPacket(qp);
}
} catch (IOException e) {
// 省略一些代码
}
} finally {
zk.unregisterJMX((Learner)this);
}
}
从上面我们可以了解到,主要由以下几点
- 与Leader建立连接
- 从Leader获取新的朝代epoch
- 与Leader进行数据同步
- 进行一个死循环,与Leader进行交互
1、与Leader建立连接
protected InetSocketAddress findLeader() {
// 根据最后胜出的投票,找到Leader的地址
InetSocketAddress addr = null;
// Find the leader by id
Vote current = self.getCurrentVote();
for (QuorumServer s : self.getView().values()) {
if (s.id == current.getId()) {
addr = s.addr;
break;
}
}
if (addr == null) {
LOG.warn("Couldn't find the leader with id = "
+ current.getId());
}
return addr;
}
protected void connectToLeader(InetSocketAddress addr)
throws IOException, ConnectException, InterruptedException {
sock = new Socket();
sock.setSoTimeout(self.tickTime * self.initLimit);
for (int tries = 0; tries < 5; tries++) {
try {
// 连接Leader,如果连接成功break。如果连接失败,那么继续循环重试5次
sock.connect(addr, self.tickTime * self.syncLimit);
sock.setTcpNoDelay(nodelay);
break;
} catch (IOException e) {
if (tries == 4) {
LOG.error("Unexpected exception",e);
throw e;
} else {
LOG.warn("Unexpected exception, tries="+tries+
", connecting to " + addr,e);
sock = new Socket();
sock.setSoTimeout(self.tickTime * self.initLimit);
}
}
Thread.sleep(1000);
}
leaderIs = BinaryInputArchive.getArchive(new BufferedInputStream(
sock.getInputStream()));
bufferedOutput = new BufferedOutputStream(sock.getOutputStream());
leaderOs = BinaryOutputArchive.getArchive(bufferedOutput);
}
protected long registerWithLeader(int pktType) throws IOException{
/*
* Send follower info, including last zxid and sid
*/
// 获取最后执行的zxid
long lastLoggedZxid = self.getLastLoggedZxid();
// 设置请求包 请求类型为Leader.FOLLOWERINFO(因为我这里说的Follower,所以是Leader.FOLLOWERINFO)
QuorumPacket qp = new QuorumPacket();
qp.setType(pktType);
qp.setZxid(ZxidUtils.makeZxid(self.getAcceptedEpoch(), 0));
// 请求数据为LearnerInfo,包括myid和协议版本号0x10000
LearnerInfo li = new LearnerInfo(self.getId(), 0x10000);
ByteArrayOutputStream bsid = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(bsid);
boa.writeRecord(li, "LearnerInfo");
qp.setData(bsid.toByteArray());
// 向leader传输该请求
writePacket(qp, true);
// 等待读取leader传输的请求包,该请求包的类型为Leader.LEADERINFO
readPacket(qp);
// 从请求包的zxid解析出新的朝代epoch
final long newEpoch = ZxidUtils.getEpochFromZxid(qp.getZxid());
// 设置新的朝代epoch
if (qp.getType() == Leader.LEADERINFO) {
leaderProtocolVersion = ByteBuffer.wrap(qp.getData()).getInt();
byte epochBytes[] = new byte[4];
final ByteBuffer wrappedEpochBytes = ByteBuffer.wrap(epochBytes);
if (newEpoch > self.getAcceptedEpoch()) {
wrappedEpochBytes.putInt((int)self.getCurrentEpoch());
self.setAcceptedEpoch(newEpoch);
} else if (newEpoch == self.getAcceptedEpoch()) {
wrappedEpochBytes.putInt(-1);
} else {
throw new IOException("Leaders epoch, " + newEpoch + " is less than accepted epoch, " + self.getAcceptedEpoch());
}
// 创建请求包,请求类型为Leader.ACKEPOCH,并且传输处理的最大zxid,准备进行数据同步
QuorumPacket ackNewEpoch = new QuorumPacket(Leader.ACKEPOCH, lastLoggedZxid, epochBytes, null);
// 向leader发送请求
writePacket(ackNewEpoch, true);
return ZxidUtils.makeZxid(newEpoch, 0);
} else {
// 省略一些代码
}
}
protected void syncWithLeader(long newLeaderZxid) throws IOException, InterruptedException{
QuorumPacket ack = new QuorumPacket(Leader.ACK, 0, null, null);
QuorumPacket qp = new QuorumPacket();
long newEpoch = ZxidUtils.getEpochFromZxid(newLeaderZxid);
/*
* 读取从leader传输的数据,数据包的类型为同步类型。
* Leader.DIFF:差异化同步
* Leader.SNAP:快照同步
* Leader.TRUNC:回滚
*/
readPacket(qp);
LinkedList<Long> packetsCommitted = new LinkedList<Long>();
LinkedList<PacketInFlight> packetsNotCommitted = new LinkedList<PacketInFlight>();
synchronized (zk) {
// 根据不同的同步类型,进行相应的擦做做
if (qp.getType() == Leader.DIFF) {
// 差异化同步,在下面进行处理
}
else if (qp.getType() == Leader.SNAP) {
// 快照同步
// 首先清除自身的zkDatabase,也就是内存中的数据
zk.getZKDatabase().clear();
// 从leader传输过来的输入流中序列化zkDatabase
zk.getZKDatabase().deserializeSnapshot(leaderIs);
// 同步处理完后,最后读取签名
String signature = leaderIs.readString("signature");
// 校验签名是否正确
if (!signature.equals("BenWasHere")) {
LOG.error("Missing signature. Got " + signature);
throw new IOException("Missing signature");
}
} else if (qp.getType() == Leader.TRUNC) {
// 回滚同步
// 根据传输的zxid进行回滚
boolean truncated=zk.getZKDatabase().truncateLog(qp.getZxid());
if (!truncated) {
LOG.error("Not able to truncate the log "
+ Long.toHexString(qp.getZxid()));
System.exit(13);
}
}
else {
LOG.error("Got unexpected packet from leader "
+ qp.getType() + " exiting ... " );
System.exit(13);
}
zk.getZKDatabase().setlastProcessedZxid(qp.getZxid());
// 创建session管理器
zk.createSessionTracker();
long lastQueued = 0;
// 判断快照是否生成成功标识
boolean snapshotTaken = false;
outerLoop:
while (self.isRunning()) {
// 循环从Leader读取请求,知道接收到Leader.UPTODATE类型的请求包为止
readPacket(qp);
switch(qp.getType()) {
case Leader.PROPOSAL:
// 读取到Leader.PROPOSAL,说明是DIFF差异化同步,需要接收新的提案
PacketInFlight pif = new PacketInFlight();
pif.hdr = new TxnHeader();
pif.rec = SerializeUtils.deserializeTxn(qp.getData(), pif.hdr);
if (pif.hdr.getZxid() != lastQueued + 1) {
LOG.warn("Got zxid 0x"
+ Long.toHexString(pif.hdr.getZxid())
+ " expected 0x"
+ Long.toHexString(lastQueued + 1));
}
lastQueued = pif.hdr.getZxid();
// 加入等待Commit队列
packetsNotCommitted.add(pif);
break;
case Leader.COMMIT:
// 读取到Leader.PROPOSAL,说明提案需要提交
if (!snapshotTaken) {
// 从等待Commit队列获取提案
pif = packetsNotCommitted.peekFirst();
if (pif.hdr.getZxid() != qp.getZxid()) {
LOG.warn("Committing " + qp.getZxid() + ", but next proposal is " + pif.hdr.getZxid());
} else {
// 执行事务提案
zk.processTxn(pif.hdr, pif.rec);
packetsNotCommitted.remove();
}
} else {
packetsCommitted.add(qp.getZxid());
}
break;
case Leader.INFORM:
PacketInFlight packet = new PacketInFlight();
packet.hdr = new TxnHeader();
packet.rec = SerializeUtils.deserializeTxn(qp.getData(), packet.hdr);
// Log warning message if txn comes out-of-order
if (packet.hdr.getZxid() != lastQueued + 1) {
LOG.warn("Got zxid 0x"
+ Long.toHexString(packet.hdr.getZxid())
+ " expected 0x"
+ Long.toHexString(lastQueued + 1));
}
lastQueued = packet.hdr.getZxid();
if (!snapshotTaken) {
// Apply to db directly if we haven't taken the snapshot
zk.processTxn(packet.hdr, packet.rec);
} else {
packetsNotCommitted.add(packet);
packetsCommitted.add(qp.getZxid());
}
break;
case Leader.UPTODATE:
// 读取到Leader.UPTODATE,说明数据Leader已经可对外提供服务
if (!snapshotTaken) { // true for the pre v1.0 case
zk.takeSnapshot();
self.setCurrentEpoch(newEpoch);
}
// 设置zkServer
self.cnxnFactory.setZooKeeperServer(zk);
// 跳出循环
break outerLoop;
case Leader.NEWLEADER:
// 读取到Leader.NEWLEADER,说明Leader已经同步完毕
File updating = new File(self.getTxnFactory().getSnapDir(),
QuorumPeer.UPDATING_EPOCH_FILENAME);
if (!updating.exists() && !updating.createNewFile()) {
throw new IOException("Failed to create " +
updating.toString());
}
// 生成新的快照
zk.takeSnapshot();
// 设置新的朝代epoch
self.setCurrentEpoch(newEpoch);
if (!updating.delete()) {
throw new IOException("Failed to delete " +
updating.toString());
}
// 表示快照生成成功
snapshotTaken = true;
// 向Leader传输一个请求包,包的类型为Leader.ACK,表示数据已同步完毕
writePacket(new QuorumPacket(Leader.ACK, newLeaderZxid, null, null), true);
break;
}
}
}
ack.setZxid(ZxidUtils.makeZxid(newEpoch, 0));
// 向Leader传输一个请求包,包的类型为Leader.ACK。表示Follower可以
writePacket(ack, true);
sock.setSoTimeout(self.tickTime * self.syncLimit);
// 启动zkServer
zk.startup();
self.updateElectionVote(newEpoch);
// 忽略一些代码
}
总结一下zk同步,主要分以下4种方式:
DIFF(差异化同步):
-
follower 的 peerLastZxid 等于 leader 的 peerLastZxid
此时说明 follower 与 leader 数据一致,采用 DIFF 方式同步,也即是无需同步
-
follower 的 peerLastZxid 介于 maxCommittedLog, minCommittedLog 两者之间
此时说明 follower 与 leader 数据存在差异,需对差异的部分进行同步;首先 leader 会向 follower 发送 DIFF 报文告知其同步方式,随后会发送差异的提案及提案提交报文
TRUNC+DIFF(先回滚再差异化同步)
在上文 DIFF 差异化同步时会存在一个特殊场景就是 虽然 follower 的 peerLastZxid 介于,maxCommittedLog minCommittedLog 两者之间,但是 follower 的 peerLastZxid 在 leader 节点中不存在; 此时 leader 需告知 follower 先回滚到 peerLastZxid 的前一个 zxid, 回滚后再进行差异化同步。
示例: 假设集群中三台节点 A, B, C 某一时刻 A 为 Leader 选举周期为 5, zxid 包括: (0x500000001, 0x500000002, 0x500000003); 假设某一时刻 leader A 节点在处理完事务为 0x500000004 的请求进行广播时 leader A 节点服务器宕机导致 0x500000004 该事务没有被同步出去;在集群进行下一轮选举之后 B 节点成为新的 leader,选举周期为 6 对外提供服务处理了新的事务请求包括 0x600000001, 0x600000002;
集群列表 | ZXID 列表 |
---|---|
A | 0x500000001, 0x500000002, 0x500000003, 0x500000004 |
B | 0x500000001, 0x500000002, 0x500000003,0x600000001, 0x600000002 |
C | 0x500000001, 0x500000002, 0x500000003,0x600000001, 0x600000002 |
此时节点 A 在重启加入集群后,在与 leader B 节点进行数据同步时会发现事务 0x500000004 在 leader 节点中并不存在,此时 leader 告知 A 需先回滚事务到 0x500000003,在差异同步事务 0x600000001,0x600000002;
TRUNC( 回滚同步)
- follower 的 peerLastZxid 大于 leader 的 maxCommittedLog,则告知 follower 回滚至 maxCommittedLog; 该场景可以认为是 TRUNC+DIFF 的简化模式
SNAP(全量同步)
- follower 的 peerLastZxid 小于 leader 的 minCommittedLog
- leader 节点上不存在提案缓存队列时
该模式下 leader 首先会向 follower 发送 SNAP 报文,随后从内存数据库中获取全量数据序列化传输给 follower, follower 在接收全量数据后会进行反序列化加载到内存数据库中。
上面的同步流程可以简化为如下流程图:
来源:CSDN
作者:dk2077
链接:https://blog.csdn.net/dk243553650/article/details/104099503