zk原生api的不足之處
- 超時重連,不支持自動,需要手動操作
- Watch註冊一次後會失效
- 不支持遞歸創建節點
Apache curator
是apache的開源項目,解決watcher註冊一次就失效的問題,api更加簡單易用,提供更多解決方案並且實現簡單:如分佈式鎖
創建一個maven工程
引入相關依賴
<!--zookeeper相關-->
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-framework</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.curator</groupId>
<artifactId>curator-recipes</artifactId>
<version>4.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.11</version>
</dependency>
zk基本操作
zk命名空間以及創建節點,節點的增刪改查
import java.util.List;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.cache.ChildData;
import org.apache.curator.framework.recipes.cache.PathChildrenCache;
import org.apache.curator.framework.recipes.cache.PathChildrenCache.StartMode;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheEvent;
import org.apache.curator.framework.recipes.cache.PathChildrenCacheListener;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;
public class CuratorOperator {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.254.130:2181";
/**
* 實例化zk客戶端
*/
public CuratorOperator() {
/**
* curator鏈接zookeeper的策略:RetryNTimes
* n:重試的次數
* sleepMsBetweenRetries:每次重試間隔的時間
*/
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
//命名空間之後創建的節點都會在workspace工作空間裏面
.namespace("workspace").build();
client.start();
}
/**
*
* @Description: 關閉zk客戶端連接
*/
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 實例化
CuratorOperator cto = new CuratorOperator();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("當前客戶的狀態:" + (isZkCuratorStarted ? "連接中" : "已關閉"));
// 創建節點
String nodePath = "/bushro/demo";
byte[] data = "data".getBytes();
cto.client.create().creatingParentsIfNeeded()
.withMode(CreateMode.PERSISTENT)
.withACL(ZooDefs.Ids.OPEN_ACL_UNSAFE)//默認的權限"world", "anyone"
.forPath(nodePath, data);
更新節點數據
byte[] newData = "newdata".getBytes();
cto.client.setData().withVersion(0).forPath(nodePath, newData);
// 刪除節點
cto.client.delete()
.guaranteed() // 如果刪除失敗,那麼在後端還是繼續會刪除,直到成功
.deletingChildrenIfNeeded() // 如果有子節點,就刪除
.withVersion(0)
.forPath(nodePath);
// 讀取節點數據
Stat stat = new Stat();
byte[] data = cto.client.getData().storingStatIn(stat).forPath(nodePath);
System.out.println("節點" + nodePath + "的數據爲: " + new String(data));
System.out.println("該節點的版本號爲: " + stat.getVersion());
// 查詢子節點
List<String> childNodes = cto.client.getChildren()
.forPath(nodePath);
System.out.println("開始打印子節點:");
for (String s : childNodes) {
System.out.println(s);
}
// 判斷節點是否存在,如果不存在則爲空
Stat statExist = cto.client.checkExists().forPath(nodePath + "/abc");
System.out.println(statExist);
Thread.sleep(100000);
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("當前客戶的狀態:" + (isZkCuratorStarted2 ? "連接中" : "已關閉"));
}
public final static String ADD_PATH = "/super/imooc/d";
}
watch與acl的操作
一次註冊多次監聽
爲節點添加watch事件
// 爲節點添加watcher
//NodeCache: 監聽數據節點的變更,會觸發事件
final NodeCache nodeCache = new NodeCache(cto.client, nodePath);
// buildInitial : 初始化的時候獲取node的值並且緩存
nodeCache.start(true);
if (nodeCache.getCurrentData() != null) {
System.out.println("節點初始化數據爲:" + new String(nodeCache.getCurrentData().getData()));
} else {
System.out.println("節點初始化數據爲空...");
}
nodeCache.getListenable().addListener(new NodeCacheListener() {
public void nodeChanged() throws Exception {
if (nodeCache.getCurrentData() == null) {
System.out.println("空");
return;
}
String data = new String(nodeCache.getCurrentData().getData());
System.out.println("節點路徑:" + nodeCache.getCurrentData().getPath() + "數據:" + data);
}
});
爲子節點添加watch事件
// 爲子節點添加watcher
// PathChildrenCache: 監聽數據節點的增刪改,會觸發事件
String childNodePathCache = nodePath;
// cacheData: 設置緩存節點的數據狀態
final PathChildrenCache childrenCache = new PathChildrenCache(cto.client, childNodePathCache, true);
/**
* StartMode: 初始化方式
* POST_INITIALIZED_EVENT:異步初始化,初始化之後會觸發事件(比較好)
* NORMAL:異步初始化
* BUILD_INITIAL_CACHE:同步初始化
*/
childrenCache.start(StartMode.POST_INITIALIZED_EVENT);
List<ChildData> childDataList = childrenCache.getCurrentData();
System.out.println("當前數據節點的子節點數據列表:");
for (ChildData cd : childDataList) {
String childData = new String(cd.getData());
System.out.println(childData);
}
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
if(event.getType().equals(PathChildrenCacheEvent.Type.INITIALIZED)){
System.out.println("子節點初始化ok...");
}
else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_ADDED)){
String path = event.getData().getPath();
System.out.println("添加子節點:" + event.getData().getPath());
System.out.println("子節點數據:" + new String(event.getData().getData()));
}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_REMOVED)){
System.out.println("刪除子節點:" + event.getData().getPath());
}else if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
System.out.println("修改子節點路徑:" + event.getData().getPath());
System.out.println("修改子節點數據:" + new String(event.getData().getData()));
}
}
});
watcher統一配置
例如創建一個存放redis的配置文件節點,裏面放入相關操作的json數據如
{“type”:“add”,“url”:“ftp://192.168.254.130/config/redis.xml”,“remark”:“add”}
{“type”:“update”,“url”:“ftp://192.168.254.130/config/redis.xml”,“remark”:“update”}
{“type”:“delete”,“url”:"",“remark”:“delete”}
在程序中我們可以把節點數據轉成實體類,根據type的類型來判斷進行相應的操作,當zookeeper集羣中監聽到變化,每臺機子就去下載最新的配置文件這樣就不用一臺一臺機子的修改過去,節省時間。
public class Client1 {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.254.130:2181";
public Client1() {
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder()
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
// public final static String CONFIG_NODE = "/bushro/demo/redis-config";
public final static String CONFIG_NODE_PATH = "/bushro/demo";
public final static String SUB_PATH = "/redis-config";
public static CountDownLatch countDown = new CountDownLatch(1);
public static void main(String[] args) throws Exception {
Client1 cto = new Client1();
System.out.println("client1 啓動成功...");
final PathChildrenCache childrenCache = new PathChildrenCache(cto.client, CONFIG_NODE_PATH, true);
childrenCache.start(StartMode.BUILD_INITIAL_CACHE);
// 添加監聽事件
childrenCache.getListenable().addListener(new PathChildrenCacheListener() {
public void childEvent(CuratorFramework client, PathChildrenCacheEvent event) throws Exception {
// 監聽節點變化
if(event.getType().equals(PathChildrenCacheEvent.Type.CHILD_UPDATED)){
String configNodePath = event.getData().getPath();
if (configNodePath.equals(CONFIG_NODE_PATH + SUB_PATH)) {
System.out.println("監聽到配置發生變化,節點路徑爲:" + configNodePath);
// 讀取節點數據
String jsonConfig = new String(event.getData().getData());
System.out.println("節點" + CONFIG_NODE_PATH + "的數據爲: " + jsonConfig);
// 從json轉換配置(轉實體類)
RedisConfig redisConfig = null;
if (StringUtils.isNotBlank(jsonConfig)) {
redisConfig = JsonUtils.jsonToPojo(jsonConfig, RedisConfig.class);
}
// 配置不爲空則進行相應操作
if (redisConfig != null) {
String type = redisConfig.getType();
String url = redisConfig.getUrl();
String remark = redisConfig.getRemark();
// 判斷事件
if (type.equals("add")) {
System.out.println("監聽到新增的配置,準備下載...");
// ... 連接ftp服務器,根據url找到相應的配置
Thread.sleep(500);
System.out.println("開始下載新的配置文件,下載路徑爲<" + url + ">");
// ... 下載配置到你指定的目錄
Thread.sleep(1000);
System.out.println("下載成功,已經添加到項目中");
// ... 拷貝文件到項目目錄
} else if (type.equals("update")) {
System.out.println("監聽到更新的配置,準備下載...");
// ... 連接ftp服務器,根據url找到相應的配置
Thread.sleep(500);
System.out.println("開始下載配置文件,下載路徑爲<" + url + ">");
// ... 下載配置到你指定的目錄
Thread.sleep(1000);
System.out.println("下載成功...");
System.out.println("刪除項目中原配置文件...");
Thread.sleep(100);
// ... 刪除原文件
System.out.println("拷貝配置文件到項目目錄...");
// ... 拷貝文件到項目目錄
} else if (type.equals("delete")) {
System.out.println("監聽到需要刪除配置");
System.out.println("刪除項目中原配置文件...");
}
}
}
}
}
});
countDown.await();
cto.closeZKClient();
}
}
acl權限操作與認證授權
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.retry.RetryNTimes;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooDefs.Perms;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import com.imooc.utils.AclUtils;
public class CuratorAcl {
public CuratorFramework client = null;
public static final String zkServerPath = "192.168.254.130:2181";
public CuratorAcl() {
RetryPolicy retryPolicy = new RetryNTimes(3, 5000);
client = CuratorFrameworkFactory.builder().authorization("digest", "bushro1:123456".getBytes())//使用賬號密碼認證,可以認證多個用戶
.connectString(zkServerPath)
.sessionTimeoutMs(10000).retryPolicy(retryPolicy)
.namespace("workspace").build();
client.start();
}
public void closeZKClient() {
if (client != null) {
this.client.close();
}
}
public static void main(String[] args) throws Exception {
// 實例化
CuratorAcl cto = new CuratorAcl();
boolean isZkCuratorStarted = cto.client.isStarted();
System.out.println("當前客戶的狀態:" + (isZkCuratorStarted ? "連接中" : "已關閉"));
String nodePath = "/acl/father/child/sub";
List<ACL> acls = new ArrayList<ACL>();
Id bushro1 = new Id("digest", AclUtils.getDigestUserPwd("bushro1:123456"));
Id bushro2 = new Id("digest", AclUtils.getDigestUserPwd("bushro2:123456"));
acls.add(new ACL(Perms.ALL, bushro1));
acls.add(new ACL(Perms.READ, bushro2));
acls.add(new ACL(Perms.DELETE | Perms.CREATE, bushro2));
// 創建節點
byte[] data = "spiderman".getBytes();
cto.client.create().creatingParentsIfNeeded()//遞歸方式創建
.withMode(CreateMode.PERSISTENT)
.withACL(acls)//該方式只有對最後一個節點設置權限,相應的父節點默認都是"world", "anyone",
// 如果後面添加true那麼所有創建的節點都是設置的權限
.forPath(nodePath, data);
//設置權限
cto.client.setACL().withACL(acls).forPath("/curatorNode");
// 更新節點數據
byte[] newData = "batman".getBytes();
cto.client.setData().withVersion(0).forPath(nodePath, newData);
// 刪除節點
cto.client.delete().guaranteed().deletingChildrenIfNeeded().withVersion(0).forPath(nodePath);
// 讀取節點數據
Stat stat = new Stat();
byte[] data = cto.client.getData().storingStatIn(stat).forPath(nodePath);
System.out.println("節點" + nodePath + "的數據爲: " + new String(data));
System.out.println("該節點的版本號爲: " + stat.getVersion());
cto.closeZKClient();
boolean isZkCuratorStarted2 = cto.client.isStarted();
System.out.println("當前客戶的狀態:" + (isZkCuratorStarted2 ? "連接中" : "已關閉"));
}
}