
org.lable.oss.dynamicconfig.provider.zookeeper.NodeWatcher Maven / Gradle / Ivy
/**
* Copyright (C) 2015 Lable ([email protected])
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lable.oss.dynamicconfig.provider.zookeeper;
import org.apache.zookeeper.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.Closeable;
import java.io.IOException;
import java.util.concurrent.TimeUnit;
/**
* Zookeeper node watcher.
*/
public class NodeWatcher implements Watcher, Runnable, Closeable {
private final static Logger logger = LoggerFactory.getLogger(NodeWatcher.class);
/*
* If the Zookeeper quorum cannot be reached, the watcher thread will attempt to reconnect, with incrementally
* longer waits between attempts. It should wait at most this long (in minutes) between attempts.
*/
static final int MAX_RETRY_WAIT_MINUTES = 5;
private final AsyncCallback.DataCallback callback;
private final String quorum;
private final String path;
private ZooKeeper zk;
private int retryCounter;
private int retryWait;
State state = State.STARTING;
/**
* Construct a new NodeWatcher.
*
* @param quorum Comma-separated list of addresses for the Zookeeper quorum.
* @param callback Callback method called upon changes in the node.
* @param path Path of the znode monitored.
*/
public NodeWatcher(String quorum, AsyncCallback.DataCallback callback, String path) {
this.quorum = quorum;
this.callback = callback;
this.path = path;
resetRetryCounters();
state = State.LIVE;
connect();
}
@Override
public void run() {
try {
synchronized (this) {
wait();
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
@Override
public void close() throws IOException {
if (state == State.CLOSED) return;
synchronized (this) {
state = State.CLOSED;
try {
zk.close();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
notifyAll();
}
}
@Override
public void process(WatchedEvent watchedEvent) {
if (state != State.LIVE) return;
Event.KeeperState state = watchedEvent.getState();
Event.EventType type = watchedEvent.getType();
// This switch handles all relevant states, and tries to reset the watch on the znode after it is
// triggered.
switch (state) {
case SyncConnected:
case ConnectedReadOnly:
resetRetryCounters();
switch (type) {
case NodeCreated:
case NodeDataChanged:
// Configuration znode changed, let the callback know.
zk.getData(path, this, callback, null);
break;
case None:
registerWatcher(path);
break;
case NodeDeleted:
logger.error("Our configuration znode was deleted. Waiting for it to be recreated…");
registerWatcher(path);
break;
}
break;
case Disconnected:
logger.warn("Disconnected from Zookeeper quorum, reconnecting…");
// The Zookeeper instance will automatically attempt reconnection.
waitBeforeRetrying();
break;
case Expired:
logger.warn("Connection to Zookeeper quorum expired. Attempting to reconnect…");
// The Zookeeper instance is no longer valid. We have to reconnect ourselves.
connect();
break;
case SaslAuthenticated:
case AuthFailed:
// Probably not relevant to us.
break;
}
}
/**
* Connect to the Zookeeper quorum and create a new Zookeeper instance.
*/
void connect() {
if (state != State.LIVE) return;
logger.debug("Connecting to ZooKeeper Quorum.");
if (zk != null) {
try {
zk.close();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
if (retryCounter > 0) {
logger.warn("Failed to connect to Zookeeper quorum, retrying (" + retryCounter + ").");
}
try {
zk = new ZooKeeper(quorum, 3000, this);
} catch (IOException e) {
waitBeforeRetrying();
connect();
}
}
/**
* Register the ZooKeeper watcher for a node. If registering it fails {@link #waitBeforeRetrying()} is called
* once to prevent hammering.
*
* @param path Node to watch.
*/
void registerWatcher(String path) {
try {
// Register the watcher.
zk.exists(path, this);
} catch (KeeperException.SessionExpiredException e) {
connect();
} catch (KeeperException e) {
logger.error("KeeperException caught, retrying…", e);
waitBeforeRetrying();
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
/**
* Sleep a while before continuing. This increments a waiting counter and sleeps longer each time it is
* called, until {@link #MAX_RETRY_WAIT_MINUTES} is reached.
*/
void waitBeforeRetrying() {
if (retryWait < MAX_RETRY_WAIT_MINUTES * 60) {
retryWait *= 2;
if (retryWait > MAX_RETRY_WAIT_MINUTES * 60) {
retryWait = MAX_RETRY_WAIT_MINUTES * 60;
}
}
retryCounter++;
try {
logger.info("Failed to connect to ZooKeeper quorum, waiting " + retryWait + "s before retrying.");
TimeUnit.SECONDS.sleep(retryWait);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
}
}
/**
* Reset retry waiting time and counter.
*/
void resetRetryCounters() {
retryCounter = 0;
retryWait = 10;
}
public enum State {
STARTING,
LIVE,
CLOSED
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy