本文将从启动类开始详细分析zookeeper的启动流程:

  1. 加载配置的过程
  2. 集群启动过程
  3. 单机版启动过程

启动类

org.apache.zookeeper.server.quorum.QuorumPeerMain类。

用于启动zookeeper服务,第一个参数用来指定配置文件,配置文件properties格式,例如以下配置参数:

  • dataDir - 数据存储目录
  • dataLogDir - txnlog(事务日志)存储目录,默认dataDir
  • clientPort - 接收客户端连接的端口,例如2181
  • tickTime - leader做quorum验证的周期时长,默认3000ms
  • initLimit - leader等待follower连接、数据同步ack的最大tick数量
  • syncLimit - leader发送同步数据等待ack的最大tick数量
  • server.id - 用于quorum协议的host:port[:port]格式的server列表

加载配置

QuorumPeerConfig config = new QuorumPeerConfig();
if (args.length == 1) {
config.parse(args[0]);
}

args[0]是配置文件名。

  1. 加载普通配置
  2. 加载quorumPeer配置
  3. 从dynamic文件加载quorumVerifier配置,zk会在processReconfig时生成dynamic文件,默认文件名zoo.cfg.dynamic.${version}格式,默认不开启Reconfig功能
  4. 从dynamic文件加载lastQuorumPeer配置,同上,默认zoo.cfg.dynamic.next文件

加载普通配置

QuorumPeerConfig封装以下字段:

// 监听客户端连接的地址,使用clientPort和clientPortAddress配置确定,用来创建cnxnFactory
protected InetSocketAddress clientPortAddress;
// 用来创建secureCnxnFactory,使用secureClientPort和secureClientPortAddress配置确定
protected InetSocketAddress secureClientPortAddress;
// quorum使用ssl通信
protected boolean sslQuorum = false;
// portUnification配置,使用UnifiedServerSocket创建套接字
protected boolean shouldUsePortUnification = false;
// 用来创建ObserverMaster,该组件可以实现链式的数据复制,减小leader的负载
protected int observerMasterPort;
// 自动重新加载ssl文件
protected boolean sslQuorumReloadCertFiles = false;
// 数据目录和事务log目录
protected File dataDir;
protected File dataLogDir;
// dynamicConfig文件名
protected String dynamicConfigFileStr = null;
// 配置文件名,非配置参数
protected String configFileStr = null;
// leader做quorum验证的周期时长,默认300ms
protected int tickTime = ZooKeeperServer.DEFAULT_TICK_TIME;
// 单个addr的client最大连接数
protected int maxClientCnxns = 60;
// 超时时长
protected int minSessionTimeout = -1;
protected int maxSessionTimeout = -1;
// metricsProvider.className配置,MetricsProvider实现类全名
protected String metricsProviderClassName = DefaultMetricsProvider.class.getName();
// 封装metricsProvider.*下面的配置
protected Properties metricsProviderConfiguration = new Properties();
// 本地session配置
protected boolean localSessionsEnabled = false;
protected boolean localSessionsUpgradingEnabled = false;
// client连接的backlog数设置
protected int clientPortListenBacklog = -1;
// leader等待follower连接、数据同步ack的最大tick数量
protected int initLimit;
// leader发送同步数据等待ack的最大tick数量
protected int syncLimit;
// 大于0时用来计算连接leader的超时时长
protected int connectToLearnerMasterLimit;
// 必须是3
protected int electionAlg = 3;
// 未使用的配置
protected int electionPort = 2182;
// 监听所有IP地址
protected boolean quorumListenOnAllIPs = false;
// myid配置
protected long serverId = UNSET_SERVERID; protected QuorumVerifier quorumVerifier = null, lastSeenQuorumVerifier = null; // autopurge.snapRetainCount配置
protected int snapRetainCount = 3;
// autopurge.purgeInterval配置
protected int purgeInterval = 0;
// 开启同步
protected boolean syncEnabled = true; protected String initialConfig; // PARTICIPANT|OBSERVER
protected LearnerType peerType = LearnerType.PARTICIPANT; /**
* Configurations for the quorumpeer-to-quorumpeer sasl authentication
*/
protected boolean quorumServerRequireSasl = false;
protected boolean quorumLearnerRequireSasl = false;
protected boolean quorumEnableSasl = false;
protected String quorumServicePrincipal = QuorumAuth.QUORUM_KERBEROS_SERVICE_PRINCIPAL_DEFAULT_VALUE;
protected String quorumLearnerLoginContext = QuorumAuth.QUORUM_LEARNER_SASL_LOGIN_CONTEXT_DFAULT_VALUE;
protected String quorumServerLoginContext = QuorumAuth.QUORUM_SERVER_SASL_LOGIN_CONTEXT_DFAULT_VALUE;
protected int quorumCnxnThreadsSize; // multi address related configs
// multiAddress.enabled配置
private boolean multiAddressEnabled = Boolean.parseBoolean(
System.getProperty(QuorumPeer.CONFIG_KEY_MULTI_ADDRESS_ENABLED, "false"));
// multiAddress.reachabilityCheckEnabled配置
private boolean multiAddressReachabilityCheckEnabled = Boolean.parseBoolean(
System.getProperty(QuorumPeer.CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_ENABLED, "true"));
// multiAddress.reachabilityCheckTimeoutMs配置
private int multiAddressReachabilityCheckTimeoutMs = Integer.parseInt(
System.getProperty(QuorumPeer.CONFIG_KEY_MULTI_ADDRESS_REACHABILITY_CHECK_TIMEOUT_MS,
String.valueOf(1000))); // 创建QuorumVerifier时使用,不为null时会创建QuorumOracleMaj
protected String oraclePath; // Minimum snapshot retain count.
private final int MIN_SNAP_RETAIN_COUNT = 3;
// JVM Pause Monitor feature switch
protected boolean jvmPauseMonitorToRun = false;
// JVM Pause Monitor warn threshold in ms
protected long jvmPauseWarnThresholdMs = JvmPauseMonitor.WARN_THRESHOLD_DEFAULT;
// JVM Pause Monitor info threshold in ms
protected long jvmPauseInfoThresholdMs = JvmPauseMonitor.INFO_THRESHOLD_DEFAULT;
// JVM Pause Monitor sleep time in ms
protected long jvmPauseSleepTimeMs = JvmPauseMonitor.SLEEP_TIME_MS_DEFAULT;

