Zookeeper服务器单机版启动

先来看下ZooKeeper服务端的整体构架。

1.单机版服务器启动

        ZooKeeper服务器的启动,大体分为以下5个步骤:配置文件解析、初始化数据管理器、初始化网络I/O管理器、数据恢复与对外服务。下图为单机版的服务器启动流程。

1.1 预启动

预启动步骤如下。

(1)统一由QuorumPeerMain作为启动类。

         无论是单机版还是集群版,在启动ZooKeeper服务器时,在zkServer.cmd和zkServer.sh两个脚本中,都配置了使用org.apach.zookeeper.server.quorum.QuorumPeerMain作为启动类入口。

public class QuorumPeerMain {
    private static final Logger LOG = LoggerFactory.getLogger(QuorumPeerMain.class);
 
    private static final String USAGE = "Usage: QuorumPeerMain configfile";
 
    protected QuorumPeer quorumPeer;
 
    /**
     * To start the replicated server specify the configuration file name on
     * the command line.
     * @param args path to the configfile
     */
    public static void main(String[] args) {
        QuorumPeerMain main = new QuorumPeerMain();
        try {
            main.initializeAndRun(args);
        } catch (IllegalArgumentException e) {
            LOG.error("Invalid arguments, exiting abnormally", e);
            LOG.info(USAGE);
            System.err.println(USAGE);
            System.exit(2);
        } catch (ConfigException e) {
            LOG.error("Invalid config, exiting abnormally", e);
            System.err.println("Invalid config, exiting abnormally");
            System.exit(2);
        } catch (Exception e) {
            LOG.error("Unexpected exception, exiting abnormally", e);
            System.exit(1);
        }
        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]);  //解析配置文件zoo.cfg
        }
 
        // 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");
            // there is only server in the quorum -- run as standalone
            ZooKeeperServerMain.main(args);  //集群模式
        }
    }

(2)解析配置文件zoo.cfg。

        ZooKeeper首先会进行配置文件的解析,配置文件的解析其实就是对zoo.cfg文件的解析,该文件配置了ZooKeeper运行时的基本参数,包括ticketTime、DataDir和clientPort等参数。

(3)创建并启动历史文件清理器DatadirCleanupManager。

        从3.4.0版本开始,ZooKeeper增加了自动清理历史数据文件的机制,包括对事务日志和快照文件进行定时清理。

(4)判断当前是集群模式还是单机模式的启动。

       从上面步骤(2)解析出的集群服务器地址列表来判断当前是集群模式还是单机模式,如果是单机模式,就委托给ZooKeeperServerMain进行启动处理。

(5)再次进行配置文件zoo.cfg的解析。

(6)创建服务器实例ZooKeeperServer。

      org.apach.zookeeper.server.ZooKeeperServer是单机版ZooKeeper服务端最核心的实体类。ZooKeeper服务器首先会进行服务器实例的创建,接下去的步骤是则都是对该服务器实例的初始化工作,包括连接器、内存数据库和请求处理器等组件的初始化。


public class ZooKeeperServerMain {
    private static final Logger LOG =
        LoggerFactory.getLogger(ZooKeeperServerMain.class);
 
    private static final String USAGE =
        "Usage: ZooKeeperServerMain configfile | port datadir [ticktime] [maxcnxns]";
 
    private ServerCnxnFactory cnxnFactory;
 
    /*
     * Start up the ZooKeeper server.
     *
     * @param args the configfile or the port datadir [ticktime]
     */
    public static void main(String[] args) {
        ZooKeeperServerMain main = new ZooKeeperServerMain();
        try {
            main.initializeAndRun(args);
        } catch (IllegalArgumentException e) {
            LOG.error("Invalid arguments, exiting abnormally", e);
            LOG.info(USAGE);
            System.err.println(USAGE);
            System.exit(2);
        } catch (ConfigException e) {
            LOG.error("Invalid config, exiting abnormally", e);
            System.err.println("Invalid config, exiting abnormally");
            System.exit(2);
        } catch (Exception e) {
            LOG.error("Unexpected exception, exiting abnormally", e);
            System.exit(1);
        }
        LOG.info("Exiting normally");
        System.exit(0);
    }
 
