在看分布式有关的东西,然后就有用到zookeeper,觉得好奇,就打算把源码看看。
看这篇之前,如果想先对zk有个感性认识,可以看下上一篇,本文主要是基于自己看源码时候的理解,当然可能有些地方理解有误,还有很多没有看懂的地方。但看我这篇,多少会有些理解,比如启动过程
选举过程
集群节点通信
客户端连接
集群节点数据同步
源码构建
这里就不多说了,参考:zookeeper源码构建
ZK集群模式启动过程
1:启动类:QuorumPeerMain(要设置main方法参数,就在zoo.cfg文件的路径)
服务器启动的入口,按照用户的配置启动QuorumPeer
2:org.apache.zookeeper.server.quorum.QuorumPeerConfig#parseDynamicConfig
方法中会创建一个QuorumVerifier(用于检查一个服务器列表能否构成一个可用的服务器集群)
long getWeight(long id);
boolean containsQuorum(Set<Long> set);//投票是否过半就是这里判断的
long getVersion();
void setVersion(long ver);
Map<Long, QuorumServer> getAllMembers();//获取集群中所有的节点。
Map<Long, QuorumServer> getVotingMembers();//获取集群中参与选举的节点。
Map<Long, QuorumServer> getObservingMembers();//获取集群中Observer节点
boolean equals(Object o);
如果zoo.cfg中没有配置分组或者权重,默认实例化的是
`org.apache.zookeeper.server.quorum.flexible.QuorumMaj#QuorumMaj(java.util.Properties)`
并会初始化一些数据,比如总共节点信息等,看下下面源码就好了,其实还是比较容易看懂的。
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('.');
long sid = Long.parseLong(key.substring(dot + 1));
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);
}
} else if (key.equals("version")) {
version = Long.parseLong(value, 16);
}
}
half = votingMembers.size() / 2;
}
public QuorumMaj(Map<Long, QuorumServer> allMembers) {
this.allMembers = allMembers;
for (QuorumServer qs : allMembers.values()) {
if (qs.type == LearnerType.PARTICIPANT) {
votingMembers.put(Long.valueOf(qs.id), qs);
} else {
observingMembers.put(Long.valueOf(qs.id), qs);
}
}
half = votingMembers.size() / 2;
}
3:QuorumPeerConfig
看下名字应该就知道,主要是节点(法定人)有关配置,但是这里要注意一点,就是要在dataDir路径下,要有一个myid,不然就没有serverId,就拿不到QuorumServer
serverId,clientPortAddress,dataDir,dataLogDir等
4:org.apache.zookeeper.server.quorum.QuorumPeerMain#runFromConfig
要集群大于一才会进入这个方法的,否则就是单机了,单机ZooKeeperServerMain是这个启动类。
-
ManagedUtil.registerLog4jMBeans() 注册日志需要的bean
-
创建节点直接的通信:ServerCnxnFactory(管理客户端的连接),默认使用的是NIOServerCnxnFactory。当然也可以自己配置,使用Netty,觉得有必要对NIOServerCnxnFactory进行分析
其实我个人觉得和Netty还是有些现实,毕竟都是基于NIO,首先在Netty中,我们一般也是两个线程组,一个是boss线程组,由于客户端的连接,一个是work线程组用于IO读写。ZK中的NIOServerCnxnFactory其实类似。首先看下
Set<SelectorThread> selectorThreads
,这是一个ZooKeeperThread线程组,就是用来进行读写的,默认数量是CPU核数的两倍(numWorkerThreads),另一个是AcceptThread acceptThread
线程组,我觉得就是用来监听客户端的连接。numWorkerThreads = Integer.getInteger( ZOOKEEPER_NIO_NUM_WORKER_THREADS, 2 * numCores); workerShutdownTimeoutMS = Long.getLong( ZOOKEEPER_NIO_SHUTDOWN_TIMEOUT, 5000); for(int i=0; i<numSelectorThreads; ++i) { selectorThreads.add(new SelectorThread(i)); } //ServerSocketChannle this.ss = ServerSocketChannel.open(); ss.socket().setReuseAddress(true); LOG.info("binding to port " + addr); ss.socket().bind(addr); ss.configureBlocking(false); acceptThread = new AcceptThread(ss, addr, selectorThreads);
-
初始化QuorumPeer,这也是一个ZK线程,看下initialize方法
public void initialize() throws SaslException { // init quorum auth server & learner if (isQuorumSaslAuthEnabled()) { Set<String> authzHosts = new HashSet<String>(); for (QuorumServer qs : getView().values()) { authzHosts.add(qs.hostname); } authServer = new SaslQuorumAuthServer(isQuorumServerSaslAuthRequired(), quorumServerLoginContext, authzHosts); authLearner = new SaslQuorumAuthLearner(isQuorumLearnerSaslAuthRequired(), quorumServicePrincipal, quorumLearnerLoginContext); } else { //这两个东西目前还不知道是干嘛的 authServer = new NullQuorumAuthServer(); authLearner = new NullQuorumAuthLearner(); }
-
初始化完后,会对quorumPeer做很多赋值,这个东西应该是每个节点的核心了。前面做的一些初始化,比如QuorumVerifier和NIOServerCnxnFactory也都会放到quorumPeer中。
org.apache.zookeeper.server.quorum.QuorumPeerMain#runFromConfig
quorumPeer.setTxnFactory(new FileTxnSnapLog( config.getDataLogDir(), config.getDataDir()));//初始化快照和事务的文件 quorumPeer.enableLocalSessions(config.areLocalSessionsEnabled()); quorumPeer.enableLocalSessionsUpgrading( config.isLocalSessionsUpgradingEnabled()); //quorumPeer.setQuorumPeers(config.getAllMembers()); quorumPeer.setElectionType(config.getElectionAlg()); quorumPeer.setMyid(config.getServerId()); //设置myid quorumPeer.setTickTime(config.getTickTime()); quorumPeer.setMinSessionTimeout(config.getMinSessionTimeout()); quorumPeer.setMaxSessionTimeout(config.getMaxSessionTimeout()); quorumPeer.setInitLimit(config.getInitLimit()); quorumPeer.setSyncLimit(config.getSyncLimit()); quorumPeer.setConfigFileName(config.getConfigFilename()); //每个节点有自己的zkDatabase,原来是放在这个quorumPeer中 quorumPeer.setZKDatabase(new ZKDatabase(quorumPeer.getTxnFactory())); quorumPeer.setQuorumVerifier(config.getQuorumVerifier(), false); if (config.getLastSeenQuorumVerifier()!=null) { quorumPeer.setLastSeenQuorumVerifier(config.getLastSeenQuorumVerifier(), false); } 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