在parse方法的最后一个else分支,会将其他配置前缀zookeeper.之后设置到System环境变量中:

System.setProperty("zookeeper." + key, value);

比如一些SSL相关配置参数:

ssl.quorum.keyStore.location=/path/to/keystore.jks
ssl.quorum.keyStore.password=password
ssl.quorum.trustStore.location=/path/to/truststore.jks
ssl.quorum.trustStore.password=password

解析配置文件之后,会根据ssl相关参数做ssl配置:

if (this.secureClientPortAddress != null) {
configureSSLAuth();
}

默认会将X509AuthenticationProvider作为ssl认证组件:

// key = "zookeeper.authProvider.x509"
System.setProperty(ProviderRegistry.AUTHPROVIDER_PROPERTY_PREFIX + "x509",
"org.apache.zookeeper.server.auth.X509AuthenticationProvider");

其余的都是参数验证代码,不详细说明。

加载quorumPeer配置

// backward compatibility - dynamic configuration in the same file as
// static configuration params see writeDynamicConfig()
if (dynamicConfigFileStr == null) {
// 解析quorum配置
setupQuorumPeerConfig(zkProp, true);
if (isDistributed() && isReconfigEnabled()) { // 默认reconfigEnabled==false分支进不来
// we don't backup static config for standalone mode.
// we also don't backup if reconfig feature is disabled.
// 备份zoo.cfg到zoo.cfg.bak
backupOldConfig();
}
}

解析quorum配置:

