一、前言
zookeeper version 3.6
上篇文章聊到了 zk 的底层线程通信模型,算是为本节 zk 是怎样处理请求的打下了一定的基础,因为如果直接进行 server 端请求的讲解可能会被绕晕,zk 底层模块间的交互基本追求完全的解耦,使用了很多队列,看完上篇文章应当至少对 zk client 端 zk server 端底层队列的使用有个大概的印象。
Zookeeper 请求准备按照以下三个层级进行介绍:启动前的准备阶段、请求前的协商阶段、请求处理阶段。
二、Zookeeper 启动前的准备阶段与协商阶段
client 端与 server 端的交互就是通过 org.apache.zookeeper.ZooKeeper 对象,下面看一个简单的案例
public class TestMain {
public static void main(String[] args) throws Exception {
ZooKeeper zooKeeper = new ZooKeeper("127.0.0.1:2181", 1000, (event) -> {
System.out.println("connect success");
});
zooKeeper.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
一)client 端
启动前的准备阶段与协商阶段就是在创建 Zookeeper 对象的时候完成的,下面看下关键代码
public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider aHostProvider,
ZKClientConfig clientConfig) throws IOException {
// 创建 watcher 管理类
watchManager = defaultWatchManager();
watchManager.defaultWatcher = watcher;
ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
hostProvider = aHostProvider;
// 创建连接并初始化 sendThread 和 eventThread 超时时间等
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this,
watchManager,
getClientCnxnSocket(),
canBeReadOnly);
// 连接
cnxn.start();
}
这部分代码主要做了三件事
1)第一个就是构建 watcher 管理类,这里简单提一下两端的 watcher 管理器,其实 watcher 逻辑主要就是在 client 端维护的,server 端做的事也就是将对应的节点与连接绑定,client 端 watcher 管理器的作用主要就是接收 server 端的事件并且如果 server 端发生异常连接断开 client 端需要将 watcher 重新注册到 server 端。之后会详细介绍 watcher 的机制跟实现原理
2)解析连接字符串,这个地方会把提供的连接包装成一个对象,因为一个 client 端可以保存集群中多个节点的信息,之后就是创建处理连接的对象
3)整个阶段的处理逻辑主要在最后 start 方法中,在这里就是启动 SendThread 与 EventThread
public void run() {
// 这几行就是记录了下时间 主要就是为发送 ping 作准备
clientCnxnSocket.introduce(this, sessionId, outgoingQueue);
clientCnxnSocket.updateNow();
clientCnxnSocket.updateLastSendAndHeard();
int to;
long lastPingRwServer = Time.currentElapsedTime();
final int MAX_SEND_PING_INTERVAL = 10000; //10 seconds
InetSocketAddress serverAddress = null;
while (state.isAlive()) {
try {
// 这些代码是为了处理如果连接失败会怎样
if (!clientCnxnSocket.isConnected()) {
// don't re-establish connection if we are closing
if (closing) {
break;
}
// 如果最近有 ping 成功的机器,则就去连接这个
// 如果没有就从一开始提供的地址组里轮询去找
if (rwServerAddress != null) {
serverAddress = rwServerAddress;
rwServerAddress = null;
} else {
serverAddress = hostProvider.next(1000);
}
//这里就是连接的逻辑
startConnect(serverAddress);
clientCnxnSocket.updateLastSendAndHeard();
}
// 这里是连接成功之后的逻辑
// 主要就是安全性相关的处理
if (state.isConnected()) {
// determine whether we need to send an AuthFailed event.
if (zooKeeperSaslClient != null) {
boolean sendAuthEvent = false;
if (zooKeeperSaslClient.getSaslState() == ZooKeeperSaslClient.SaslState.INITIAL) {
try {
zooKeeperSaslClient.initialize(ClientCnxn.this);
} catch (SaslException e) {
LOG.error("SASL authentication with Zookeeper Quorum member failed.", e);
state = States.AUTH_FAILED;
sendAuthEvent = true;
}
}
KeeperState authState = zooKeeperSaslClient.getKeeperState();
if (authState != null) {
if (authState == KeeperState.AuthFailed) {
// An authentication error occurred during authentication with the Zookeeper Server.
state = States.AUTH_FAILED;
sendAuthEvent = true;
} else {
if (authState == KeeperState.SaslAuthenticated) {
sendAuthEvent = true;
}
}
}
if (sendAuthEvent) {
eventThread.queueEvent(new WatchedEvent(Watcher.Event.EventType.None, authState, null));
if (state == States.AUTH_FAILED) {
eventThread.queueEventOfDeath();
}
}
}
to = readTimeout - clientCnxnSocket.getIdleRecv();
} else {
// 这里记录了下是否超时
to = connectTimeout - clientCnxnSocket.getIdleRecv();
}
if (to <= 0) {
String warnInfo = String.format(
"Client session timed out, have not heard from server in %dms for session id 0x%s",
clientCnxnSocket.getIdleRecv(),
Long.toHexString(sessionId));
LOG.warn(warnInfo);
throw new SessionTimeoutException(warnInfo);
}
// 这里是连接成功之后会计算下次发送 ping 的时间
// 如果需要就会发送 ping 报文
if (state.isConnected()) {
//1000(1 second) is to prevent race condition missing to send the second ping
//also make sure not to send too many pings when readTimeout is small
int timeToNextPing = readTimeout / 2
- clientCnxnSocket.getIdleSend()
- ((clientCnxnSocket.getIdleSend() > 1000) ? 1000 : 0);
//send a ping request either time is due or no packet sent out within MAX_SEND_PING_INTERVAL
if (timeToNextPing <= 0 || clientCnxnSocket.getIdleSend() > MAX_SEND_PING_INTERVAL) {
// 发送 ping 报文并更新发送时间
sendPing();
clientCnxnSocket.updateLastSend();
} else {
if (timeToNextPing < to) {
to = timeToNextPing;