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