zookeeper服务端源码深入分析

从启动脚本寻找入口

与客户端启动类似,服务端肯定也会有一个启动类作为入口。
例如zkServer.cmd脚本文件

setlocal
call "%~dp0zkEnv.cmd"

set ZOOMAIN=org.apache.zookeeper.server.quorum.QuorumPeerMain
echo on
java "-Dzookeeper.log.dir=%ZOO_LOG_DIR%" "-Dzookeeper.root.logger=%ZOO_LOG4J_PROP%" -cp "%CLASSPATH%" %ZOOMAIN% "%ZOOCFG%" %*

endlocal

可以看到启动类是QuorumPeerMain,与zkCli.cmd脚本文件相比,就多了一个%ZOOMAIN%
根据call "%~dp0zkEnv.cmd",可知变量定义在zkEnv.cmd中,

set ZOOCFGDIR=%~dp0%..\conf
set ZOO_LOG_DIR=%~dp0%..
set ZOO_LOG4J_PROP=INFO,CONSOLE

REM for sanity sake assume Java 1.6
REM see: http://java.sun.com/javase/6/docs/technotes/tools/windows/java.html

REM add the zoocfg dir to classpath
set CLASSPATH=%ZOOCFGDIR%

REM make it work in the release
SET CLASSPATH=%~dp0..\*;%~dp0..\lib\*;%CLASSPATH%

REM make it work for developers
SET CLASSPATH=%~dp0..\build\classes;%~dp0..\build\lib\*;%CLASSPATH%

set ZOOCFG=%ZOOCFGDIR%\zoo.cfg

zkEnv.cmd配置文件最后一行可以看到ZOOCFG的定义
说明服务端是通过配置文件zoo.cfg来启动的
单机服务器的基础配置

#基础时间单元
tickTime=2000
#Follower同步Leader的限制时间,initLimit * tickTime,随ZK集群数据量适当增加值
initLimit=10
#心跳检测机制,来检测机器的存活状态,syncLimit * tickTime
syncLimit=5
#志输出目录
dataDir=D:/tools/zookeeper-3.4.6/data
#端口
clientPort=2181

QuorumPeerMain入口

public static void main(String[] args) {
    // args就是变量ZOOCFG
    QuorumPeerMain main = new QuorumPeerMain();
    try {
        // 初始化配置信息并运行
        main.initializeAndRun(args);
    } catch (IllegalArgumentException e) {
       .
       .
       
    }
    LOG.info("Exiting normally");
    System.exit(0);
}

初始化配置信息并运行

protected void initializeAndRun(String[] args) throws ConfigException, IOException
    {
    // 集群模式的配置类
    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.servers.size() > 0) {
        // 运行集群模式
        runFromConfig(config);
    } else {
        LOG.warn("Either no config or no quorum defined in config, running "
                + " in standalone mode");
        // 单机模式
        ZooKeeperServerMain.main(args);
    }
}

这里先只看单机模式的服务端

ZooKeeperServerMain

public static void main(String[] args) {
    ZooKeeperServerMain main = new ZooKeeperServerMain();
    try {
        // 单机模式初始化配置信息并运行
        main.initializeAndRun(args);
    } catch (IllegalArgumentException e) {
       .
       .
    }
    LOG.info("Exiting normally");
    System.exit(0);
}

initializeAndRun单机模式初始化配置信息并运行

protected void initializeAndRun(String[] args) throws ConfigException, IOException
    {
    try {
        ManagedUtil.registerLog4jMBeans();
    } catch (JMException e) {
        LOG.warn("Unable to register log4j JMX control", e);
    }

    // 单机模式的配置类
    ServerConfig config = new ServerConfig();
    if (args.length == 1) {
        // 解析配置文件,将配置信息放入配置类里
        config.parse(args[0]);
    } else {
        config.parse(args);
    }
    // 根据配置信息运行服务器
    runFromConfig(config);
}

runFromConfig根据配置信息运行服务器

public void runFromConfig(ServerConfig config) throws IOException {
    LOG.info("Starting server");
    FileTxnSnapLog txnLog = null;
    try {
        // 服务端对象
        final ZooKeeperServer zkServer = new ZooKeeperServer();
        final CountDownLatch shutdownLatch = new CountDownLatch(1);
        zkServer.registerServerShutdownHandler(
                new ZooKeeperServerShutdownHandler(shutdownLatch));
        // 工具类,存储日志文件与快照
        txnLog = new FileTxnSnapLog(new File(config.dataLogDir), new File(
                config.dataDir));
        txnLog.setServerStats(zkServer.serverStats());
        zkServer.setTxnLogFactory(txnLog);
        zkServer.setTickTime(config.tickTime);
        zkServer.setMinSessionTimeout(config.minSessionTimeout);
        zkServer.setMaxSessionTimeout(config.maxSessionTimeout);
        // 获取建立socket工厂,工厂方法模式
        cnxnFactory = ServerCnxnFactory.createFactory();
        // 建立socket,默认是NIOServerCnxnFactory(是一个线程)
        cnxnFactory.configure(config.getClientPortAddress(),
                config.getMaxClientCnxns());
        cnxnFactory.startup(zkServer);
        // Watch status of ZooKeeper server. It will do a graceful shutdown
        // if the server is not running or hits an internal error.
        shutdownLatch.await();
        shutdown();

        cnxnFactory.join();
        if (zkServer.canShutdown()) {
            zkServer.shutdown(true);
        }
    } catch (InterruptedException e) {
        // warn, but generally this is ok
        LOG.warn("Server interrupted", e);
    } finally {
        if (txnLog != null) {
            txnLog.close();
        }
    }
}

NIOServerCnxnFactory服务端socket

建立socket,默认是NIOServerCnxnFactory

configure

public void configure(InetSocketAddress addr, int maxcc) throws IOException {
    configureSaslLogin();

    // 把当前类作为线程
    thread = new ZooKeeperThread(this, "NIOServerCxn.Factory:" + addr);
    // java中线程分为两种类型:用户线程和守护线程。
    // 通过Thread.setDaemon(false)设置为用户线程;通过Thread.setDaemon(true)设置为守护线程。
    // 如果不设置次属性,默认为用户线程。
    // 守护进程(Daemon)是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。也就是说守护线程不依赖于终端,但是依赖于系统,与系统“同生共死”。
    // 那Java的守护线程是什么样子的呢。当JVM中所有的线程都是守护线程的时候,JVM就可以退出了;如果还有一个或以上的非守护线程则JVM不会退出
    // 垃圾回收线程就是一个经典的守护线程,当我们的程序中不再有任何运行的Thread,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。
    // 它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。

    // 所以这里的这个线程是为了和JVM生命周期绑定,只剩下这个线程时已经没有意义了,应该关闭掉。
    thread.setDaemon(true);
    maxClientCnxns = maxcc;
    this.ss = ServerSocketChannel.open();
    ss.socket().setReuseAddress(true);
    LOG.info("binding to port " + addr);
    ss.socket().bind(addr);
    ss.configureBlocking(false);
    ss.register(selector, SelectionKey.OP_ACCEPT);
}

startup

public void startup(ZooKeeperServer zks) throws IOException,InterruptedException {
    // 启动线程
    start();
    // 初始化zkServer
    setZooKeeperServer(zks);
    // 加载数据
    zks.startdata();
    // 设置请求处理器链
    zks.startup();
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值