1.引入
在前面我们已经介绍了zk的单机启动,其过程也是比较简单,主要是启动zk服务,zk admin服务,创建管理快照和事务日志的FileTxnSnapLog对象等。现在我们来说一下zk集群启动过程,这也是zk生产环境启动服务的方式。
2.从QuorumPeerMain.main说起
单机启动和集群启动的都是从QuorumPeerMain.main这个方法开始,只是由于配置文件不同,最终会运行在不同的环境中,单机启动不用设置和多节点相关的配置,比如zoo.cfg配置文件示例:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper
clientPort=2181
如果要启动集群服务,可以使用如下配置:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper
clientPort=2181
# myid=1和2的选举和内部通讯地址
server.1=localhost:2888:3888
server.2=localhost:2889:3889
可以看出,集群配置明显需要指定多个节点之间的配置,所有在执行QuorumPeerMain.main方法时,我们需要传入类似下面的配置参数。
根据前面单机启动,我们知道最终会根据参数情况,执行不同的zk服务(启动前,配置解析和事务日志与快照清理等操作跳过)
// 当配置了多节点信息,config.isDistributed()=true
if (args.length == 1 && config.isDistributed()) {
// 分布式模式
runFromConfig(config);
} else {
// 单机模式
ZooKeeperServerMain.main(args);
}
如果以集群方式运行,最终会调用QuorumPeerMain.runFromConfig方法。这个方法执行过程如下:
- 注册jmx
- 创建服务端/安全协议的服务端对象,用于接口zk客户端的请求
- 创建quorumPeer对象(quorumPeer看做一个zk节点),设置事务快照操作对象,选举方式,认证服务等属性
- 启动节点服务(重点)
public void runFromConfig(QuorumPeerConfig config)
throws IOException, AdminServerException
{
try {
// 注册jmx
ManagedUtil.registerLog4jMBeans();
} catch (JMException e) {
LOG.warn("Unable to register log4j JMX control", e);
}
LOG.info("Starting quorum peer");
try {
ServerCnxnFactory cnxnFactory = null;
ServerCnxnFactory secureCnxnFactory = null;
if (config.getClientPortAddress() != null) {
cnxnFactory = ServerCnxnFactory.createFactory();
// 配置客户端连接端口
cnxnFactory.configure(config.getClientPortAddress(),
config.getMaxClientCnxns(),
false);
}
if (config.getSecureClientPortAddress() != null) {
secureCnxnFactory = ServerCnxnFactory.createFactory();
// 配置安全连接端口
secureCnxnFactory.configure(config.getSecureClientPortAddress(),
config.getMaxClientCnxns(),
true);
}
// ------------初始化当前zk服务节点的配置----------------
quorumPeer = getQuorumPeer();
// 设置数据和快照操作
quorumPeer.setTxnFactory(new FileTxnSnapLog(
config.getDataLogDir(),
config.getDataDir()));
quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled());
quorumPeer.enableLocalSessionsUpgrading(
config.isLocalSessionsUpgradingEnabled());
//quorumPeer.setQuorumPeers(config.getAllMembers());
// 选举类型
quorumPeer.setElectionType(config.getElectionAlg());
// server Id
quorumPeer.setMyid(config.getServerId());
quorumPeer.setTickTime(config.getTickTime());
quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout());
quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout());
quorumPeer.setInitLimit(config.getInitLimit());
quorumPeer.setSyncLimit(config.getSyncLimit());
quorumPeer.setConfigFileName(config.getConfigFilename());
// 设置zk的节点数据库
quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory()));
quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false);
if (config.getLastSeenQuorumVerifier()!=null) {
quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false);
}
// 初始化zk数据库
quorumPeer.initConfigInZKDatabase();
quorumPeer.setCnxnFactory(cnxnFactory);
quorumPeer.setSecureCnxnFactory(secureCnxnFactory);
quorumPeer.setSslQuorum(config.isSslQuorum());
quorumPeer.setUsePortUnification(config.shouldUsePortUnification());
quorumPeer.setLearnerType(config.getPeerType());
quorumPeer.setSyncEnabled(config.getSyncEnabled());
quorumPeer.setQuorumListenOnAllIPs(config.getQuorumListenOnAllIPs());
if (config.sslQuorumReloadCertFiles) {
quorumPeer.getX509Util().enableCertFileReloading();
}
// sets quorum sasl authentication configurations
quorumPeer.setQuorumSaslEnabled(config.quorumEnableSasl);
if(quorumPeer.isQuorumSaslAuthEnabled()){
quorumPeer.setQuorumServerSaslRequired(config.quorumServerRequireSasl);
quorumPeer.setQuorumLearnerSaslRequired(config.quorumLearnerRequireSasl);
quorumPeer.setQuorumServicePrincipal(config.quorumServicePrincipal);
quorumPeer.setQuorumServerLoginContext(config.quorumServerLoginContext);
quorumPeer.setQuorumLearnerLoginContext(config.quorumLearnerLoginContext);
}
quorumPeer.setQuorumCnxnThreadsSize(config.quorumCnxnThreadsSize);
// -------------初始化当前zk服务节点的配置---------------
quorumPeer.initialize();
quorumPeer.start();
quorumPeer.join();
} catch (InterruptedException e) {
// warn, but generally this is ok
LOG.warn("Quorum Peer interrupted", e);
}
}
quorumPeer.start是实现的重点,所以看看这个方法是如何实现的:
- 加载zk数据库,由于zk服务运行过程中会将数据保存到快照和事务日志文件中,所以在启动时需要将这些磁盘上的数据加载到zk内存数据库中
- 启动服务端(含安全协议服务端)
- 启动zk admin服务(即每一个zk服务节点上都有一个zk admin服务)
- 选举leader
public synchronized void start() {
// 如果不在peer列表中,抛异常
if (!getView().containsKey(myid)) {
throw new RuntimeException("My id " + myid + " not in the peer list");
}
// 加载zk数据库
loadDataBase();
// 启动连接服务端
startServerCnxnFactory();
try {
adminServer.start();
} catch (AdminServerException e) {
LOG.warn("Problem starting AdminServer", e);
System.out.println(e);
}
// 启动之后马上进行选举,主要是创建选举必须的环境,比如:启动相关线程
startLeaderElection();
// 执行选举逻辑
super.start();
}
zk的选举是一个非常复杂且重要的过程,在后面博客中会专门介绍。