void setupQuorumPeerConfig(Properties prop,
boolean configBackwardCompatibilityMode) throws IOException, ConfigException {
quorumVerifier = parseDynamicConfig(
prop, electionAlg, true, configBackwardCompatibilityMode, oraclePath);
// 读取${dataDir}/myid文件,给serverId赋值
setupMyId();
// 对比clientPortAddress配置与quorum配置进行重新赋值
setupClientPort();
// 对比peerType配置与quorum配置进行重新赋值
setupPeerType();
checkValidity(); // 参数验证
}

parseDynamicConfig方法需要看一下:

public static QuorumVerifier parseDynamicConfig(
Properties dynamicConfigProp, int eAlg, boolean warnings,
boolean configBackwardCompatibilityMode, String oraclePath) throws IOException, ConfigException {
boolean isHierarchical = false;
for (Entry<Object, Object> entry : dynamicConfigProp.entrySet()) {
String key = entry.getKey().toString().trim();
// group.*和weight.*的参数配置
if (key.startsWith("group") || key.startsWith("weight")) {
isHierarchical = true;
} else if (!configBackwardCompatibilityMode &&
!key.startsWith("server.") && !key.equals("version")) {
throw new ConfigException("Unrecognised parameter: " + key);
}
} QuorumVerifier qv = createQuorumVerifier(dynamicConfigProp, isHierarchical, oraclePath); // 验证 略
} private static QuorumVerifier createQuorumVerifier(
Properties dynamicConfigProp, boolean isHierarchical, String oraclePath) throws ConfigException {
if (oraclePath == null) {
return createQuorumVerifier(dynamicConfigProp, isHierarchical);
} else {
return new QuorumOracleMaj(dynamicConfigProp, oraclePath);
}
} private static QuorumVerifier createQuorumVerifier(
Properties dynamicConfigProp, boolean isHierarchical) throws ConfigException {
if (isHierarchical) {
return new QuorumHierarchical(dynamicConfigProp);
} else {
return new QuorumMaj(dynamicConfigProp);
}
}

QuorumMaj

解析集群配置,配置形如:

server.1=server_config;client_config
server.2=server_config;client_config
server.3=server_config;client_config # 配置两个也能启动,但是只能提供副本能力,无法保证高可用 # 使用分号分隔server_config和client_config # 1. server_config格式:
# host:quorumPort:electionPort 或 host:quorumPort:electionPort:type
# 可以配置多个,使用|分隔
# 例如:
# 127.0.0.1:2888:3888:PARTICIPANT # 2. client_config可以没有,格式: port或host:port

构造方法:

public QuorumMaj(Properties props) throws ConfigException {
for (Entry<Object, Object> entry : props.entrySet()) {
String key = entry.getKey().toString();
String value = entry.getValue().toString();
if (key.startsWith("server.")) {
int dot = key.indexOf('.');
// 获取serverId
long sid = Long.parseLong(key.substring(dot + 1));
// 创建QuorumServer对象,解析value字符串
// value格式: server_config或server_config;client_config
// server_config格式是使用|分隔的列表,每个元素是:
// host:quorumPort:electionPort或host:quorumPort:electionPort:type
// client_config格式: port或host:port
QuorumServer qs = new QuorumServer(sid, value);
allMembers.put(Long.valueOf(sid), qs);
if (qs.type == LearnerType.PARTICIPANT) {
votingMembers.put(Long.valueOf(sid), qs); // 投票成员
} else {
observingMembers.put(Long.valueOf(sid), qs); // observer成员
}
} else if (key.equals("version")) {
version = Long.parseLong(value, 16);
}
}
half = votingMembers.size() / 2; // 成员半数,例如5/2=2
}

QuorumHierarchical

比QuorumMaj多了group和weight等特性。

从dynamic文件加载quorumVerifier配置

用于加载quorumVerifier信息:从dynamicConfigFileStr参数指定的文件加载quorumPeer配置,方式与上一小节一样。

从dynamic文件加载lastQuorumPeer配置

用于加载lastSeenQuorumVerifier信息:从zoo.cfg.dynamic.next文件加载lastSeenQuorumVerifier配置,方式与上一小节一样。