    protected void initializeAndRun(String[] args)
        throws ConfigException, IOException
    {
        try {
            ManagedUtil.registerLog4jMBeans();
        } catch (JMException e) {
            LOG.warn("Unable to register log4j JMX control", e);
        }
 
        //再次进行配置文件zoo.cfg的解析
        ServerConfig config = new ServerConfig();
        if (args.length == 1) {
            config.parse(args[0]);
        } else {
            config.parse(args);
        }
 
        //创建服务器实例ZooKeeperServer。
        runFromConfig(config);
    }
 
    /**
     * Run from a ServerConfig.
     * @param config ServerConfig to use.
     * @throws IOException
     */
    public void runFromConfig(ServerConfig config) throws IOException {
        LOG.info("Starting server");
        try {
            // Note that this thread isn't going to be doing anything else,
            // so rather than spawning another thread, we will just call
            // run() in this thread.
            // create a file logger url from the command line args
            ZooKeeperServer zkServer = new ZooKeeperServer();  //创建服务器实例ZooKeeperServer。
 
            //创建ZooKeeper数据管理器FileTxnSnapLog
            FileTxnSnapLog ftxn = new FileTxnSnapLog(new
                   File(config.dataLogDir), new File(config.dataDir));
            zkServer.setTxnLogFactory(ftxn);
  
            //设置服务器tickTime和会话超时时间限制
            zkServer.setTickTime(config.tickTime);
            zkServer.setMinSessionTimeout(config.minSessionTimeout);
            zkServer.setMaxSessionTimeout(config.maxSessionTimeout);
  
            //创建ServerCnxnFactory
            cnxnFactory = ServerCnxnFactory.createFactory();
  
            //初始化ServerCnxnFactory
            cnxnFactory.configure(config.getClientPortAddress(),
                    config.getMaxClientCnxns());
  
            //启动ServerCnxnFactory主线程 以及后续操作。。
            cnxnFactory.startup(zkServer);
            cnxnFactory.join();
            if (zkServer.isRunning()) {
                zkServer.shutdown();
            }
        } catch (InterruptedException e) {
            // warn, but generally this is ok
            LOG.warn("Server interrupted", e);
        }
    }
 
    /**
     * Shutdown the serving instance
     */
    protected void shutdown() {
        cnxnFactory.shutdown();
    }
}

1.2 初始化

初始化的步骤如下。

(1)创建服务器统计器ServerStats。

       ServerStats是ZooKeeper服务器运行时的统计器,包含了最基本的运行时通信。ZooKeeper服务器基本统计信息如下表。


(2)创建ZooKeeper数据管理器FileTxnSnapLog。

        FileTxnSnapLog是ZooKeeper上层服务与底层数据存储之间的对接层,提供了一系列操作数据文件的接口,包括事务日志文件和快照文件。ZooKeeper根据zoo.cfg文件解析出的快照数据目录dataDIr和事务日志目录dataLodDir来创建FIleTxnSnapLog。

(3)设置服务器tickTime和会话超时时间限制。

(4)创建ServerCnxnFactory。

       从3.4.0之后,使用者可以通过设置系统属性zookeeper.serverCnxnFactory 来指定使用ZooKeeper自己实现的NIO还是使用Netty框架来作为ZooKeeper服务端网络连接工厂。

public abstract class ServerCnxnFactory {
 
    public static final String ZOOKEEPER_SERVER_CNXN_FACTORY = "zookeeper.serverCnxnFactory";
    public abstract int getLocalPort();
    public abstract Iterable getConnections();
    public abstract void closeSession(long sessionId);
    public abstract void configure(InetSocketAddress addr,
                                   int maxClientCnxns) throws IOException;
    protected SaslServerCallbackHandler saslServerCallbackHandler;
    public Login login;
    /** Maximum number of connections allowed from particular host (ip) */
    public abstract int getMaxClientCnxnsPerHost();
    /** Maximum number of connections allowed from particular host (ip) */
    public abstract void setMaxClientCnxnsPerHost(int max);
 
