ZooKeeper启动源码分析
- 1. 客户端启动
- 1. getClientCnxnSocket()
- 2. 创建ClientCnxn实例
- 2.1. SendThread
- 2.2. EventThread
- 3. 启动服务
从github上拉下来的zk源码会附带很多单元测试,咱们就源码中的ZooKeeperTestClient#create_get_stat_test()方法来分析 :
public class ZooKeeperTestClient extends ZKTestCase implements Watcher {
protected String hostPort = "127.0.0.1:22801";
protected static final String dirOnZK = "/test_dir";
protected String testDirOnZK = dirOnZK + "/" + Time.currentElapsedTime();
private void create_get_stat_test() throws IOException, InterruptedException, KeeperException {
ZooKeeper zk = new ZooKeeper(hostPort, 10000, this);
String parentName = testDirOnZK;
String nodeName = parentName + "/create_with_stat_tmp";
deleteNodeIfExists(zk, nodeName);
deleteNodeIfExists(zk, nodeName + "_2");
Stat stat = new Stat();
zk.create(nodeName, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, stat);
assertNotNull(stat);
assertTrue(stat.getCzxid() > 0);
assertTrue(stat.getCtime() > 0);
zk.close();
}
public synchronized void process(WatchedEvent event) {
try {
System.out.println("Got an event " + event.toString());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
1. 客户端启动
zk在创建实例的时候就启动了两个线程去处理不同的业务,一个是负责客户端和服务端的通信,另一个是负责在客户端回调注册的Watchers进行通知处理。
我们来看下它是怎么实现的:
ZooKeeper zk = new ZooKeeper(hostPort, 10000, this);
请注意这里传了一个watcher即this
我们将Zookeeper的构造函数一层层点进去看下:
public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider aHostProvider) throws IOException {
this(connectString, sessionTimeout, watcher, canBeReadOnly, aHostProvider, null);
}
public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider hostProvider,
ZKClientConfig clientConfig
) throws IOException {
LOG.info(
"Initiating client connection, connectString={} sessionTimeout={} watcher={}",
connectString,
sessionTimeout,
watcher);
this.clientConfig = clientConfig != null ? clientConfig : new ZKClientConfig();
//保存了之前new ZooKeeper时传入的主机地址(InetSocketAddress实例)
this.hostProvider = hostProvider;
//解析连接地址
ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this.clientConfig,
watcher,
getClientCnxnSocket(),
canBeReadOnly);
cnxn.start();
}
这里有三个重要的逻辑:
1. getClientCnxnSocket()
看下这个方法的实现
private ClientCnxnSocket getClientCnxnSocket() throws IOException {
String clientCnxnSocketName = getClientConfig().getProperty(ZKClientConfig.ZOOKEEPER_CLIENT_CNXN_SOCKET);
if (clientCnxnSocketName == null) {
clientCnxnSocketName = ClientCnxnSocketNIO.class.getName();
}
try {
Constructor<?> clientCxnConstructor = Class.forName(clientCnxnSocketName)
.getDeclaredConstructor(ZKClientConfig.class);
ClientCnxnSocket clientCxnSocket = (ClientCnxnSocket) clientCxnConstructor.newInstance(getClientConfig());
return clientCxnSocket;
} catch (Exception e) {
throw new IOException("Couldn't instantiate " + clientCnxnSocketName, e);
}
}
这个会创建一个ClientCnxnSocket实例,主要负责处理客户端的socket请求,后面的文章会分析到它,他的实现有两种,一个是基于Netty的实现,另一个是基于NIO的实现,可以在配置文件中配置使用哪一个。假设我们在配置文件中没有指定。那默认使用的是基于NIO实现的ClientCnxnSocketNIO
2. 创建ClientCnxn实例
ClientCnxn是为客户端管理套接字IO的类,看一下它的构造方法和成员变量:
public class ClientCnxn {
private static final Logger LOG = LoggerFactory.getLogger(ClientCnxn.class);
/* ZOOKEEPER-706: If a session has a large number of watches set then
* attempting to re-establish those watches after a connection loss may
* fail due to the SetWatches request exceeding the server's configured
* jute.maxBuffer value. To avoid this we instead split the watch
* re-establishement across multiple SetWatches calls. This constant
* controls the size of each call. It is set to 128kB to be conservative
* with respect to the server's 1MB default for jute.maxBuffer.
*/
private static final int SET_WATCHES_MAX_LENGTH = 128 * 1024;
/* predefined xid's values recognized as special by the server */
// -1 means notification(WATCHER_EVENT)
public static final int NOTIFICATION_XID = -1;
// -2 is the xid for pings
public static final int PING_XID = -2;
// -4 is the xid for AuthPacket
public static final int AUTHPACKET_XID = -4;
// -8 is the xid for setWatch
public static final int SET_WATCHES_XID = -8;
/**
* These are the packets that have been sent and are waiting for a response.
*/
private final Queue<Packet> pendingQueue = new ArrayDeque<>();
/**
* These are the packets that need to be sent.
* 待发送的消息会先暂存到这个队列中
*/
private final LinkedBlockingDeque<Packet> outgoingQueue = new LinkedBlockingDeque<Packet>();
final SendThread sendThread;
final EventThread eventThread;
public ClientCnxn(
String chrootPath,
HostProvider hostProvider,
int sessionTimeout,
ZKClientConfig clientConfig,
Watcher defaultWatcher,
ClientCnxnSocket clientCnxnSocket,
long sessionId,
byte[] sessionPasswd,
boolean canBeReadOnly
) throws IOException {
this.chrootPath = chrootPath;
this.hostProvider = hostProvider;
this.sessionTimeout = sessionTimeout;
this.clientConfig = clientConfig;
this.sessionId = sessionId;
this.sessionPasswd = sessionPasswd;
this.readOnly = canBeReadOnly;
this.watchManager = new ZKWatchManager(
clientConfig.getBoolean(ZKClientConfig.DISABLE_AUTO_WATCH_RESET),
defaultWatcher);
this.connectTimeout = sessionTimeout / hostProvider.size();
this.readTimeout = sessionTimeout * 2 / 3;
this.sendThread = new SendThread(clientCnxnSocket);
this.eventThread = new EventThread();
initRequestTimeout();
}
可以看到在构造方法里面创建了两个线程,这两个线程都是ClientCnxn内部类
2.1. SendThread
这个线程主要负责与服务端通信,是个典型的生产者-消费者模型。zk产生的消息会先放到outgoingQueue阻塞队列中,然后由sendThread去消费,后面会分析它的sendThread方法。传进去的参数是上面第一步讲到的ClientCnxnSocket实例
2.2. EventThread
该线程维护了一个waitingEvents队列
class EventThread extends ZooKeeperThread {
private final LinkedBlockingQueue<Object> waitingEvents = new LinkedBlockingQueue<Object>();
}
sendThread中产生的事件会放到EventThread的这个队列中,然后由它去消费这同样是一个生产者-消费者的体现
3. 启动服务
启动服务就只是开启了sendThread和eventThread
cnxn.start();
public void start() {
//负责客户端和服务端的通信
sendThread.start();
//主要负责在客户端回调注册的Watchers进行通知处理
eventThread.start();
}
关于ZooKeeper的客户端启动就分析到这儿,下一篇会分析上面提到的SendThread和WorkThread
Zk客户端(ClientCnxn)中的EventThread和SendThread