这篇文章有点长,如果你想知道zk怎么进行leader选举的话,请耐心看完,
最好是安装完zk源码之后再来看,对着源码来看这篇文章不容易晕车,好了,大家坐好,准备发车。。滴滴
leader节点
客户端的写请求一定会到这个节点,然后这个节点会同步到follower节点
follower节点
客户端的读请求会经过一定的算法分部到不同的flower节点,如果leader节点挂掉,follower会参与投票选举出新的leader
observer节点
一个特殊的节点,可以帮助解决zk的扩展性(如果大量客户端访问我们的集群,需要增加zk集群性能,导致zk性能下降,因为zk的数据变更需要半数以上的节点投票通过,找出网络消耗,增加投票成本。)1.observer不参与投票,只接收投票结果
2.不属于zk的关键节点,但是一些高可用架构里最好有这个节点,用来提高性能
1、zk启动入口分析
怎么找zk启动类呢? 我来看一下zk中bin目录下的启动脚本zkServer.sh
可能看不懂,没关系,找能看懂的。
在开头定义了一个ZOOMAIN的东西,看着像是java的包路径, 假设这个就是启动类,然后再往后看,不懂、不懂、不懂、哎哎哎,突然看到一个start,我靠有点兴奋了,start就是启动的意思啊、看看start里都干什么了,我看到了nohup这个命令,并且这个命令后面跟着ZOOMAIN。nohup这个命令是干什么用的呢?这个不就是运行我们的程序的命令嘛。好吧,我的假设成立了,zk的启动类就是ZOOMAIN后面跟的java类 QuorumPeerMain。
入口找到了,下面就一步一步分析一下zk选举的过程
2、QuorumPeerMain类分析
刚刚我们说了,QuorumPeerMain类是我们zk的入口,那么这个类做了哪些事情呢?
这个类里加了一些注释,有的我就不进行文字描述了
打开这个类,会发现这里有main方法,在main方法里有这么一段话:
QuorumPeerMain main = new QuorumPeerMain();
main.initializeAndRun(args);
main.initializeAndRun(args)就是初始化并运行我们的zk,接着去看initializeAndRun方法做了哪些事情,下面贴出源码
protected void initializeAndRun(String[] args)
throws ConfigException, IOException, AdminServerException
{
//这个里面存的是我们在安装zk的时候在zoo.cfg中配置的东西,当然还有一些zk默认的配置
QuorumPeerConfig config = new QuorumPeerConfig();
if (args.length == 1) {
config.parse(args[0]);
}
// Start and schedule the the purge task
//启动并计划清除任务,这里面做了个定时器
//留个疑问?清除什么东西,定时任务做了什么,后面慢慢解决,目前看不懂
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
.getDataDir(), config.getDataLogDir(), config
.getSnapRetainCount(), config.getPurgeInterval());
purgeMgr.start();
//判断是集群模式还是单机模式、如果单机模式不会涉及选举算法
if (args.length == 1 && config.isDistributed()) {//集群
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);//单机
}
}
在上面源码中,我加了一些,这章我们主要是分析选举,那么其他的东西可以先抛开,
在分析选举前,我们要知道zk在什么时候进行选举
1、zk启动的时候会去选举
2、leader挂的时候会去选举
我们知道什么时候去选举了就好分析源码了,目前leader挂掉选举的源码还不知道在哪,所以先分析zk启动的时候选举的源码,说不定看着看着就发现第二种情况的选举源码了。
在源码中 if (args.length == 1 && config.isDistributed()) 这段代码是判断我们zk是集群呢还是单机呢,单机是没有选举的,所以我们看集群的方法 runFromConfig(config);
public void runFromConfig(QuorumPeerConfig config)
throws IOException, AdminServerException
{
try {
ManagedUtil.registerLog4jMBeans();
} catch (JMException e) {
LOG.warn("Unable to register log4j JMX control", e);
}
LOG.info("Starting quorum peer");
try {
ServerCnxnFactory cnxnFactory = null;
ServerCnxnFactory secureCnxnFactory = null;
//为客户端提供读写2181这个端口的访问功能
if (config.getClientPortAddress() != null) {
cnxnFactory = ServerCnxnFactory.createFactory();
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns(),
false);
}
if (config.getSecureClientPortAddress() != null) {
secureCnxnFactory = ServerCnxnFactory.createFactory();
secureCnxnFactory.configure(config.getSecureClientPortAddress(),
config.getMaxClientCnxns(),
true);
}
//这里是主逻辑,负责选举,投票
//这下面会从配置文件里加载配置信息
//quorumPeer继承了ZooKeeperThread,是个线程
quorumPeer = getQuorumPeer();
quorumPeer.setTxnFactory(new FileTxnSnapLog(
config.getDataLogDir(),
config.getDataDir()));
quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled());
quorumPeer.enableLocalSessionsUpgrading(
config.isLocalSessionsUpgradingEnabled());
//quorumPeer.setQuorumPeers(config.getAllMembers());
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.setConfigFileName(config.getConfigFilename());
quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
if (config.getLastSeenQuorumVerifier()!=null) {
quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
}
quorumPeer.initConfigInZKDatabase();
quorumPeer.setCnxnFactory(cnxnFactory);
quorumPeer.setSecureCnxnFactory(secureCnxnFactory);
quorumPeer.setLearnerType(config.getPeerType());
quorumPeer.setSyncEnabled(config.getSyncEnabled());
quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
// sets quorum sasl authentication configurations
quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl);
if(quorumPeer.isQuorumSaslAuthEnabled()){
quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl);
quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl);
quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal);
quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext);
quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext);
}
quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize);
quorumPeer.initialize();
quorumPeer.start();//加载完各种信息后去启动线程
quorumPeer.join();
} catch (InterruptedException e) {
// warn, but generally this is ok
LOG.warn("Quorum Peer interrupted", e);
}
}
这个方法看着多,不着急,慢慢看,我们发现这个方法里操作最多的就是给QuorumPeer类set的一些值,那我们打开这个类看下这个类的描述,这里我按照自己理解翻译了一下
/**
* 这个类管理者我们的quorum协议,
* 这个服务器处于三种状态
* 1、leader选举 每个服务器将选举一个leader,最初提出自己是leader
* 2、Follower 服务器(follower)将与leader同步并复制任何事务
* 3、Leader 服务器器(Leader)将处理请求并转发给follower
* 这个类设置一个数据报,这个数据报总是响应当前leader的看法
* 这个响应包涵了xid myid leader_id leader_zxid
* 对当前leader的请求将只包含一个xid: int xid
*/
public class QuorumPeer extends ZooKeeperThread implements QuorumStats.Provider {
QuorumPeer 这个类主要是干嘛的,我们大致清楚了,主要是封装我们的请求和响应,然后发送给其他服务器,是不是我们的选举算法会在这个类里呢?带着个疑问接着回到我们启动类中的runFromConfig方法中
刚刚我们在看QuorumPeer 类的时候我们看到了这个类是继承了ZooKeeperThread 类,是个线程,启动线程是start方法,所以在runFromConfig方法中肯定会有一个quorumPeer.start()方法。在runFromConfig方法中我们也找到了quorumPeer.start()方法,这个方法处于启动类QuorumPeerMain的207行,执行了start方法,我们需要去找到quorumPeer中的run()方法,这个才是线程启动之后要做的事情。
好了,我们又回到QuorumPeer 类中,去搜索run()方法,这里需要注意了,你会搜索到ResponderThread内部类下的run()方法,这个内部类在3.4.0之后被弃用了。所以不要去看这个内部类的run方法了。真的的run方法是在1112行
下面我们看下这个run方法里到底做了哪些事情,这里截取部分代码
try {
/*
* Main loop
*/
while (running) {
switch (getPeerState()) { //判断当前节点状态
case LOOKING: //如果是looking则进入初始化选举过程
LOG.info("LOOKING");
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() {
在1147行,我们发现有个注释叫 Main loop ,主循环嘛,在这个循环里有个判断switch (getPeerState())
这个判断是判断我们当前服务器的Peer状态的,默认为LOOKING状态,所以我们先去看case LOOKING下面的内容,这里补充一下我们Peer状态分为四种, LOOKING, FOLLOWING, LEADING, OBSERVING;
LOOKING状态下zk做了什么什么事情?不要急,快到高潮了。。
在1191行附近,为什么说在1191行附近呢?这个是楼主的锅,因为楼主自己加了些注释。。。。。。接着看代码吧。有这这么一段代码
//通过策略模式来决定使用哪个选举算法来进行选举
//并且将投票结果赋值给当前的投票中
setCurrentVote(makeLEStrategy().lookForLeader());
vote投票的意思,兴奋起来了,好像找到投票的入口了,到底是不是呢?高潮来没来呢?继续。。
在setCurrentVote方法里传入了makeLEStrategy().lookForLeader()东西,这个东西是什么?占时还不知道,我们直接去setCurrentVote方法里面看看这个方法做了什么事情
public synchronized void setCurrentVote(Vote v){
currentVote = v;
}
/**
* This is who I think the leader currently is.
* 这就是我认为目前的leader。
*/
volatile private Vote currentVote;
这时候发现,setCurrentVote方法里面就一句话,currentVote = v 那么currentVote 这个是什么?在我们当前类(QuorumPeer)中定义了一个Vote 类型的变量,这个变量就是currentVote ,好吧,我们知道了,setCurrentVote方法就是个赋值的方法,把我们的投票赋值给currentVote 。
这时候我们就明白setCurrentVote方法里传入的makeLEStrategy().lookForLeader()就是个传投票信息进来
而我们的makeLEStrategy().lookForLeader()就是为了构造投票信息的
这个时候我们的方向就又明确了,我们去看看makeLEStrategy().lookForLeader()怎么来构造投票信息的。
这里用了策略模式来决定使用哪个选举算法,zk中有4中选举算法,那我们到底使用的是哪一种呢?
其实不用纠结用哪一种。。。因为3.4之后只有一种选举算法没有被废弃。。那就是FastLeaderElection其他几个都被废弃不用了。。。
那我们现在就去看看FastLeaderElection这个选举算法是怎么选举出leader的
ps:在这里切断一下,文章太长了会导致消化不了,下面一篇文章会详细讲解FastLeaderElection这个算法策略
这里分享一下看我看源码的一些方法
1、确定我们看源码要看哪些东西,有方向有目标
2、找到源码入口,如果自己找不到可以网上冲个浪
3、如果是参考别人的文章一定要自己下载了源码对照了看
4、初次看源码切记不要太深,点到为止
5、多看几遍
6、最重要的一点,不要怕源码,都是人写的