    public abstract void startup(ZooKeeperServer zkServer)
        throws IOException, InterruptedException;
 
    public abstract void join() throws InterruptedException;
    public abstract void shutdown();
    public abstract void start();
 
    protected ZooKeeperServer zkServer;
    
    //注册ZooKeeper服务器实例。
    final public void setZooKeeperServer(ZooKeeperServer zk) {
        this.zkServer = zk;
        if (zk != null) {
            zk.setServerCnxnFactory(this);
        }
    }
     
    //创建ServerCnxnFactory。
    static public ServerCnxnFactory createFactory() throws IOException {
        String serverCnxnFactoryName =
            System.getProperty(ZOOKEEPER_SERVER_CNXN_FACTORY);
        if (serverCnxnFactoryName == null) {
            serverCnxnFactoryName = NIOServerCnxnFactory.class.getName();
        }
        try {
            return (ServerCnxnFactory) Class.forName(serverCnxnFactoryName)
                                                .newInstance();
        } catch (Exception e) {
            IOException ioe = new IOException("Couldn't instantiate "
                    + serverCnxnFactoryName);
            ioe.initCause(e);
            throw ioe;
        }
    }
}
public class NIOServerCnxnFactory extends ServerCnxnFactory implements Runnable {
 
    ...  ...
    Thread thread;
    @Override
    //初始化ServerCnxnFactory
    //ZooKeeper首先会初始化一个Thread,作为整个ServerCnxnFactory的主线程,然后再初始化NIO服务器
    public void configure(InetSocketAddress addr, int maxcc) throws IOException {
        configureSaslLogin();
 
        thread = new Thread(this, "NIOServerCxn.Factory:" + addr);
        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);
    }
 
   
    @Override
    //启动ServerCnxnFactory主线程
    public void start() {
        // ensure thread is started once and only once
        if (thread.getState() == Thread.State.NEW) {
            thread.start();
        }
    }
 
    @Override
    public void startup(ZooKeeperServer zks) throws IOException,
            InterruptedException {
        start();  //启动ServerCnxnFactory主线
        zks.startdata(); //恢复本地数据。
        zks.startup();
        setZooKeeperServer(zks);
    }
 
    // 启动主线程执行 run方法
    public void run() {
        while (!ss.socket().isClosed()) {
            try {
                selector.select(1000);
                Set selected;
                synchronized (this) {
                    selected = selector.selectedKeys();
                }
                ArrayList selectedList = new ArrayList(
                        selected);
                Collections.shuffle(selectedList);
                for (SelectionKey k : selectedList) {
                    if ((k.readyOps() & SelectionKey.OP_ACCEPT) != 0) {
                        SocketChannel sc = ((ServerSocketChannel) k
                                .channel()).accept();
                        InetAddress ia = sc.socket().getInetAddress();
                        int cnxncount = getClientCnxnCount(ia);
                        if (maxClientCnxns > 0 && cnxncount >= maxClientCnxns){
                            LOG.warn("Too many connections from " + ia
                                     + " - max is " + maxClientCnxns );
                            sc.close();
                        } else {
                            LOG.info("Accepted socket connection from "
                                     + sc.socket().getRemoteSocketAddress());
                            sc.configureBlocking(false);
                            SelectionKey sk = sc.register(selector,
                                    SelectionKey.OP_READ);
                            NIOServerCnxn cnxn = createConnection(sc, sk);
                            sk.attach(cnxn);
                            addCnxn(cnxn);
                        }
                    } else if ((k.readyOps() & (SelectionKey.OP_READ | SelectionKey.OP_WRITE)) != 0) {
                        NIOServerCnxn c = (NIOServerCnxn) k.attachment();
                        c.doIO(k);
                    } else {
                        if (LOG.isDebugEnabled()) {
                            LOG.debug("Unexpected ops in select "
                                      + k.readyOps());
                        }
                    }
                }
                selected.clear();
            } catch (RuntimeException e) {
                LOG.warn("Ignoring unexpected runtime exception", e);
            } catch (Exception e) {
                LOG.warn("Ignoring exception", e);
            }
        }
        closeAll();
        LOG.info("NIOServerCnxn factory exited run method");
    }
}