创建并启动DatadirCleanupManager

默认配置时不启动。

// Start and schedule the purge task
DatadirCleanupManager purgeMgr = new DatadirCleanupManager(
config.getDataDir(),
config.getDataLogDir(),
config.getSnapRetainCount(), // 默认3
config.getPurgeInterval());
purgeMgr.start();

启动周期任务:

public void start() {
if (PurgeTaskStatus.STARTED == purgeTaskStatus) {
LOG.warn("Purge task is already running.");
return;
}
// 默认不启动
if (purgeInterval <= 0) {
LOG.info("Purge task is not scheduled.");
return;
} timer = new Timer("PurgeTask", true);
TimerTask task = new PurgeTask(dataLogDir, snapDir, snapRetainCount);
timer.scheduleAtFixedRate(task, 0, TimeUnit.HOURS.toMillis(purgeInterval)); purgeTaskStatus = PurgeTaskStatus.STARTED;
}

清理逻辑在PurgeTask的run方法:

public void run() {
try {
PurgeTxnLog.purge(logsDir, snapsDir, snapRetainCount);
} catch (Exception e) {}
}

启动集群

if (args.length == 1 && config.isDistributed()) {
runFromConfig(config);
}

isDistributed判断:

public boolean isDistributed() {
return quorumVerifier != null && (!standaloneEnabled || quorumVerifier.getVotingMembers().size() > 1);
} // standaloneEnabled默认true

创建并启动MetricsProvider

final MetricsProvider metricsProvider;
try {
metricsProvider = MetricsProviderBootstrap.startMetricsProvider(
config.getMetricsProviderClassName(), // DefaultMetricsProvider
config.getMetricsProviderConfiguration());
} catch (MetricsProviderLifeCycleException error) {
throw new IOException("Cannot boot MetricsProvider " + config.getMetricsProviderClassName(), error);
}
// 注册到全局
ServerMetrics.metricsProviderInitialized(metricsProvider);

创建ServerCnxnFactory

ServerCnxnFactory cnxnFactory = null;
ServerCnxnFactory secureCnxnFactory = null; if (config.getClientPortAddress() != null) {
// 默认使用NIOServerCnxnFactory实现类
cnxnFactory = ServerCnxnFactory.createFactory();
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns(), config.getClientPortListenBacklog(), false);
} if (config.getSecureClientPortAddress() != null) {
secureCnxnFactory = ServerCnxnFactory.createFactory();
secureCnxnFactory.configure(config.getSecureClientPortAddress(),
config.getMaxClientCnxns(), config.getClientPortListenBacklog(), true);
}

ServerCnxnFactory有两个主要的子类:

  • NIOServerCnxnFactory
  • NettyServerCnxnFactory

默认使用NIOServerCnxnFactory实现类,可以使用-Dzookeeper.serverCnxnFactory=xx来修改:

-Dzookeeper.serverCnxnFactory=org.apache.zookeeper.server.NettyServerCnxnFactory

ServerCnxnFactory

用于接收客户端连接、管理客户端session、处理客户端请求。

NIOServerCnxnFactory

基于NIO的非阻塞、多线程的ServerCnxnFactory实现类,多线程之间通过queue通信:

  • 1个accept线程,用来接收客户端连接,交给selector线程处理
  • 1-N个selector线程,每个线程会select 1/N个连接,多个selector线程的原因是,由于有大量连接,select()可能会成为性能瓶颈
  • 0-M个socket IO worker线程,做socket读写,如果配置为0则selector线程来做IO
  • 1个清理线程,用于关闭空闲连接

线程数量分配示例:32核的机器,1accept线程,1个清理线程,4个selector线程,64个worker线程。

