zookeeper源码学习(一)——启动流程

zookeeper源码学习(二)——选举

开始启动——定时清理任务

话不多说,找到我们的启动类org.apache.zookeeper.server.quorum.QuorumPeerMain,查看他的main方法

public static void main(String[] args) {
        QuorumPeerMain main = new QuorumPeerMain();
        try {
            main.initializeAndRun(args);
            }
            ...
            }

其实就是调用了自己的initializeAndRun方法

protected void initializeAndRun(String[] args)
        throws ConfigException, IOException, AdminServerException
    {
    //解析配置
        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();

        // 当配置了多节点信息,config.isDistributed()=true
        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);
        }
    }

通过传递进来的路径参数解析zoo.cfg文件,创建定时清理的任务DatadirCleanupManager#start

public void start() {
        timer = new Timer("PurgeTask", true);
        TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
        timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval));

        purgeTaskStatus = PurgeTaskStatus.STARTED;
    }

跟进PurgeTask#run方法,查看具体任务内容

public void run() {
            LOG.info("Purge task started.");
            try {
                PurgeTxnLog.purge(logsDir, snapsDir, snapRetainCount);
            } catch (Exception e) {
                LOG.error("Error occurred while purging.", e);
            }
            LOG.info("Purge task completed.");
        }
 public static void purge(File dataDir, File snapDir, int num) throws IOException {
        if (num < 3) {
            throw new IllegalArgumentException(COUNT_ERR_MSG);
        }

        FileTxnSnapLog txnLog = new FileTxnSnapLog(dataDir, snapDir);

        List<File> snaps = txnLog.findNRecentSnapshots(num);
        int numSnaps = snaps.size();
        if (numSnaps > 0) {
            purgeOlderSnapshots(txnLog, snaps.get(numSnaps - 1));
        }
    }

主要清理的逻辑在purgeOlderSnapshots方法,主要清楚最近以及之前zxid的快照文件,不包括文件中有包含比最近的zxid版本更新的事务的文件。

集群启动

接下来就是通过参数数量与配置判断是集群启动还是单机启动,这里以集群启动为例调用QuorumPeerMain#runFromConfig

public void runFromConfig(QuorumPeerConfig config)
            throws IOException, AdminServerException
    {
      try {
          // 注册jmx
          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;

          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);
          }

          // ------------初始化当前zk服务节点的配置----------------
          // 设置数据和快照操作
          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());
          // server Id
          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());

          // 设置zk的节点数据库
          quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
          quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
          if (config.getLastSeenQuorumVerifier()!=null) {
              quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
          }

          // 初始化zk数据库
          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);

          // -------------初始化当前zk服务节点的配置---------------
          quorumPeer.initialize();

          //重点
          quorumPeer.start();
          quorumPeer.join();
      } catch (InterruptedException e) {
          // warn, but generally this is ok
          LOG.warn("Quorum Peer interrupted", e);
      }
    }

首先对log4j注册 JMX MBean ,实现监控、管理能力,创建ServerCnxnFactory工厂,默认是NIOServerCnxnFactory,初始化 QuorumPeer,设置各种参数,其中会创建初始化zk数据库也就是ZKDatabase,ZKDatabase中会对DataTree进行初始化,创建root节点,以及/zookeeper/config配置的节点。 quorumPeer.setElectionType(config.getElectionAlg());为设置选举的类型,默认是3.接着会调用quorumPeer.start方法这是整个启动逻辑的重点方法

 public synchronized void start() {

        // 校验serverid如果不在peer列表中,抛异常
        if (!getView().containsKey(myid)) {
            throw new RuntimeException("My id " + myid + " not in the peer list");
         }

        // 加载zk数据库:载入之前持久化的一些信息
        loadDataBase();

        // 启动连接服务端
        startServerCnxnFactory();
        try {
            adminServer.start();
        } catch (AdminServerException e) {
            LOG.warn("Problem starting AdminServer", e);
            System.out.println(e);
        }
        // 启动之后马上进行选举,主要是创建选举必须的环境,比如:启动相关线程
        startLeaderElection();

        // 执行选举逻辑
        super.start();
    }

首先会去调用loadDataBase方法加载快照文件中的数据到内存中,主要是重启、故障恢复时快速恢复数据的。这个具体的实现过程后面再讲。然后就是调用startServerCnxnFactory方法去启动ServerCnxnFactory服务

private void startServerCnxnFactory() {
        if (cnxnFactory != null) {
            cnxnFactory.start();
        }
        if (secureCnxnFactory != null) {
            secureCnxnFactory.start();
        }
    }