(5)初始化ServerCnxnFactory。

       ZooKeeper首先会初始化一个Thread,作为整个ServerCnxnFactory的主线程,然后再初始化NIO服务器。

(6)启动ServerCnxnFactory主线程。

       启动(5)中已经初始化的主线程ServerCnxnFactory的主逻辑(run方法)。需要注意的是,虽然ZooKeeper的NIO服务器已经对外开放,客户端能够访问到ZooKeeper的客户端服务端口2181,但此时ZooKeeper服务器是无法正常处理客户端请求的。

//恢复本地数据。
public void startdata()
throws IOException, InterruptedException {
    //check to see if zkDb is not null
    if (zkDb == null) {
        zkDb = new ZKDatabase(this.txnLogFactory);
    } 
    if (!zkDb.isInitialized()) {
        loadData();
    }
}
  
  
public void startup() {       
    if (sessionTracker == null) {
        createSessionTracker(); //创建并启动会话管理
    }
    startSessionTracker();   //开启会话管理器
    setupRequestProcessors();  //初始化ZooKeeper的请求处理链
 
    registerJMX();  //注册JMX服务。
 
    synchronized (this) {
        running = true;
        notifyAll();
    }
}
  
//创建并启动会话管理
protected void createSessionTracker() {
    sessionTracker = new SessionTrackerImpl(this, zkDb.getSessionWithTimeOuts(),
            tickTime, 1);
}
//开启会话管理器
protected void startSessionTracker() {
    ((SessionTrackerImpl)sessionTracker).start();
}
  
//初始化ZooKeeper的请求处理链
protected void setupRequestProcessors() {
    RequestProcessor finalProcessor = new FinalRequestProcessor(this);
    RequestProcessor syncProcessor = new SyncRequestProcessor(this,
            finalProcessor);
    ((SyncRequestProcessor)syncProcessor).start();
    firstProcessor = new PrepRequestProcessor(this, syncProcessor);
    ((PrepRequestProcessor)firstProcessor).start();
}

(7)恢复本地数据。

       每次在ZooKeeper启动的时候,都需要从本地快照文件和事务日志文件中进行数据恢复。

(8)创建并启动会话管理。

     在ZooKeeper启动阶段,会创建一个会话管理器SessionTracker.SessionTracker主要负责ZooKeeper服务端的会话管理。创建SessionTracker的时候,会初始化expirationInterval、nextExpirationTime和SessionWithTimeout,同时还会计算出一个初始化的sessionId。

(9)初始化ZooKeeper的请求处理链。

    ZooKeeper的请求处理方式是典型的责任链模式的实现,在ZooKeeper服务器上,会有多个请求处理器依次来处理一个客户端请求。在服务器启动的时候,会将这些请求处理器串联起来形成一个请求处理链。单机版服务器的请求处理链包括PrepRequestProcessor、syncRequestProcessor和FinalRequestProcessor单个请求处理器,如下如所示。

(10) 注册JMX服务。

      ZooKeeper会将服务器运行的一些信息以JML的方式暴露给外部。

(11)注册ZooKeeper服务器实例。

     在(6)中,ZooKeeper已经将ServerCnxnFactory主线程启动,但是同时我们提到此时ZooKeeper依旧无法处理客户端请求,原因就是此时网络层尚不能够访问ZooKeeper服务器实例。在经过后续步骤的初始化后,ZooKeeper服务器实例已经初始化完毕,只需注册给ServerCnxnFactory即可,之后,ZooKeeper就可以对外提供正常服务了。


参考 《从Paxos到ZooKeeper分布式一致性原理与实践》


你可能感兴趣的:(分布式,zookeeper,分布式)