configure方法:

  • 不支持ssl

  • 创建ConnectionExpirerThread线程

  • 根据核数确定各个线程的数量

    int numCores = Runtime.getRuntime().availableProcessors();
    // 32 cores sweet spot seems to be 4 selector threads
    numSelectorThreads = Integer.getInteger(
    ZOOKEEPER_NIO_NUM_SELECTOR_THREADS,
    Math.max((int) Math.sqrt((float) numCores / 2), 1)); // 64
    numWorkerThreads = Integer.getInteger(ZOOKEEPER_NIO_NUM_WORKER_THREADS, 2 * numCores);
  • 创建SelectorThread线程

  • 创建ServerSocketChannel、启动监听、设置非阻塞

  • 创建AcceptThread线程

start方法启动各种线程:

  • acceptThread
  • selectorThreads
  • workerPool
  • expirerThread

NettyServerCnxnFactory

基于Netty的ServerCnxnFactory实现,使用CnxnChannelHandler作为业务处理器。

后续会有文章详细分析。

创建并启动QuorumPeer

管理quorum协议,服务器可能处于以下三种状态:

  • Leader选举 - 每个服务器将选出一个leader,最初都会选自己
  • Follower节点 - 将与Leader同步并复制所有事务
  • Leader节点 - 处理请求并将其转发给Follower节点,大多数Follower节点必须同步,该请求才能被提交

创建QuorumPeer并使用QuorumPeerConfig为其设置属性:

public QuorumPeer() throws SaslException {
super("QuorumPeer");
quorumStats = new QuorumStats(this);
jmxRemotePeerBean = new HashMap<>();
adminServer = AdminServerFactory.createAdminServer(); // http管理的服务,使用JettyAdminServer实现类
x509Util = createX509Util();
initialize();
reconfigEnabled = QuorumPeerConfig.isReconfigEnabled(); // 默认false不开启Reconfig功能
}

下面记录一下重要的步骤。

创建FileTxnSnapLog

quorumPeer.setTxnFactory(new FileTxnSnapLog(config.getDataLogDir(), config.getDataDir()));

FileTxnSnapLog类:操作TxnLog和SnapShot的入口类。

此步骤会创建dataDir和snapDir目录、判断数据目录可写、创建txnLog和snapLog对象访问数据文件。

创建并初始化ZKDatabase

quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
quorumPeer.initConfigInZKDatabase();

维护zookeeper服务器内存数据库,包括session、dataTree和committedlog数据,从磁盘读取日志和快照后启动。

内部使用DataTree存储数据,先看一下创建和初始化阶段的代码。

构造方法:创建DataTree对象,创建/zookeeper/quota、/zookeeper/config节点,创建dataWatches和childWatches对象(使用WatchManager实现类)。

initConfigInZKDatabase方法:

public synchronized void initConfigInZKDatabase() {
if (zkDb != null) {
zkDb.initConfigInZKDatabase(getQuorumVerifier());
}
} public synchronized void initConfigInZKDatabase(QuorumVerifier qv) {
try {
if (this.dataTree.getNode(ZooDefs.CONFIG_NODE) == null) {
// should only happen during upgrade
this.dataTree.addConfigNode();
}
// 把当前QuorumVerifier保存到/zookeeper/config中
// qv.toString()格式如下:
// server.1=host1:2888:3888:participant;host1:2181\n
// server.2=host2:2888:3888:participant;host2:2181\n
// ...
// version=2
this.dataTree.setData(ZooDefs.CONFIG_NODE,
qv.toString().getBytes(UTF_8), // data
-1, // version
qv.getVersion(), // txid
Time.currentWallTime());
} catch (NoNodeException e) {}
}

设置QuorumVerifier

quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
if (config.getLastSeenQuorumVerifier() != null) {
quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
}

初始化启动QuorumPeer

// 初始化QuorumAuthServer
quorumPeer.initialize();
// 启动QuorumPeer
quorumPeer.start();
// 线程阻塞
quorumPeer.join();

启动QuorumPeer方法:

public synchronized void start() {
loadDataBase();
startServerCnxnFactory();
try {
adminServer.start();
} catch (AdminServerException e) {}
startLeaderElection();
startJvmPauseMonitor();
super.start();
}

启动QuorumPeer流程

ZKDatabase加载

从txnlog和snapshot加载dataTree数据:

long zxid = snapLog.restore(dataTree, sessionsWithTimeouts, commitProposalPlaybackListener);
  1. 倒序查找所有snapshot文件,从文件名解析snapZxid作为dataTree的lastProcessedZxid属性,文件内容解析到dataTree中
  2. 如果从snapshot文件未找到数据,则生成snapshot.0文件,将当前dataTree(空的)保存到里面
  3. 使用fastForwardFromEdits方法从txnlog加载数据

获取currentEpoch和acceptedEpoch的值:

// 当前zxid
long lastProcessedZxid = zkDb.getDataTree().lastProcessedZxid;
// 当前epoch = zxid >> 32L
long epochOfZxid = ZxidUtils.getEpochFromZxid(lastProcessedZxid);

从${dataDir}/currentEpoch文件读取currentEpoch值:

currentEpoch = readLongFromFile(CURRENT_EPOCH_FILENAME);
  1. 如果文件不存在,直接使用epochOfZxid作为currentEpoch并保存到文件
  2. 如果currentEpoch比epochOfZxid小,则继续查找${dataDir}/currentEpoch.tmp文件作为currentEpoch保存到文件,如果文件不存在则抛数据异常

从${dataDir}/acceptedEpoch文件读取acceptedEpoch值:

acceptedEpoch = readLongFromFile(ACCEPTED_EPOCH_FILENAME);
  1. 如果文件不存在,直接使用epochOfZxid作为acceptedEpoch并保存到文件
  2. 如果acceptedEpoch比currentEpoch小则抛数据异常

启动serverCnxnFactory

private void startServerCnxnFactory() {
if (cnxnFactory != null) {
cnxnFactory.start(); // NIOServerCnxnFactory在启动阶段会启动内部的4类线程
}
if (secureCnxnFactory != null) {
secureCnxnFactory.start();
}
}

启动AdminServer

默认使用JettyAdminServer实现类,负责提供管理端的http接口。

启动选举

public synchronized void startLeaderElection() {
try {
if (getPeerState() == ServerState.LOOKING) {
// 投自己一票,封装zxid和epoch
currentVote = new Vote(myid, getLastLoggedZxid(), getCurrentEpoch());
}
} catch (IOException e) {
RuntimeException re = new RuntimeException(e.getMessage());
re.setStackTrace(e.getStackTrace());
throw re;
}
// electionType总是3
this.electionAlg = createElectionAlgorithm(electionType);
} protected Election createElectionAlgorithm(int electionAlgorithm) {
Election le = null; // TODO: use a factory rather than a switch
// 可以使用策略模式替换switch语句
switch (electionAlgorithm) {
case 1:
throw new UnsupportedOperationException("Election Algorithm 1 is not supported.");
case 2:
throw new UnsupportedOperationException("Election Algorithm 2 is not supported.");
case 3:
QuorumCnxManager qcm = createCnxnManager();
QuorumCnxManager oldQcm = qcmRef.getAndSet(qcm);
// 关闭oldQcm
if (oldQcm != null) {
oldQcm.halt();
}
// 用来启动ServerSocket监听
QuorumCnxManager.Listener listener = qcm.listener;
if (listener != null) {
listener.start();
FastLeaderElection fle = new FastLeaderElection(this, qcm);
fle.start();
le = fle;
}
break;
default:
assert false;
}
return le;
}

创建QuorumCnxManager对象:

public QuorumCnxManager createCnxnManager() {
// 默认tickTime * syncLimit
// 按照zoo_sample.cfg文件配置是2000 * 5
int timeout = quorumCnxnTimeoutMs > 0 ? quorumCnxnTimeoutMs : this.tickTime * this.syncLimit;
return new QuorumCnxManager(
this,
this.getMyId(),
this.getView(), // serverId->quorumServer
this.authServer,
this.authLearner,
timeout,
this.getQuorumListenOnAllIPs(), // 是否监听所有IP默认false
this.quorumCnxnThreadsSize, // 默认20
this.isQuorumSaslAuthEnabled());
}

