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