一,Watcher 的基本流程
ZooKeeper 的 Watcher 机制,总的来说可以分为三个过程:客户端注册 Watcher、服务器处理 Watcher 和客户端回调 Watcher
客户端注册 watcher 有 3 种方式,getData、exists、getChildren;以如下代码为例
1,基于 zkclient 客户端发起一个数据操作
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<version>0.10</version>
</dependency>
@SneakyThrows
public static void main(String[] args) {
SpringApplication.run(ZkclientApplication.class, args);
ZooKeeper zookeeper = new ZooKeeper("192.168.13.102:2181",
4000, event -> System.out.println("event.type" + event.getType()));
zookeeper.create("/watch", "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // 创建节点
zookeeper.exists("/watch", true); // 注册监听
Thread.sleep(1000);
zookeeper.setData("/watch", "1".getBytes(), -1); // 修改节点的值触发监听
System.in.read();
}
2,ZooKeeper API 的初始化过程
在创建一个 ZooKeeper 客户端对象实例时,我们通过 new Watcher()向构造方法中传入一个默认的 Watcher, 这个 Watcher 将作为整个 ZooKeeper 会话期间的默认Watcher,会一直被保存在客户端ZKWatchManager的defaultWatcher中。
1)Zookeeper构造器
public ZooKeeper(
String connectString,
int sessionTimeout,
Watcher watcher,
boolean canBeReadOnly,
HostProvider hostProvider,
ZKClientConfig clientConfig
) throws IOException {
this.clientConfig = clientConfig != null ? clientConfig : new ZKClientConfig();
this.hostProvider = hostProvider;
ConnectStringParser connectStringParser = new ConnectStringParser(connectString);
//debug
cnxn = createConnection(
connectStringParser.getChrootPath(),
hostProvider,
sessionTimeout,
this.clientConfig,
watcher,
getClientCnxnSocket(),
canBeReadOnly);
//调用start方法
cnxn.start();
}
2)createConnection()
ClientCnxn createConnection(
String chrootPath,
HostProvider hostProvider,
int sessionTimeout,
ZKClientConfig clientConfig,
Watcher defaultWatcher,
ClientCnxnSocket clientCnxnSocket,
boolean canBeReadOnly
) throws IOException {
//初始化ClientCnxn
return new ClientCnxn(
chrootPath,
hostProvider,
sessionTimeout,
clientConfig,
defaultWatcher,
clientCnxnSocket,
canBeReadOnly);
}
3)ClientCnxn构造器
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;
//在这里将watcher设置到ZKWatchManager
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是zk客户端和zk服务器端进行通信和事件通知处理的主要类,它内部包含两个类
SendThread:负责客户端和服务端的数据通信,也包括事件信息的传输。
EventThread:主要在客户端回调注册的Watchers进行通知处理。
3,ClientCnxn 初始化
public void start() {
sendThread.start();
eventThread.start();
}
二,服务端接收请求处理流程
服务端有一个 NIOServerCnxn 类,用来处理客户端发送过来的请求
1,NIOServerCnxn
doIO()处理IO请求,里面调用了那个那啥readPayload()
readPayload()调用了readRequest()来处理读请求
实际上读请求交给了ZookeeperServer的processPacket()。
protected void readRequest() throws IOException {
zkServer.processPacket(this, incomingBuffer);
}
2,processPacket()
处理客户端传送过来的数据包
public void processPacket(ServerCnxn cnxn, ByteBuffer incomingBuffer) throws IOException {
// We have the request, now process and setup for next
InputStream bais = new ByteBufferInputStream(incomingBuffer);
BinaryInputArchive bia = BinaryInputArchive.getArchive(bais);
RequestHeader h = new RequestHeader();
//反序列化客户端 header 头信息
h.deserialize(bia, "header");
cnxn.incrOutstandingAndCheckThrottle(h);
incomingBuffer = incomingBuffer.slice();
// 判断当前操作类型,如果是 auth操作,则执行下面的代码
if (h.getType() == OpCode.auth) {
//省略
} else if (h.getType() == OpCode.sasl) {
// 如果不是授权操作,再判断是否为 sasl 操作
processSasl(incomingBuffer, cnxn, h);
} else {// 最终进入这个代码块进行处理
if (!authHelper.enforceAuthentication(cnxn, h.getXid())) {
return;
} else {
// 封装请求对象
Request si = new Request(cnxn, cnxn.getSessionId(), h.getXid(), h.getType(), incomingBuffer, cnxn.getAuthInfo());
int length = incomingBuffer.limit();
if (isLargeRequest(length)) {
// checkRequestSize will throw IOException if request is rejected
checkRequestSizeWhenMessageReceived(length);
si.setLargeRequestSize(length);
}
si.setOwner(ServerCnxn.me);
// 提交请求
submitRequest(si);
}
}
}
3,submitRequest()
负责在服务端提交当前请求
public void submitRequestNow(Request si) {
if (firstProcessor == null) {//processor 处理器, request 过来以后会经历一系列处理器的处理过程
synchronized (this) {
try {
while (state == State.INITIAL) {
wait(1000);
}
} catch (InterruptedException e) {
LOG.warn("Unexpected interruption", e);
}
if (firstProcessor == null || state != State.RUNNING) {
throw new RuntimeException("Not started");
}
}
}
try {
touch(si.cnxn);
// 判断是否合法
boolean validpacket = Request.isValid(si.type);
if (validpacket) {
setLocalSessionFlag(si);
//调用 firstProcessor发起请求,而这个 firstProcess 是一个接口,有多个实现类
firstProcessor.processRequest(si);
if (si.cnxn != null) {
incInProcess();
}
} else {
LOG.warn("Received packet at server of unknown type {}", si.type);
requestFinished(si);
new UnimplementedRequestProcessor().processRequest(si);
}
} catch (MissingSessionException e) {
LOG.debug("Dropping request.", e);
// Update request accounting/throttling limits
requestFinished(si);
} catch (RequestProcessorException e) {
LOG.error("Unable to process request", e);
// Update request accounting/throttling limits
requestFinished(si);
}
}
1)firstProcessor的请求链组成
firstProcessor 的初始化是在 ZookeeperServer 的 setupRequestProcessor 中完成的,代码如下
protected void setupRequestProcessors() {
RequestProcessor finalProcessor = new FinalRequestProcessor(this);
RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
// 需要注意的是, PrepRequestProcessor 中传递的是一个 syncProcessor
((SyncRequestProcessor) syncProcessor).start();
firstProcessor = new PrepRequestProcessor(this, syncProcessor);
((PrepRequestProcessor) firstProcessor).start();
}
从上面我们可以看到 firstProcessor 的实例是一个 PrepRequestProcessor,而这个构造方法中又传递了一个 Processor 构成了一个调用链。
RequestProcessor syncProcessor = new SyncRequestProcessor(this, finalProcessor);
而 syncProcessor 的构造方法传递的又是一个 Processor,对应的是FinalRequestProcessor
所以整个调用链是 PrepRequestProcessor -> SyncRequestProcessor ->FinalRequestProcessor
4,PredRequestProcessor.processRequest()
firstProcessor.processRequest(si);会调用到 PrepRequestProcessor
public void processRequest(Request request) {
submittedRequests.add(request);
}
processRequest 只是把 request 添加到 submittedRequests 中,根据前面的经验,很自然的想到这里又是一个异步操作。而 subittedRequests 又是一个阻塞队列
LinkedBlockingQueue submittedRequests = new LinkedBlockingQueue()
而 PrepRequestProcessor 这个类又继承了线程类,因此直接找到当前类中的run 方法如下
@Override
public void run() {
try {
while (true) {
ServerMetrics.getMetrics().PREP_PROCESSOR_QUEUE_SIZE.add(submittedRequests.size());
//从队列中拿到请求进行处理
Request request = submittedRequests.take();
ServerMetrics.getMetrics().PREP_PROCESSOR_QUEUE_TIME
.add(Time.currentElapsedTime() - request.prepQueueStartTime);
if (LOG.isTraceEnabled()) {
long traceMask = ZooTrace.CLIENT_REQUEST_TRACE_MASK;
if (request.type == OpCode.ping) {
traceMask = ZooTrace.CLIENT_PING_TRACE_MASK;
}
ZooTrace.logRequest(LOG, traceMask, 'P', request, "");
}
if (Request.requestOfDeath == request) {
break;
}
request.prepStartTime = Time.currentElapsedTime();
// 调用 pRequest 进行预处理
pRequest(request);
}
} catch (Exception e) {
handleException(this.getName(), e);
}
}
1)pRequest
nextProcessor.processRequest(request);
5,SyncRequestProcessor.processRequest()
public void processRequest(Request request) {
// request.addRQRec(">sync");
queuedRequests.add(request);
}
这个方法的代码也是一样,基于异步化的操作,把请求添加到 queuedRequets 中
@Override
public void run() {
try {
resetSnapshotStats();
lastFlushTime = Time.currentElapsedTime();
while (true) {
ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_SIZE.add(queuedRequests.size());
long pollTime = Math.min(zks.getMaxWriteQueuePollTime(), getRemainingDelay());
Request si = queuedRequests.poll(pollTime, TimeUnit.MILLISECONDS);
if (si == null) {
flush();
// 从阻塞队列中获取请求
si = queuedRequests.take();
}
if (si == REQUEST_OF_DEATH) {
break;
}
long startProcessTime = Time.currentElapsedTime();
ServerMetrics.getMetrics().SYNC_PROCESSOR_QUEUE_TIME.add(startProcessTime - si.syncQueueStartTime);
// track the number of records written to the log
if (!si.isThrottled() && zks.getZKDatabase().append(si)) {
if (shouldSnapshot()) {
resetSnapshotStats();
// roll the log
zks.getZKDatabase().rollLog();
// take a snapshot
if (!snapThreadMutex.tryAcquire()) {
LOG.warn("Too busy to snap, skipping");
} else {
//启动一个处理快照的线程
new ZooKeeperThread("Snapshot Thread") {
public void run() {
try {
zks.takeSnapshot();
} catch (Exception e) {
LOG.warn("Unexpected exception", e);
} finally {
snapThreadMutex.release();
}
}
}.start();
}
}
} else if (toFlush.isEmpty()) {
if (nextProcessor != null) {
// 继续调用下一个处理器来处理请求
nextProcessor.processRequest(si);
if (nextProcessor instanceof Flushable) {
((Flushable) nextProcessor).flush();
}
}
continue;
}
toFlush.add(si);
if (shouldFlush()) {
flush();
}
ServerMetrics.getMetrics().SYNC_PROCESS_TIME.add(Time.currentElapsedTime() - startProcessTime);
}
} catch (Throwable t) {
handleException(this.getName(), t);
}
LOG.info("SyncRequestProcessor exited!");
}
6,FinalRequestProcessor.processRequest()
FinalRequestProcessor.processRequest 方法并根据 Request 对象中的操作更新内存中 Session 信息或者 znode 数据。
case OpCode.exists: {
lastOp = "EXIS";
ExistsRequest existsRequest = new ExistsRequest();
// 反序列化 ( 将 ByteBuffer 反序列化成为 ExitsRequest. 这个就是我们在客户端发起请求的时候传递过来的 Request 对象
ByteBufferInputStream.byteBuffer2Record(request.request, existsRequest);
// 得到请求的路径
path = existsRequest.getPath();
if (path.indexOf('\0') != -1) {
throw new KeeperException.BadArgumentsException();
}
// 终于找到一个很关键的代码,判断请求的 getWatch 是否存在,如果存在,则传递 cnxn ( servercnxn )
//对于 exists 请求,需要监听 data 变化事件,添加 watcher
Stat stat = zks.getZKDatabase().statNode(path, existsRequest.getWatch() ? cnxn : null);
// 在服务端内存数据库中根据路径得到结果进行组装,设置为 ExistsResponse
rsp = new ExistsResponse(stat);
requestPathMetricsCollector.registerRequest(request.type, path);
break;
}
三,客户端接收服务端处理完成的响应
1,ClientCnxnSocketNIO.doIO()
服务端处理完成以后,会通过 NIOServerCnxn.sendResponse 发送返回的响应信息,客户端会在 ClientCnxnSocketNIO.doIO 接收服务端的返回,
注意一下 SendThread.readResponse ,接收服务端的信息进行读取.
void doIO(Queue<Packet> pendingQueue, ClientCnxn cnxn) throws InterruptedException, IOException {
SocketChannel sock = (SocketChannel) sockKey.channel();
if (sock == null) {
throw new IOException("Socket is null!");
}
if (sockKey.isReadable()) {
int rc = sock.read(incomingBuffer);
if (rc < 0) {
throw new EndOfStreamException("Unable to read additional data from server sessionid 0x"
+ Long.toHexString(sessionId)
+ ", likely server has closed socket");
}
if (!incomingBuffer.hasRemaining()) {
incomingBuffer.flip();
if (incomingBuffer == lenBuffer) {
recvCount.getAndIncrement();
readLength();
} else if (!initialized) {
readConnectResult();
enableRead();
if (findSendablePacket(outgoingQueue, sendThread.tunnelAuthInProgress()) != null) {
// Since SASL authentication has completed (if client is configured to do so),
// outgoing packets waiting in the outgoingQueue can now be sent.
enableWrite();
}
lenBuffer.clear();
incomingBuffer = lenBuffer;
updateLastHeard();
initialized = true;
} else {
sendThread.readResponse(incomingBuffer);
lenBuffer.clear();
incomingBuffer = lenBuffer;
updateLastHeard();
}
}
}
if (sockKey.isWritable()) {
Packet p = findSendablePacket(outgoingQueue, sendThread.tunnelAuthInProgress());
if (p != null) {
updateLastSend();
// If we already started writing p, p.bb will already exist
if (p.bb == null) {
if ((p.requestHeader != null)
&& (p.requestHeader.getType() != OpCode.ping)
&& (p.requestHeader.getType() != OpCode.auth)) {
p.requestHeader.setXid(cnxn.getXid());
}
p.createBB();
}
sock.write(p.bb);
if (!p.bb.hasRemaining()) {
sentCount.getAndIncrement();
outgoingQueue.removeFirstOccurrence(p);
if (p.requestHeader != null
&& p.requestHeader.getType() != OpCode.ping
&& p.requestHeader.getType() != OpCode.auth) {
synchronized (pendingQueue) {
pendingQueue.add(p);
}
}
}
}
if (outgoingQueue.isEmpty()) {
disableWrite();
} else if (!initialized && p != null && !p.bb.hasRemaining()) {
disableWrite();
} else {
// Just in case
enableWrite();
}
}
}
2,SendThread.readResponse()
这个方法里面主要的流程如下:
首先读取 header,如果其 xid == -2,表明是一个 ping 的 response,return
如果 xid 是 -4 ,表明是一个 AuthPacket 的 response return
如果 xid 是 -1,表明是一个 notification,此时要继续读取并构造一个 enent,通过EventThread.queueEvent 发送,return
其它情况下:
从 pendingQueue 拿出一个 Packet,校验后更新 packet 信息
3,finishPacket()
主要功能是把从 Packet 中取出对应的 Watcher 并注册到 ZKWatchManager 中去.
下面这段代码是客户端存储 watcher 的几个 map 集合,分别对应三种注册监听事件
static class ZKWatchManager implements ClientWatchManager {
private final Map<String, Set<Watcher>> dataWatches =
new HashMap<String, Set<Watcher>>();
private final Map<String, Set<Watcher>> existWatches =
new HashMap<String, Set<Watcher>>();
private final Map<String, Set<Watcher>> childWatches =
new HashMap<String, Set<Watcher>>();
总的来说,当使用 ZooKeeper 构造方法或者使用 getData、exists 和getChildren 三个接口来向 ZooKeeper 服务器注册 Watcher 的时候,首先将此消息传递给服务端,传递成功后,服务端会通知客户端,然后客户端将该路径和Watcher 对应关系存储起来备用。
finishPacket 方法最终会调用 eventThread.queuePacket, 将当前的数据包添加到等待事件通知的队列中
四,事件触发
前面这么长的说明,只是为了清晰的说明事件的注册流程,最终的触发,还得需要通过事务型操作来完成
zookeeper.setData(“/mic”, “1”.getByte(),-1) ; // 修改节点的值触发监听
1,服务端的事件响应DataTree.setData()
// 触发对应节点的 NodeDataChanged 事件
dataWatches.triggerWatch(path, EventType.NodeDataChanged);
1)WatcherManager.triggerWatch()
w.process(e);
2)w.process(e)
在服务端绑定事件的时候,watcher 绑定是是什么?是 ServerCnxn,所以 w.process(e),其实调用的应该是 ServerCnxn 的 process 方法。而
servercnxn 又是一个抽象方法,有两个实现类,分别是:NIOServerCnxn 和NIOServerCnxn。
public void process(WatchedEvent event) {
ReplyHeader h = new ReplyHeader(ClientCnxn.NOTIFICATION_XID, -1L, 0);
if (LOG.isTraceEnabled()) {
}
WatcherEvent e = event.getWrapper();
// 这个地方发送了一个事件,事件对象为 WatcherEvent 。
int responseSize = sendResponse(h, e, "notification", null, null, ZooDefs.OpCode.error);
ServerMetrics.getMetrics().WATCH_BYTES.add(responseSize);
}
那接下来,客户端会收到这个 response,触发 SendThread.readResponse 方法
2,客户端处理事件响应
SendThread 接收到服务端的通知事件后,会通过调用 EventThread 类的queueEvent 方法将事件传给 EventThread 线程,queueEvent 方法根据该通知事件,从 ZKWatchManager 中取出所有相关的 Watcher,如果获取到相应的Watcher,就会让 Watcher 移除失效。
通过 dataWatches 或者 existWatches 或者 childWatches 的 remove 取出对应的watch,表明客户端 watch 也是注册一次就移除.
同时需要根据 keeperState、eventType 和 path 返回应该被通知的 Watcher 集合
waitingEvents 是 EventThread 这个线程中的阻塞队列,很明显,又是在我们第一步操作的时候实例化的一个线程。
从名字可以知道,waitingEvents 是一个待处理 Watcher 的队列,EventThread 的run() 方法会不断从队列中取数据,交由 processEvent 方法处理:
判断事件类型,得到 watcherseteventPair,拿到符合触发机制的所有 watcher 列表,循环进行调用,调用客户端的回调 process.
3,服务端接收数据请求
zookeeper 启动的时候,通过下面的代码构建了一个NIOServerCnxnFactory,它实现了 Thread,所以在启动的时候,会在 run 方法中不断循环接收客户端的请求进行分发
ServerCnxnFactory cnxnFactory = ServerCnxnFactory.createFactory();