前言
- 为了方便我们进行Debug跟踪启动流程和查看控制台日志,所以我们要在IDE中启动zk。首先我们创建一个项目引入zk的maven包
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.6</version>
</dependency>
- 创建一个main方法,调用QuorumPeerMain的main方法即可启动zk
public static void main(String[] args) throws IOException {
/*
* path数组放入你们系统中的zk的配置文件,配置文件自行去官网下载一个安装包即可
*/
String[] path = {"C:\\Users\\Administrator\\Desktop\\zookeeper-3.4.6\\conf\\cluster\\zoo-2182.cfg"};
// 传入path数组,进行启动zk
QuorumPeerMain.main(path);
}
- ZK在Windows中没有提供命令去查看集群的状态,所以我们可以自己在IDE中调用方法去查看集群的状态
public static void main(String[] args) throws IOException{
/*
* 数组中的值依次为:1、zk的ip地址、
* 2、zk接收客户端连接的端口(配置文件中的clientPort默认为2181)
* 3、需要执行命令(因为我们需要查询集群的状态,那么传status即可)
*/
String[] cmd_2181 = {"127.0.0.1","2181","status"};
FourLetterWordMain.main(cmd_2181);
}
启动流程核心入口
从上述前言中,我们得知如何在ide中启动zk,所以我们便可知zk的启动入口是类:org.apache.zookeeper.server.quorum.QuorumPeerMain的main方法,所以我们就从这个方法开始。
下面我会把核心的源码复制出来进行讲解,并且会省去一些无关紧要的代码来节省边幅。
类:org.apache.zookeeper.server.quorum.QuorumPeerMain
public static void main(String[] args) {
// 其实main方法什么都没做,主要是调用了本类中的initializeAndRun方法,并且传入args数组
QuorumPeerMain main = new QuorumPeerMain();
try {
main.initializeAndRun(args);
} catch (Exception e) {
..........
}
LOG.info("Exiting normally");
System.exit(0);
}
protected void initializeAndRun(String[] args)
throws ConfigException, IOException
{
// 创建QuorumPeerConfig对象,该类作用很简单就是解析我们之前传入进来的配置文件(zoo.cfg)
QuorumPeerConfig config = new QuorumPeerConfig();
if (args.length == 1) {
// 解析zoo.cfg
config.parse(args[0]);
}
/*
* 创建DatadirCleanupManager对象,该类主要是定期清理snapshot快照文件和log事务文件
*/
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
.getDataDir(), config.getDataLogDir(), config
.getSnapRetainCount(), config.getPurgeInterval());
// 启动清理。默认是不开启
purgeMgr.start();
/*
* 判断是否为集群模式还是单机模式
* 如何判断是否为单机和集群启动,等等解析zoo.cfg文件的时候在简单分析一下
*/
if (args.length == 1 && config.servers.size() > 0) {
// 集群模式调用这个方法
runFromConfig(config);
} else {
LOG.warn("Either no config or no quorum defined in config, running "
+ " in standalone mode");
// there is only server in the quorum -- run as standalone
// 单机模式调用这个方法
ZooKeeperServerMain.main(args);
}
}
由上面代码可知,main方法其实主要做了3件事
- 创建QuorumPeerConfig对象,用于解析配置文件(默认zoo.cfg)
- 创建DatadirCleanupManager,用于定期清理snapshot快照文件和log事务文件,默认不开启,需要在配置文件中进行配置
- 判断是否为集群模式启动还是单机模式启动,调用对应的方法,这边只讲集群启动。
1、解析配置文件
类:org.apache.zookeeper.server.quorum.QuorumPeerConfig
public void parse(String path) throws ConfigException {
File configFile = new File(path);
LOG.info("Reading configuration from: " + configFile);
try {
if (!configFile.exists()) {
throw new IllegalArgumentException(configFile.toString()
+ " file is missing");
}
Properties cfg = new Properties();
FileInputStream in = new FileInputStream(configFile);
try {
cfg.load(in);
} finally {
in.close();
}
// 上面代码没什么好说的,直接看这个方法
parseProperties(cfg);
} catch (IOException e) {
......
}
}
public void parseProperties(Properties zkProp)
throws IOException, ConfigException {
int clientPort = 0;
String clientPortAddress = null;
for (Entry<Object, Object> entry : zkProp.entrySet()) {
String key = entry.getKey().toString().trim();
String value = entry.getValue().toString().trim();
if (key.equals("dataDir")) {
// 快照存储的目录;即snapshot,对应配置文件中的是dataDir
dataDir = value;
} else if (key.equals("dataLogDir")) {
// 事务日志存储的目录;即log,对应配置文件中的是dataLogDir
dataLogDir = value;
//..... 省略了一些判断实在太多,有兴趣可以自己去看
} else if (key.equals("autopurge.snapRetainCount")) {
// 定期清理snapshot文件,需要保留的snapshot文件个数
snapRetainCount = Integer.parseInt(value);
} else if (key.equals("autopurge.purgeInterval")) {
// 多久启动清理。单位:小时。配置文件中没开启这个值,所以默认不开启定时定期。如有需要,自行开启
purgeInterval = Integer.parseInt(value);
} else if (key.startsWith("server.")) {
// 这里也就是解析集群配置。如果我们在配置文件中有配置server.id=xxxx就是集群启动
int dot = key.indexOf('.');
long sid = Long.parseLong(key.substring(dot + 1));
String parts[] = value.split(":");
if ((parts.length != 2) && (parts.length != 3) && (parts.length !=4)) {
LOG.error(value
+ " does not have the form host:port or host:port:port " +
" or host:port:port:type");
}
InetSocketAddress addr = new InetSocketAddress(parts[0],
Integer.parseInt(parts[1]));
// 下面解析不同的集群配置,有3种情况
if (parts.length == 2) {
//1、eg: server.1=127.0.0.1:2887
servers.put(Long.valueOf(sid), new QuorumServer(sid, addr));
} else if (parts.length == 3) {
//2、eg: server.1=127.0.0.1:2887:3887
InetSocketAddress electionAddr = new InetSocketAddress(
parts[0], Integer.parseInt(parts[2]));
servers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
electionAddr));
} else if (parts.length == 4) {
//3、eg: server.1=127.0.0.1:2887:3887:participant
InetSocketAddress electionAddr = new InetSocketAddress(
parts[0], Integer.parseInt(parts[2]));
LearnerType type = LearnerType.PARTICIPANT;
if (parts[3].toLowerCase().equals("observer")) {
type = LearnerType.OBSERVER;
observers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
electionAddr,type));
} else if (parts[3].toLowerCase().equals("participant")) {
type = LearnerType.PARTICIPANT;
servers.put(Long.valueOf(sid), new QuorumServer(sid, addr,
electionAddr,type));
} else {
throw new ConfigException("Unrecognised peertype: " + value);
}
}
}
//.........省略一些代码
// 下面是解析myid文件
File myIdFile = new File(dataDir, "myid");
if (!myIdFile.exists()) {
throw new IllegalArgumentException(myIdFile.toString()
+ " file is missing");
}
BufferedReader br = new BufferedReader(new FileReader(myIdFile));
String myIdString;
try {
myIdString = br.readLine();
} finally {
br.close();
}
try {
serverId = Long.parseLong(myIdString);
MDC.put("myid", myIdString);
} catch (NumberFormatException e) {
throw new IllegalArgumentException("serverid " + myIdString
+ " is not a number");
}
//.........省略一些代码
}
}
2、启动定期清理快照文件和事务log
类:org.apache.zookeeper.server.DatadirCleanupManager
public void start() {
if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
LOG.warn("Purge task is already running.");
return;
}
// Don't schedule the purge task with zero or negative purge interval.
// 如果配置文件中autopurge.purgeInterval这个值没有开启或者设置小于0,那么定时清理就没有开启
if (purgeInterval <= 0) {
LOG.info("Purge task is not scheduled.");
return;
}
// 创建一个定时器
timer = new Timer("PurgeTask", true);
// 定时清理的逻辑在类PurgeTask中
TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
// purgeInterval 小时执行一次
timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));
// 标志状态为已启动
purgeTaskStatus = PurgeTaskStatus.STARTED;
}
类:org.apache.zookeeper.server.DatadirCleanupManager.PurgeTask
@Override
public void run() {
LOG.info("Purge task started.");
try {
PurgeTxnLog.purge(new File(logsDir), new File(snapsDir), snapRetainCount);
} catch (Exception e) {
LOG.error("Error occured while purging.", e);
}
LOG.info("Purge task completed.");
}
类:org.apache.zookeeper.server.PurgeTxnLog
public static void purge(File dataDir, File snapDir, int num) throws IOException {
if (num < 3) {
throw new IllegalArgumentException("count should be greater than 3");
}
FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);
Set<File> exc=new HashSet<File>();
/*
* 快照(snapshot)文件命名的格式为:snapshot.lastZxid;lastZxid是该快照文件存储的最大的事务id
* 后面我会写一篇文章详细介绍zk的数据存储,在来讲快照文件和事务日志
* findNRecentSnapshots:该方法是从存储快照的目录中,首先进行快照文件以文件名的lastZxid降序排序,
* 找到排在前num的文件,放入list中。
*/
List<File> snaps = txnLog.findNRecentSnapshots(num);
// 如果为空,直接返回,说明还没有继续过快照
if (snaps.size() == 0)
return;
// 取出列表中最后一个快照
File snapShot = snaps.get(snaps.size() -1);
// 将刚才list的文件放入set集合中,该set集合的存储的文件是不用被清理的
for (File f: snaps) {
exc.add(f);
}
/*
* 从刚刚获取的最后一个快照的文件中解析出zxid。
*/
long zxid = Util.getZxidFromName(snapShot.getName(),"snapshot");
/*
* 事务log文件命名:log.zxid;该zxid是这个事务log文件中最小的事务id
* getSnapshotLogs:找到大于该zxid的事务日志log,并将它们全部加入set集合中。
* 原因就是小于该zxid的事务已经被全部快照存储到snapshot文件中了,所以这些事务log就没用了可以被清理,
* 只需要保留大于zxid的事务log即可
*/
exc.addAll(Arrays.asList(txnLog.getSnapshotLogs(zxid)));
final Set<File> exclude=exc;
class MyFileFilter implements FileFilter{
private final String prefix;
MyFileFilter(String prefix){
this.prefix=prefix;
}
public boolean accept(File f){
if(!f.getName().startsWith(prefix) || exclude.contains(f))
return false;
return true;
}
}
// add all non-excluded log files
/*
* 找到"log."和"snapshot."开头的文件,并且该文件不在exclude集合中的
*/
List<File> files=new ArrayList<File>(
Arrays.asList(txnLog.getDataDir().listFiles(new MyFileFilter("log."))));
// add all non-excluded snapshot files to the deletion list
files.addAll(Arrays.asList(txnLog.getSnapDir().listFiles(new MyFileFilter("snapshot."))));
// remove the old files
// 清除这些旧文件
for(File f: files)
{
System.out.println("Removing file: "+
DateFormat.getDateTimeInstance().format(f.lastModified())+
"\t"+f.getPath());
if(!f.delete()){
System.err.println("Failed to remove "+f.getPath());
}
}
}
3、集群启动
类:org.apache.zookeeper.server.quorum.QuorumPeerMain
public void runFromConfig(QuorumPeerConfig config) throws IOException {
..... 省略一些代码
try {
/*
* 创建一个连接工厂。该工厂有2中模式分别为Netty、NIO,默认为NIO
* 该工厂作用主要是接收客户端的连接、请求等。
*/
ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns());
/**
* 创建一个QuorumPeer对象,设置一些必要的值,这里没啥好说的。
* 主要是对刚刚从zoo.cfg中解析出来数据进行设置
*/
quorumPeer = new QuorumPeer();
quorumPeer.setClientPortAddress(config.getClientPortAddress());
quorumPeer.setTxnFactory(new FileTxnSnapLog(
new File(config.getDataLogDir()),
new File(config.getDataDir())));
quorumPeer.setQuorumPeers(config.getServers());
quorumPeer.setElectionType(config.getElectionAlg());
quorumPeer.setMyid(config.getServerId());
quorumPeer.setTickTime(config.getTickTime());
quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
quorumPeer.setInitLimit(config.getInitLimit());
quorumPeer.setSyncLimit(config.getSyncLimit());
quorumPeer.setQuorumVerifier(config.getQuorumVerifier());
quorumPeer.setCnxnFactory(cnxnFactory);
quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
quorumPeer.setLearnerType(config.getPeerType());
quorumPeer.setSyncEnabled(config.getSyncEnabled());
quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
// 启动QuorumPeer,该类继承了Thread,不过它又重写了start方法
quorumPeer.start();
quorumPeer.join();
} catch (InterruptedException e) {
// warn, but generally this is ok
LOG.warn("Quorum Peer interrupted", e);
}
}
类:org.apache.zookeeper.server.quorum.QuorumPeer
public synchronized void start() {
// 加载zk的内存数据库
loadDataBase();
// 启动连接工厂,接收客户端的连接、请求等
cnxnFactory.start();
// 创建选举流程需要的一些必要对象
startLeaderElection();
// 因为QuorumPeer类集成了Thread,所以执行run方法(主要是进行选举、数据同步等等)
super.start();
}
3.1 加载zk内存数据库
private void loadDataBase() {
File updating = new File(getTxnFactory().getSnapDir(),
UPDATING_EPOCH_FILENAME);
try {
// 核心就是调用了ZKDatabase.loadDataBase()
zkDb.loadDataBase();
// 下面是进行一些数据检验
long lastProcessedZxid = zkDb.getDataTree().lastProcessedZxid;
long epochOfZxid = ZxidUtils.getEpochFromZxid(lastProcessedZxid);
try {
currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
if (epochOfZxid > currentEpoch && updating.exists()) {
LOG.info("{} found. The server was terminated after " +
"taking a snapshot but before updating current " +
"epoch. Setting current epoch to {}.",
UPDATING_EPOCH_FILENAME, epochOfZxid);
setCurrentEpoch(epochOfZxid);
if (!updating.delete()) {
throw new IOException("Failed to delete " +
updating.toString());
}
}
} catch(FileNotFoundException e) {
// 省略一些代码..
}
}
类:org.apache.zookeeper.server.ZKDatabase;后面会单独抽出一章来详细介绍zk的内存数据存储
public long loadDataBase() throws IOException {
// 创建一个监听器
PlayBackListener listener=new PlayBackListener(){
/*
* 等等解析事务log恢复内存数据的时候,会调用该方法
*/
public void onTxnLoaded(TxnHeader hdr,Record txn){
Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
null, null);
r.txn = txn;
r.hdr = hdr;
r.zxid = hdr.getZxid();
/*
* 将最新的一些事务保存到内存中,作用就是方便leader-follower数据同步的时候,如果follower最新的事务
* 是在leader内存中存在的,那么可以采用DIFF(差异化)同步,后面讲数据同步在说。
*/
addCommittedProposal(r);
}
};
// 根据快照和事务log进行恢复数据到内存数据库,并且返回最新的zxid
long zxid = snapLog.restore(dataTree,sessionsWithTimeouts,listener);
// 标记初始化内存数据库为true
initialized = true;
// 返回最新的zxid
return zxid;
}
类:org.apache.zookeeper.server.persistence.FileTxnSnapLog。恢复数据到内存数据库由2部分数据组成,分别为快照和事务log
public long restore(DataTree dt, Map<Long, Integer> sessions,
PlayBackListener listener) throws IOException {
// 首先从***最新的有效的***快照中恢复数据
snapLog.deserialize(dt, sessions);
FileTxnLog txnLog = new FileTxnLog(dataDir);
/*
* 有一部分数据可能在关闭zk或者zk宕机时没有进行快照,而存在事务log列表中。
* 所以需要根据快照读出来的最新zxid去判断,那么大于这个zxid的事务log列表
* 即是不存在与快照中,而需要恢复的事务log列表
*/
TxnIterator itr = txnLog.read(dt.lastProcessedZxid+1);
long highestZxid = dt.lastProcessedZxid;
TxnHeader hdr;
try {
while (true) {
// iterator points to
// the first valid txn when initialized
hdr = itr.getHeader();
if (hdr == null) {
//empty logs
return dt.lastProcessedZxid;
}
if (hdr.getZxid() < highestZxid && highestZxid != 0) {
LOG.error("{}(higestZxid) > {}(next log) for type {}",
new Object[] { highestZxid, hdr.getZxid(),
hdr.getType() });
} else {
// 更新最新的zxid
highestZxid = hdr.getZxid();
}
try {
// 执行该事务
processTransaction(hdr,dt,sessions, itr.getTxn());
} catch(KeeperException.NoNodeException e) {
throw new IOException("Failed to process transaction type: " +
hdr.getType() + " error: " + e.getMessage(), e);
}
// 回调之前传进来的监听器
listener.onTxnLoaded(hdr, itr.getTxn());
// 如果全部事务log读取完毕,那么直接break
if (!itr.next())
break;
}
} finally {
if (itr != null) {
itr.close();
}
}
// 返回目前内存数据库中最新的zxid
return highestZxid;
}
从快照中恢复数据
类:org.apache.zookeeper.server.persistence.FileSnap
public long deserialize(DataTree dt, Map<Long, Integer> sessions)
throws IOException {
// 首先找到100个以snapshot.开头的快照文件。并且以文件名中的zxid进行降序排序,之前说过快照文件的命名
List<File> snapList = findNValidSnapshots(100);
if (snapList.size() == 0) {
return -1L;
}
File snap = null;
boolean foundValid = false;
// 遍历该列表 找到一个最新的合法的快照文件 进行反序列,一个即可,不用全部反序列化
for (int i = 0; i < snapList.size(); i++) {
snap = snapList.get(i);
InputStream snapIS = null;
CheckedInputStream crcIn = null;
try {
LOG.info("Reading snapshot " + snap);
// 创建输入流读取文件
snapIS = new BufferedInputStream(new FileInputStream(snap));
// 这里使用Adler32进行输入流的校验
crcIn = new CheckedInputStream(snapIS, new Adler32());
InputArchive ia = BinaryInputArchive.getArchive(crcIn);
// 反序列化,具体的就不详细说了,自己可以进去看,其实很简单
deserialize(dt,sessions, ia);
// 根据输入流获得校验和
long checkSum = crcIn.getChecksum().getValue();
// 从文件中读取校验和
long val = ia.readLong("val");
// 校验和进行对比。如果不一样,那么说明是非法快照文件,抛出异常,进行下一次循环读取下一个文件
if (val != checkSum) {
throw new IOException("CRC corruption in snapshot : " + snap);
}
// 如果一样,那么说明是合法快照文件,break跳出循环
foundValid = true;
break;
} catch(IOException e) {
LOG.warn("problem reading snap file " + snap, e);
} finally {
if (snapIS != null)
snapIS.close();
if (crcIn != null)
crcIn.close();
}
}
if (!foundValid) {
throw new IOException("Not able to find valid snapshots in " + snapDir);
}
// 根据反序列的的快照文件的名称中的zxid,来设置最新的zxid
dt.lastProcessedZxid = Util.getZxidFromName(snap.getName(), "snapshot");
// 返回快照文件中读取的最新的zxid
return dt.lastProcessedZxid;
}
PlayBackListener 监听器的作用
PlayBackListener listener=new PlayBackListener(){
public void onTxnLoaded(TxnHeader hdr,Record txn){
Request r = new Request(null, 0, hdr.getCxid(),hdr.getType(),
null, null);
r.txn = txn;
r.hdr = hdr;
r.zxid = hdr.getZxid();
addCommittedProposal(r);
}
};
/*
* 该方法将提交的事务保存到内存集合中,作用就是方便之后leader-follower数据同步的时候,如果follower最新的事务
* 是在leader内存中存在的,那么可以采用DIFF(差异化)同步,后面讲数据同步在说。
*/
public void addCommittedProposal(Request request) {
WriteLock wl = logLock.writeLock();
try {
// 加写锁
wl.lock();
// 如果内存中保存的事务log数量大于定义数量的限制
if (committedLog.size() > commitLogCount) {
// 移除第一个事务log
committedLog.removeFirst();
// 重新设置最小事务zxid
minCommittedLog = committedLog.getFirst().packet.getZxid();
}
// 如果该request是第一个事务,那么直接设置最小提交zxid和最大zxid为该request的zxid
if (committedLog.size() == 0) {
minCommittedLog = request.zxid;
maxCommittedLog = request.zxid;
}
// 构造QuorumPacket对象
ByteArrayOutputStream baos = new ByteArrayOutputStream();
BinaryOutputArchive boa = BinaryOutputArchive.getArchive(baos);
try {
request.hdr.serialize(boa, "hdr");
if (request.txn != null) {
request.txn.serialize(boa, "txn");
}
baos.close();
} catch (IOException e) {
LOG.error("This really should be impossible", e);
}
QuorumPacket pp = new QuorumPacket(Leader.PROPOSAL, request.zxid,
baos.toByteArray(), null);
// 构建Proposal对象
Proposal p = new Proposal();
p.packet = pp;
p.request = request;
// 加入commitLog集合
committedLog.add(p);
// 设置最大提交的zxid
maxCommittedLog = p.packet.getZxid();
} finally {
wl.unlock();
}
}
3.2 创建选举流程需要的一些必要对象
类:org.apache.zookeeper.server.quorum.QuorumPeer
synchronized public void startLeaderElection() {
try {
// 创建一个对自己的投票对象
currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
} catch(IOException e) {
RuntimeException re = new RuntimeException(e.getMessage());
re.setStackTrace(e.getStackTrace());
throw re;
}
for (QuorumServer p : getView().values()) {
if (p.id == myid) {
myQuorumAddr = p.addr;
break;
}
}
if (myQuorumAddr == null) {
throw new RuntimeException("My id " + myid + " not in the peer list");
}
// 如果electionType等于0,创建UDP
if (electionType == 0) {
try {
udpSocket = new DatagramSocket(myQuorumAddr.getPort());
responder = new ResponderThread();
responder.start();
} catch (SocketException e) {
throw new RuntimeException(e);
}
}
// 根据electionType创建选举算法,如果没有在配置文件中设置electionAlg。那么该electionType默认为3
this.electionAlg = createElectionAlgorithm(electionType);
}
protected Election createElectionAlgorithm(int electionAlgorithm){
Election le=null;
//TODO: use a factory rather than a switch
switch (electionAlgorithm) {
case 0:
le = new LeaderElection(this);
break;
case 1:
le = new AuthFastLeaderElection(this);
break;
case 2:
le = new AuthFastLeaderElection(this, true);
break;
case 3:
// 默认electionAlgorithm = 3
/*
* 首先创建QuorumCnxManager对象
* 该对象主要作用于管理服务器之间选举的通信
*/
qcm = new QuorumCnxManager(this);
QuorumCnxManager.Listener listener = qcm.listener;
if(listener != null){
// 开启监听器,监听器用于监听选举端口、接收别的服务器连接
listener.start();
// 创建FastLeaderElection选举算法,用于选举,后面单独拿出一节来讲详细的选举流程
le = new FastLeaderElection(this, qcm);
} else {
LOG.error("Null listener when initializing cnx manager");
}
break;
default:
assert false;
}
return le;
}
类:org.apache.zookeeper.server.quorum.QuorumPeer
public void run() {
// 省略一些代码
try {
/*
* Main loop
*/
while (running) {
// 这里有个死循环
switch (getPeerState()) {
// 根据服务器的状态,执行对应的逻辑
case LOOKING:
// 正在进行选举
LOG.info("LOOKING");
if (Boolean.getBoolean("readonlymode.enabled")) {
// 这里是开启只读模式的一些逻辑,暂时省略。。。。
} else {
try {
/*
* 调用选举算法的lookForLeader()方法,触发选举流程。(默认是FastLeaderElection算法)
* 选举结束后,将leader选票设置为currentVote
*/
setBCVote(null);
setCurrentVote(makeLEStrategy().lookForLeader());
} catch (Exception e) {
LOG.warn("Unexpected exception", e);
setPeerState(ServerState.LOOKING);
}
}
break;
case OBSERVING:
// 选举完毕,Observer角色执行的逻辑
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:
// 选举完毕,Follower角色执行的逻辑
try {
LOG.info("FOLLOWING");
setFollower(makeFollower(logFactory));
// Follower数据同步等等流程,后续说
follower.followLeader();
} catch (Exception e) {
LOG.warn("Unexpected exception",e);
} finally {
follower.shutdown();
setFollower(null);
setPeerState(ServerState.LOOKING);
}
break;
case LEADING:
// 选举完毕,Leader角色执行的逻辑
LOG.info("LEADING");
try {
setLeader(makeLeader(logFactory));
// Leader数据同步等等流程,后续说
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");
try {
MBeanRegistry.getInstance().unregisterAll();
} catch (Exception e) {
LOG.warn("Failed to unregister with JMX", e);
}
jmxQuorumBean = null;
jmxLocalPeerBean = null;
}
}