调用ServerCnxnFactory的start方法,默认是使用NIOServerCnxnFactory实现的

 public void start() {
        stopped = false;
        //初始化worker线程池
        if (workerPool == null) {
            workerPool = new WorkerService(
                "NIOWorker", numWorkerThreads, false);
        }

        //挨个启动Selector线程(处理客户端请求线程),
        for(SelectorThread thread : selectorThreads) {
            if (thread.getState() == Thread.State.NEW) {
                thread.start();
            }
        }
        // ensure thread is started once and only once
        //启动acceptThread线程(处理接收连接进行事件)
        if (acceptThread.getState() == Thread.State.NEW) {
            acceptThread.start();
        }

       // ExpirerThread(处理过期连接)
        if (expirerThread.getState() == Thread.State.NEW) {
            expirerThread.start();
        }
    }
  • workerPool为空时,创建一个WorkerService,他是一个worker线程池,处理工作任务,里面包含若干ExecutorService,可以设置工作线程数,shutDown超时时间。
  • 挨个启动Selector线程,这是zk封装的thread,继承自Thread,封装了NIO的 Selector、SocketChannel,SelectionKey,用于完成nio网络通信。
  • 启动acceptThread线程,根据状态确保只启动一次。处理注册监听ACCEPT事件.
  • 启动ExpirerThread线程,循环判断连接是否过期,处理清除过期连接。

选举准备工作

线程启动完后,会去调用startLeaderElection方法去准备选举相关环境

 synchronized public void startLeaderElection() {
       try {
           // 所有节点启动的初始状态都是LOOKING,因此这里都会是创建一张投自己为Leader的票
           if (getPeerState() == ServerState.LOOKING) {
               currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
           }
       } catch(IOException e) {
           RuntimeException re = new RuntimeException(e.getMessage());
           re.setStackTrace(e.getStackTrace());
           throw re;
       }
        if (electionType == 0) {
            try {
                udpSocket = new DatagramSocket(myQuorumAddr.getPort());
                responder = new ResponderThread();
                responder.start();
            } catch (SocketException e) {
                throw new RuntimeException(e);
            }
        }
        //初始化选举算法,electionType默认为3
        this.electionAlg = createElectionAlgorithm(electionType);
    }

所有节点启动的初始状态都是LOOKING,因此这里都会是创建一个投票,设置到currentVote中,就是说创建一张投给自己的票。Vote属性传入myid,最近的事务版本zxid,peerEpoch三个参数,然后前面看到过electionType默认是设置为3的。所以会调用createElectionAlgorithm方法创建选举算法。

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,直接走到这里
            qcm = createCnxnManager();
            //监听选举事件的listener
            QuorumCnxManager.Listener listener = qcm.listener;
            if(listener != null){
                //开启监听器
                listener.start();
                //初始化选举算法
                FastLeaderElection fle = new FastLeaderElection(this, qcm);
                fle.start();
                le = fle;
            } else {
                LOG.error("Null listener when initializing cnx manager");
            }
            break;
        default:
            assert false;
        }
        return le;
    }

因为传进来的electionAlgorithm是3,所以会创建一个QuorumCnxManager,然后开启里面的监听器(这个监听器主要就是根据配置信息创建选举地址的套接字,bind绑定监听选举端口),创建初始化具体的选举算法FastLeaderElection。然后FastLeaderElection 执行start方法,其实就是启动两个线程WorkerSender、WorkerReceiver,分别用于向其他所有节点发送自己的投票信息、接收并处理其他节点发送给自己的投票信息的线程,具体的可以放在之后在说明。最后,会调用QuorumPeer父类的start方法执行具体选举逻辑。QuorumPeer类继承自ZooKeeperThread类,而ZooKeeperThread类继承自Thread。所以调用QuorumPeer父类的start方法就是在调用线程run方法。这个在后面再具体分析。

小结

回顾一下zookeeper集群的启动过程:

  1. QuorumPeerMain启动,通过传入的配置文件路径地址解析zoo.cfg配置文件。
  2. 创建并启动定时清理任务的线程
  3. 创建ServerCnxnFactory工厂,默认是NIOServerCnxnFactory,初始化 QuorumPeer,设置各种参数,其中会创建初始化zk数据库也就是ZKDatabase(存放节点信息的)
  4. 加载快照日志文件恢复数据
  5. 开启各种工作 连接 清理的线程
  6. 监听选举端口等待接收信息,创建具体选举算法,启动WorkerSender、WorkerReceiver,分别用于向其他所有节点发送自己的信息、接收并处理其他节点发送给自己的信息的线程。
  7. 开始具体选举。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值