QuorumCnxManager类:

This class implements a connection manager for leader election using TCP.
It maintains one connection for every pair of servers. The tricky part is to guarantee that there is exactly one connection for every pair of servers that are operating correctly and that can communicate over the network. If two servers try to start a connection concurrently, then the connection manager uses a very simple tie-breaking mechanism to decide which connection to drop based on the IP addressed of the two parties.
For every peer, the manager maintains a queue of messages to send. If the connection to any particular peer drops, then the sender thread puts the message back on the list. As this implementation currently uses a queue implementation to maintain messages to send to another peer, we add the message to the tail of the queue, thus changing the order of messages. Although this is not a problem for the leader election, it could be a problem when consolidating peer communication. This is to be verified, though.
  1. 维护leader选举时server之间的tcp连接
  2. 确保两个server之间存在一个连接,如果两个server同时建立连接,则始终保留id大的一方建立的连接
  3. 队列缓存待发送的消息

FastLeaderElection类:

  • 使用TCP实现leader选举
  • 使用QuorumCnxManager管理连接
  • 某些参数可以改变选举行为,比如finalizeWait参数决定leader确定之前需要等待的时间

启动线程

QuorumPeer继承了ZooKeeperThread类,最后会使用super.start()启动线程。run方法while循环,根据当前的ServerState执行不同的逻辑。

启动单机版服务

启动入口

在QuorumPeerMain的initializeAndRun阶段:

if (args.length == 1 && config.isDistributed()) {
runFromConfig(config);
} else {
// 启动单机版服务
ZooKeeperServerMain.main(args);
}

ZooKeeperServerMain.main方法:

ZooKeeperServerMain main = new ZooKeeperServerMain();
try {
main.initializeAndRun(args);
}
// 略

initializeAndRun方法:

protected void initializeAndRun(String[] args) throws ConfigException, IOException, AdminServerException {
// 略 ServerConfig config = new ServerConfig();
if (args.length == 1) {
config.parse(args[0]); // args[0]是配置文件
} else {
config.parse(args); // args = {clientPortAddress, dataDir, tickTime, maxClientCnxns}
} runFromConfig(config);
}

启动流程

  1. 创建FileTxnSnapLog对象
  2. 创建ZooKeeperServer对象
  3. 创建并启动AdminServer组件
  4. 创建并启动cnxnFactory和secureCnxnFactory用于接受客户端连接、处理客户端请求,会启动ZooKeeperServer、ZXDatabase等核心组件
  5. 创建并启动ContainerManager组件

zookeeper源码(03)启动流程的更多相关文章

  1. Zookeeper 源码分析-启动

    Zookeeper 源码分析-启动 博客分类: Zookeeper   本文主要介绍了zookeeper启动的过程 运行zkServer.sh start命令可以启动zookeeper.入口的main ...

  2. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(下)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/12/es-code03/ 前提 上篇文章写完了 ES 流程启动的一部分,main 方法都入 ...

  3. 渣渣菜鸡的 ElasticSearch 源码解析 —— 启动流程(上)

    关注我 转载请务必注明原创地址为:http://www.54tianzhisheng.cn/2018/08/11/es-code02/ 前提 上篇文章写了 ElasticSearch 源码解析 -- ...

  4. apiserver源码分析——启动流程

    前言 apiserver是k8s控制面的一个组件,在众多组件中唯一一个对接etcd,对外暴露http服务的形式为k8s中各种资源提供增删改查等服务.它是RESTful风格,每个资源的URI都会形如 / ...

  5. Zookeeper源码(启动+选举)

    简介 关于Zookeeper,目前普遍的应用场景基本作为服务注册中心,用于服务发现.但这只是Zookeeper的一个的功能,根据Apache的官方概述:"The Apache ZooKeep ...

  6. Android4.0源码Launcher启动流程分析【android源码Launcher系列一】

    最近研究ICS4.0的Launcher,发现4.0和2.3有稍微点区别,但是区别不是特别大,所以我就先整理一下Launcher启动的大致流程. Launcher其实是贯彻于手机的整个系统的,时时刻刻都 ...

  7. hadoop源码_hdfs启动流程_3_心跳机制

    hadoop在启动namenode和datanode之后,两者之间是如何联动了?datanode如何向namenode注册?如何汇报数据?namenode又如何向datanode发送命令? 心跳机制基 ...

  8. (三)elasticsearch 源码之启动流程分析

    1.前面我们在<(一)elasticsearch 编译和启动>和 <(二)elasticsearch 源码目录 >简单了解下es(elasticsearch,下同),现在我们来 ...

  9. hadoop源码_hdfs启动流程_2_DataNode

    执行start-dfs.sh脚本后,集群是如何启动的? 本文阅读并注释了start-dfs脚本,以及datanode的启动主要流程流程源码. DataNode 启动流程 脚本代码分析 start-df ...

  10. zookeeper源码分析之五服务端(集群leader)处理请求流程

    leader的实现类为LeaderZooKeeperServer,它间接继承自标准ZookeeperServer.它规定了请求到达leader时需要经历的路径: PrepRequestProcesso ...

