zk版本:3.5.6

1. 初始化到启动过程

根据zkStart.sh脚本可知,QuorumPeerMain.main方法是启动zk的唯一位置。但是这个启动zk可以分为两种模式:单机模式与集群模式。由于集群模式比较复杂,会在后面一一介绍,现在我们先看看单机模式zk的启动。

public static void main(String[] args) {
        QuorumPeerMain main = new QuorumPeerMain();
        // 解析配置并运行zk
        main.initializeAndRun(args);
        .... // 处理异常省略
    }
    /**
     * 解析配置并运行(单机模式+集群模式)
     * @param args 指定启动参数
     */
 protected void initializeAndRun(String[] args)
        throws ConfigException, IOException, AdminServerException
    {
        // zookeeper配置对象
        QuorumPeerConfig config = new QuorumPeerConfig();
        // 指定配置文件
        if (args.length == 1) {
            config.parse(args[0]);
        }

        /**
         * 定时清理zookeeper数据()
         */
        DatadirCleanupManager purgeMgr = new DatadirCleanupManager(config
                .getDataDir(), config.getDataLogDir(), config
                .getSnapRetainCount(), config.getPurgeInterval());
        // 触发定时任务,定时清除数据和快照文件
        purgeMgr.start();
        
        if (args.length == 1 && config.isDistributed()) {
            // 集群模式
            runFromConfig(config);
        } else {
            // 单机模式
            ZooKeeperServerMain.main(args);
        }
    }
    
    /**
     * 是否是集群模式
     * @return
     */
    public boolean isDistributed() {
        // quorumVerifier.getVotingMembers() 投票成员要两个以上
        return quorumVerifier!=null && (!standaloneEnabled || quorumVerifier.getVotingMembers().size() > 1);
    }

启动zk的主要流程:

解析配置文件成QuorumPeerConfig配置对象

定时清理zookeeper事务日志和快照日志

根据配置启动单机模式/集群模式,单机模式启动的主类是: ZooKeeperServerMain.main

