zookeeper通過使用watcher可以實現發(fā)布訂閱的功能,實際上就是基于監(jiān)聽的事件觸發(fā)。
示例
以下是在zk上創(chuàng)建一個Node存儲app的配置信息,然后監(jiān)聽配置變化來做出相應的動作。
模擬配置信息類
public class SampleConf {
private String url;
private int port;
private String name;
private String password;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
sb.append("\"url\":\"")
.append(url).append('\"');
sb.append(",\"port\":")
.append(port);
sb.append(",\"name\":\"")
.append(name).append('\"');
sb.append(",\"password\":\"")
.append(password).append('\"');
sb.append('}');
return sb.toString();
}
}
簡單封裝的zk工具類
import com.alibaba.fastjson.JSON;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
public class SimpleZKUtils {
private SimpleZKUtils() {
}
private static final Logger LOGGER = LoggerFactory.getLogger(SimpleZKUtils.class);
private static final String hostPort = "localhost:2181";
private static ZooKeeper zk;
static {
try {
zk = new ZooKeeper(hostPort, 3000, event -> System.out.println(JSON.toJSONString(event)));
} catch (IOException e) {
e.printStackTrace();
}
}
public static Stat set(String path, String data) {
try {
Stat stat = zk.exists(path, false);
if (null != stat) {
return zk.setData(path, data.getBytes(), stat.getVersion());
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static String get(String path) {
try {
Stat stat = zk.exists(path, false);
if (null != stat) {
return new String(zk.getData(path, true, stat));
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static String get(String path, Watcher watcher) {
try {
Stat stat = zk.exists(path, false);
if (null != stat) {
return new String(zk.getData(path, watcher, stat));
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
return null;
}
public static void create(String path, String data) {
try {
Stat stat = zk.exists(path, false);
if (null == stat) {
zk.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} else {
throw new RuntimeException("node is already existed");
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
public static void del(String path) {
try {
Stat stat = zk.exists(path, false);
if (null != stat) {
zk.delete(path, stat.getVersion());
} else {
throw new RuntimeException("node is already existed");
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}
測試
- 創(chuàng)建ZNode
public static final String basePath = "/app1";
public static final String path = "/app1/conf";
private static SampleConf sampleConf = null;
@Test
public void testCreate() {
SimpleZKUtils.create(basePath,"");
SimpleZKUtils.create(path, "");
}
@Test
public void configure() {
SampleConf sampleConf = new SampleConf();
sampleConf.setName("jk");
sampleConf.setUrl("localhost");
sampleConf.setPort(2181);
sampleConf.setPassword("helloworld");
SimpleZKUtils.set(path, JSON.toJSONString(sampleConf));
System.out.println(SimpleZKUtils.get(path));
}
- 監(jiān)聽
@Test
public void testPubSub() {
String conf = SimpleZKUtils.get(path, new ConfigWatcher((watcher) -> {
System.out.println("watcher execute");
sampleConf = JSON.parseObject(SimpleZKUtils.get(path, watcher), SampleConf.class);
System.out.println(JSON.toJSONString(sampleConf));
}));
System.out.println(conf);
// 阻塞線程以查看監(jiān)聽觸發(fā)的動作
LockSupport.park();
}
// Watcher實現類
static class ConfigWatcher implements Watcher {
private Consumer<Watcher> myWatch;
ConfigWatcher(Consumer<Watcher> myWatch) {
this.myWatch = myWatch;
}
@Override
public void process(WatchedEvent event) {
if (event.getType().equals(Watcher.Event.EventType.NodeDataChanged)) {
// 使用此方式是為了把watcher實例設置到zk的get方法里面去
myWatch.accept(this);
}
}
}
因為zk的watcher是一次性的,所以每次在觸發(fā)事件時需要設置watcher才能在后續(xù)的事件發(fā)生時繼續(xù)響應,此處我套了個Consumer接口來復用最外層的watcher實例,因為在lambda表達式里面沒法直接傳this。使用匿名內部類可以解決:
@Test
public void testPubSub2() {
String conf = SimpleZKUtils.get(path, new Watcher(){
@Override
public void process(WatchedEvent watchedEvent) {
System.out.println("watcher execute");
sampleConf = JSON.parseObject(SimpleZKUtils.get(path, this), SampleConf.class);
System.out.println(JSON.toJSONString(sampleConf));
}
});
System.out.println(conf);
LockSupport.park();
}