随机推荐

  1. Java并发(二十一)----wait notify介绍

    1.小故事 - 为什么需要 wait 由于条件不满足(没烟干不了活啊,等小M把烟送过来),小南不能继续进行计算 但小南如果一直占用着锁,其它人就得一直阻塞,效率太低 于是老王单开了一间休息室(调用 w ...

  2. 监控工具nmon使用方法

    https://blog.csdn.net/linabc123000/article/details/70833427

  3. 零基础电气专业毕业生,花费9.9元自学前端,成都月薪6.5K

    介绍 毕业于成都理工电气专业,大学毕业后进入了一家电气公司,月薪2000元.一直对互联网行业感兴趣,但由于没有相关专业背景,所以一直没有勇气转行. 转行契机 公司的书记想搞一个内部生产管理系统,看我们 ...

  4. 欧拉定理 & 扩展欧拉定理 笔记

    欧拉函数 欧拉函数定义为:\(\varphi(n)\) 表示 \(1 \sim n\) 中所有与 \(n\) 互质的数的个数. 关于欧拉函数有下面的性质和用途: 欧拉函数是积性函数.可以通过这个性质求 ...

  5. Spring Boot3 系列:Spring Boot3 跨域配置 Cors

    目录 什么是CORS? Spring Boot 如何配置CORS? 前端代码 注解配置 全局配置 过滤器配置 注意事项 什么是CORS? CORS,全称是"跨源资源共享"(Cros ...

  6. antd ui的from使用问题

    select 的allowClear失效问题 select的value与allowClear同时使用会导致allowClear失效 解决方法 from包装一层,通过const [form] = For ...

  7. 云图说 | 图解制品仓库服务CodeArts Artifact

    本文分享自华为云社区<[云图说]第277期 图解制品仓库CodeArts Artifact>,作者:阅识风云. 制品仓库服务CodeArts Artifact用于存放源码编译生成的.可运行 ...

  8. 【菜鸟必看】stm32定时器的妙用

    摘要:本文为你带来关于stm32定时器的使用的便利和优势之处. 使用定时器去计算获取一条的时间 一.初步了解定时器 stm32定时器时钟图如下: 定时器2-7:普通定时器定时器1.8:高级定时器 二. ...

  9. 详解驱动开发中内核PE结构VA与FOA转换

    摘要:本文将探索内核中解析PE文件的相关内容. 本文分享自华为云社区<驱动开发:内核PE结构VA与FOA转换>,作者: LyShark . 本章将探索内核中解析PE文件的相关内容,PE文件 ...

  10. 2天完成17TB数据量迁移,华为云数据库是如何做的?

    摘要:童年时候,我们会对着墙上挂着的中国地图,来认识一处处山川河流和城市人文.如今,数字化时代下,传统的地图已经不能满足人们的需求,如何获取各种丰富的地理内容和实时动态信息成为现代人普遍的地理信息诉求 ...