ZooKeeperServerMain.java
-------------------------

    public static void main(String[] args) {
        ZooKeeperServerMain main = new ZooKeeperServerMain();
        // 启动单机模式
        main.initializeAndRun(args);
        .....
    /**
     * 解析单机模式的配置对象,并启动单机模式
     */
      protected void initializeAndRun(String[] args)
        throws ConfigException, IOException, AdminServerException
    {
        // 注册jmx
        ManagedUtil.registerLog4jMBeans();
    
        // 服务配置
        ServerConfig config = new ServerConfig();
        if (args.length == 1) {
            // 解析配置文件
            config.parse(args[0]);
        } else {
            // 解析配置参数
            config.parse(args);
        }
        // 根据配置运行服务
        runFromConfig(config);
    }
 
    /**
     * 根据配置运行zk单机服务
     *
     */
   public void runFromConfig(ServerConfig config)
            throws IOException, AdminServerException {
        FileTxnSnapLog txnLog = null;
        try {
            // 管理事务日志和快照
            txnLog = new FileTxnSnapLog(config.dataLogDir, config.dataDir);
            final ZooKeeperServer zkServer = new ZooKeeperServer(txnLog,
                    config.tickTime, config.minSessionTimeout, config.maxSessionTimeout, null);
            txnLog.setServerStats(zkServer.serverStats());

            final CountDownLatch shutdownLatch = new CountDownLatch(1);
            // 服务结束钩子
            zkServer.registerServerShutdownHandler(
                    new ZooKeeperServerShutdownHandler(shutdownLatch));

            // Start Admin server
            // 创建admin服务,用于接收请求(创建jetty服务)
            adminServer = AdminServerFactory.createAdminServer();
            // 设置zookeeper服务
            adminServer.setZooKeeperServer(zkServer);
            // 服务启动,监听客户端请求
            adminServer.start();

            boolean needStartZKServer = true;
            // ------------创建客户端连接对象-----------
            if (config.getClientPortAddress() != null) {
                cnxnFactory = ServerCnxnFactory.createFactory();
                cnxnFactory.configure(config.getClientPortAddress(), config.getMaxClientCnxns(), false);
                cnxnFactory.startup(zkServer);
                // zkServer has been started. So we don't need to start it again in secureCnxnFactory.
                needStartZKServer = false;
            }
            if (config.getSecureClientPortAddress() != null) {
                secureCnxnFactory = ServerCnxnFactory.createFactory();
                secureCnxnFactory.configure(config.getSecureClientPortAddress(), config.getMaxClientCnxns(), true);
                secureCnxnFactory.startup(zkServer, needStartZKServer);
            }

            // 定时清除容器节点和临时节点
            containerManager = new ContainerManager(zkServer.getZKDatabase(), zkServer.firstProcessor,
                    Integer.getInteger("znode.container.checkIntervalMs", (int) TimeUnit.MINUTES.toMillis(1)),
                    Integer.getInteger("znode.container.maxPerMinute", 10000)
            );
            containerManager.start();

            // 根据ZooKeeperServerShutdownHandler处理逻辑可知,只有在服务运行不正常的情况下,才会往下执行
            shutdownLatch.await();

            // 关闭服务
            shutdown();

            if (cnxnFactory != null) {
                cnxnFactory.join();
            }
            if (secureCnxnFactory != null) {
                secureCnxnFactory.join();
            }
            if (zkServer.canShutdown()) {
                //完全清除zkDataBase
                zkServer.shutdown(true);
            }
        } catch (InterruptedException e) {
            LOG.warn("Server interrupted", e);
        } finally {
            if (txnLog != null) {
                txnLog.close();
            }
        }
    }

zk单机模式启动主要流程:

  1. 注册jmx
  2. 解析ServerConfig配置对象
  3. 根据配置对象,运行单机zk服务
  4. 创建管理事务日志和快照FileTxnSnapLog对象,zookeeperServer对象,并设置zkServer的统计对象
  5. 设置zk服务钩子,原理是通过设置CountDownLatch,调用ZooKeeperServerShutdownHandler的handle方法,可以将触发shutdownLatch.await方法继续执行,即调用shutdown关闭单机服务
  6. 基于jetty创建zk的admin服务【后面文章将介绍】,用于使用url的形式管理zk。使用常见的命令形式:
http://<hostname>:8080/commands
http://<hostname>:8080/commands/<commandname>
  1. 创建连接对象cnxnFactory和secureCnxnFactory(安全连接才创建该对象),用于处理客户端的请求,比如:setData,getData,create等请求
  2. 创建定时清除容器节点管理器,用于处理容器节点下不存在子节点的清理容器节点工作等

2. 发现与总结

2.1 sessionTimeout设置最大最小值的作用?

客户端可以主动设置sessionTimeout的值,如果客户端设置的超时时间不在这个范围,那么会被强制设置为最大或最小时间

2.2 ServerStats用途

统计性能和连接的客户端的列表,在四字命令中可以查询。

2.3 服务结束钩子什么时候触发?

ZooKeeperServerShutdownHandler类只有一个主要的方法:

void handle(State state) {
        if (state == State.ERROR || state == State.SHUTDOWN) {
            shutdownLatch.countDown();
        }
    }

根据这个方法可以知道当State = ERROR / SHUTDOWN时,shutdownLatch.countDown()会被调用就会执行shutdownLatch.await()后的关闭服务逻辑。 所以需要找到调用handle方法的地方,还好全局只有一个地方:

ZooKeeperServer.java
---------------------

    protected void setState(State state) {
        this.state = state;
        // Notify server state changes to the registered shutdown handler, if any.

        // 设置状态时,判断state是不是处于异常情况,如果是就使用zkShutdownHandler处理
        if (zkShutdownHandler != null) {
            zkShutdownHandler.handle(state);
        } else {
            LOG.debug(".......");
        }
    }

根据反向查找可知,

ZooKeeperThread.uncaughtExceptionalHandler.uncaughtException -> ZooKeeperCriticalThread.handleException -> ZooKeeperServerListener.notifyStopping

即: 当类继承了ZooKeeperCriticalThread线程,在出现不可捕获的异常之后就会触发服务关闭。

3. 相关链接

  1. admin服务启动和处理
  2. 单机处理客户端请求