zookeeper的 JavaAPI
<dependency>
<groupId>com.101tec</groupId>
<artifactId>zkclient</artifactId>
<exclusions>
<exclusion>
<artifactId>zookeeper</artifactId>
<groupId>org.apache.zookeeper</groupId>
</exclusion>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-api</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<version>0.9</version>
</dependency>
<dependency>
<artifactId>zookeeper</artifactId>
<exclusions>
<exclusion>
<artifactId>log4j</artifactId>
<groupId>log4j</groupId>
</exclusion>
<exclusion>
<artifactId>slf4j-log4j12</artifactId>
<groupId>org.slf4j</groupId>
</exclusion>
</exclusions>
<groupId>org.apache.zookeeper</groupId>
<version>3.4.10</version>
</dependency>
zonde
是 zookeeper
集合的核心组件,zookeeper API
提供了一小组使用 zookeeper
集群来操作znode
的所有细节
客户端应该遵循以下步骤,与zookeeper
服务器进行清晰和干净的交互
- 连接到
zookeeper
服务器。zookeeper
服务器为客户端分配会话ID
- 定期向服务器发送心跳。否则,
zookeeper
服务器将过期会话ID
,客户端需要重新连接 - 只要会话
Id
处于活动状态,就可以获取/设置znode
- 所有任务完成后,断开与
zookeeper
服务器连接,如果客户端长时间不活动,则zookeeper
服务器将自动断开客户端
连接到Zookeeper
这部分,官网的解释十分稀少https://zookeeper.apache.org/doc/r3.4.14/zookeeperStarted.html#sc_ConnectingToZooKeeper
[zkshell: 0] help
ZooKeeper host:port cmd args
get path [watch]
ls path [watch]
set path data [version]
delquota [-n|-b] path
quit
printwatches on|off
create path data acl
stat path [watch]
listquota path
history
setAcl path acl
getAcl path
sync path
redo cmdno
addauth scheme auth
delete path [version]
deleteall path
setquota -n|-b val path
Zookeeper(String connectionString, int sessionTimeout, watcher watcher)
connectionString
-zookeeper
主机sessionTimeout
- 会话超时watcher
- 实现"监听器" 对象。zookeeper
集合通过监视器对象返回连接状态
public static void main(String[] args) throws IOException, InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
ZooKeeper zookeeper = new ZooKeeper("192.168.133.133:2181", 5000, (WatchedEvent x) -> {
if (x.getState() == Watcher.Event.KeeperState.SyncConnected) {
System.out.println("连接成功");
countDownLatch.countDown();
}
});
countDownLatch.await();
System.out.println(zookeeper.getSessionId());
zookeeper.close();
}
新增节点
-
// 同步 create(String path, byte[] data, List<ACL> acl, CreateMode createMode) // 异步 create(String path, byte[] data, List<ACL> acl, CreateMode createMode, AsynCallback.StringCallback callBack, Object ctx)
-
参数 解释 path
znode
路径data
数据 acl
要创建的节点的访问控制列表。 zookeeper API
提供了一个静态接口ZooDefs.Ids
来获取一些基本的acl
列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE
返回打开znode
的acl
列表createMode
节点的类型,这是一个枚举 callBack
异步回调接口 ctx
传递上下文参数
示例:
-
// 枚举的方式 public static void createTest1() throws Exception{ String str = "node"; String s = zookeeper.create("/node", str.getBytes(), ZooDefs.Ids.READ_ACL_UNSAFE, CreateMode.PERSISTENT); System.out.println(s); }
-
// 自定义的方式 public static void createTest2() throws Exception{ ArrayList<ACL> acls = new ArrayList<>(); Id id = new Id("ip","192.168.133.133"); acls.add(new ACL(ZooDefs.Perms.ALL,id)); zookeeper.create("/create/node4","node4".getBytes(),acls,CreateMode.PERSISTENT); }
-
// auth public static void createTest3() throws Exception{ zookeeper.addAuthInfo("digest","itcast:12345".getBytes()); zookeeper.create("/node5","node5".getBytes(), ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.PERSISTENT); } // 自定义的方式 public static void createTest3() throws Exception{ // zookeeper.addAuthInfo("digest","itcast:12345".getBytes()); // zookeeper.create("/node5","node5".getBytes(), // ZooDefs.Ids.CREATOR_ALL_ACL,CreateMode.PERSISTENT); zookeeper.addAuthInfo("digest","itcast:12345".getBytes()); List<ACL> acls = new ArrayList<>(); Id id = new Id("auth","itcast"); acls.add(new ACL(ZooDefs.Perms.READ,id)); zookeeper.create("/create/node6","node6".getBytes(), acls,CreateMode.PERSISTENT); }
-
// digest public static void createTest3() throws Exception{ List<ACL> acls = new ArrayList<>(); Id id = new Id("digest","itcast:qUFSHxJjItUW/93UHFXFVGlvryY="); acls.add(new ACL(ZooDefs.Perms.READ,id)); zookeeper.create("/create/node7","node7".getBytes(), acls,CreateMode.PERSISTENT); }
-
// 异步 public static void createTest4() throws Exception{ zookeeper.create("/node12", "node12".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, new AsyncCallback.StringCallback(){ /** * @param rc 状态,0 则为成功,以下的所有示例都是如此 * @param path 路径 * @param ctx 上下文参数 * @param name 路径 */ public void processResult(int rc, String path, Object ctx, String name){ System.out.println(rc + " " + path + " " + name + " " + ctx); } }, "I am context"); TimeUnit.SECONDS.sleep(1); System.out.println("结束"); }
修改节点
同样也有两种修改方式(异步和同步
)
-
// 同步 setData(String path, byte[] data, int version) // 异步 setData(String path, byte[] data, int version, StatCallback callBack, Object ctx)
-
参数 解释 path
节点路径 data
数据 version
数据的版本号, - 1
代表不使用版本号,乐观锁机制callBack
异步回调 AsyncCallback.StatCallback
,和之前的回调方法参数不同,这个可以获取节点状态ctx
传递上下文参数 -
public static void setData1() throws Exception{ // arg1:节点的路径 // arg2:修改的数据 // arg3:数据的版本号 -1 代表版本号不参与更新 Stat stat = zookeeper.setData("/hadoop","hadoop-1".getBytes(),-1); }
-
public static void setData2() throws Exception{ zookeeper.setData("/hadoop", "hadoop-1".getBytes(), 3 ,new AsyncCallback.StatCallback(){ @Override public void processResult(int rc, String path, Object ctx, Stat stat) { // 讲道理,要判空 System.out.println(rc + " " + path + " " + stat.getVersion() + " " + ctx); } }, "I am context"); }
删除节点
异步、同步
-
// 同步 delete(String path, int version) // 异步 delete(String path, int version, AsyncCallback.VoidCallback callBack, Object ctx)
-
参数 解释 path
节点路径 version
版本 callBack
数据的版本号, - 1
代表不使用版本号,乐观锁机制ctx
传递上下文参数 -
public static void deleteData1() throws Exception { zookeeper.delete("/hadoop", 1); } public static void deleteData2() throws Exception { zookeeper.delete("/hadoop", 1, new AsyncCallback.VoidCallback() { @Override public void processResult(int rc, String path, Object ctx) { System.out.println(rc + " " + path + " " + ctx); } }, "I am context"); TimeUnit.SECONDS.sleep(1); }
查看节点
同步、异步
-
// 同步 getData(String path, boolean watch, Stat stat) getData(String path, Watcher watcher, Stat stat) // 异步 getData(String path, boolean watch, DataCallback callBack, Object ctx) getData(String path, Watcher watcher, DataCallback callBack, Object ctx)
-
参数 解释 path
节点路径 boolean
是否使用连接对象中注册的监听器 stat
元数据 callBack
异步回调接口,可以获得状态和数据 ctx
传递上下文参数 -
public static void getData1() throws Exception { Stat stat = new Stat(); byte[] data = zookeeper.getData("/hadoop", false, stat); System.out.println(new String(data)); // 判空 System.out.println(stat.getCtime()); } public static void getData2() throws Exception { zookeeper.getData("/hadoop", false, new AsyncCallback.DataCallback() { @Override public void processResult(int rc, String path, Object ctx, byte[] bytes, Stat stat) { // 判空 System.out.println(rc + " " + path + " " + ctx + " " + new String(bytes) + " " + stat.getCzxid()); } }, "I am context"); TimeUnit.SECONDS.sleep(3); }
查看子节点
同步、异步
-
// 同步 getChildren(String path, boolean watch) getChildren(String path, Watcher watcher) getChildren(String path, boolean watch, Stat stat) getChildren(String path, Watcher watcher, Stat stat) // 异步 getChildren(String path, boolean watch, ChildrenCallback callBack, Object ctx) getChildren(String path, Watcher watcher, ChildrenCallback callBack, Object ctx) getChildren(String path, Watcher watcher, Children2Callback callBack, Object ctx) getChildren(String path, boolean watch, Children2Callback callBack, Object ctx)
-
参数 解释 path
节点路径 boolean
callBack
异步回调,可以获取节点列表 ctx
传递上下文参数 -
public static void getChildren_1() throws Exception{ List<String> hadoop = zookeeper.getChildren("/hadoop", false); hadoop.forEach(System.out::println); } public static void getChildren_2() throws Exception { zookeeper.getChildren("/hadoop", false, new AsyncCallback.ChildrenCallback() { @Override public void processResult(int rc, String path, Object ctx, List<String> list) { list.forEach(System.out::println); System.out.println(rc + " " + path + " " + ctx); } }, "I am children"); TimeUnit.SECONDS.sleep(3); }
检查节点是否存在
同步、异步
-
// 同步 exists(String path, boolean watch) exists(String path, Watcher watcher) // 异步 exists(String path, boolean watch, StatCallback cb, Object ctx) exists(String path, Watcher watcher, StatCallback cb, Object ctx)
-
参数 解释 path
节点路径 boolean
callBack
异步回调,可以获取节点列表 ctx
传递上下文参数 -
public static void exists1() throws Exception{ Stat exists = zookeeper.exists("/hadoopx", false); // 判空 System.out.println(exists.getVersion() + "成功"); } public static void exists2() throws Exception{ zookeeper.exists("/hadoopx", false, new AsyncCallback.StatCallback() { @Override public void processResult(int rc, String path, Object ctx, Stat stat) { // 判空 System.out.println(rc + " " + path + " " + ctx +" " + stat.getVersion()); } }, "I am children"); TimeUnit.SECONDS.sleep(1); }
事件监听机制
watcher概念
https://zookeeper.apache.org/doc/r3.4.14/zookeeperProgrammers.html#sc_WatchRememberThese
zookeeper
提供了数据的发布/订阅
功能,多个订阅者可同时监听某一特定主题对象,当该主题对象的自身状态发生变化时例如节点内容改变、节点下的子节点列表改变等,会实时、主动通知所有订阅者zookeeper
采用了Watcher
机制实现数据的发布订阅功能。该机制在被订阅对象发生变化时会异步通知客户端,因此客户端不必在Watcher
注册后轮询阻塞,从而减轻了客户端压力watcher
机制事件上与观察者模式类似,也可看作是一种观察者模式在分布式场景下的实现方式
watcher架构
watcher
实现由三个部分组成
zookeeper
服务端zookeeper
客户端- 客户端的
ZKWatchManager对象
客户端首先将 Watcher
注册到服务端,同时将 Watcher
对象保存到客户端的watch
管理器中。当Zookeeper
服务端监听的数据状态发生变化时,服务端会主动通知客户端,接着客户端的 Watch
管理器会**触发相关 Watcher
**来回调相应处理逻辑,从而完成整体的数据 发布/订阅
流程
watcher特性
-
特性 说明 一次性 watcher
是一次性的,一旦被触发就会移除,再次使用时需要重新注册客户端顺序回调 watcher
回调是顺序串行执行的,只有回调后客户端才能看到最新的数据状态。一个watcher
回调逻辑不应该太多,以免影响别的watcher
执行轻量级 WatchEvent
是最小的通信单位,结构上只包含通知状态、事件类型和节点路径,并不会告诉数据节点变化前后的具体内容时效性 watcher
只有在当前session
彻底失效时才会无效,若在session
有效期内快速重连成功,则watcher
依然存在,仍可接收到通知;
watcher接口设计
Watcher
是一个接口,任何实现了Watcher
接口的类就算一个新的Watcher
。Watcher
内部包含了两个枚举类:KeeperState
、EventType
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-skz27hDm-1609679102665)(assets/zookeeper-7.png)]
Watcher通知状态(KeeperState)
KeeperState
是客户端与服务端连接状态发生变化时对应的通知类型。路径为org.apache.zookeeper.Watcher.EventKeeperState
,是一个枚举类,其枚举属性如下:
-
枚举属性 说明 SyncConnected
客户端与服务器正常连接时 Disconnected
客户端与服务器断开连接时 Expired
会话 session
失效时AuthFailed
身份认证失败时
Watcher事件类型(EventType)
EventType
是数据节点znode
发生变化时对应的通知类型。EventType
变化时KeeperState
永远处于SyncConnected
通知状态下;当keeperState
发生变化时,EventType
永远为None
。其路径为org.apache.zookeeper.Watcher.Event.EventType
,是一个枚举类,枚举属性如下:
-
枚举属性 说明 None
无 NodeCreated
Watcher
监听的数据节点被创建时NodeDeleted
Watcher
监听的数据节点被删除时NodeDataChanged
Watcher
监听的数据节点内容发生更改时(无论数据是否真的变化)NodeChildrenChanged
Watcher
监听的数据节点的子节点列表发生变更时 -
注意:客户端接收到的相关事件通知中只包含状态以及类型等信息,不包含节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需要调用
get
等方法重新获取
捕获相应的事件
上面讲到zookeeper
客户端连接的状态和zookeeper
对znode
节点监听的事件类型,下面我们来讲解如何建立zookeeper
的***watcher
监听***。在zookeeper
中采用zk.getChildren(path,watch)、zk.exists(path,watch)、zk.getData(path,watcher,stat)
这样的方式来为某个znode
注册监听 。
下表以node-x
节点为例,说明调用的注册方法和可用监听事件间的关系:
注册方式 | created | childrenChanged | Changed | Deleted |
---|---|---|---|---|
zk.exists("/node-x",watcher) | 可监控 | 可监控 | 可监控 | |
zk.getData("/node-x",watcher) | 可监控 | 可监控 | ||
zk.getChildren("/node-x",watcher) | 可监控 | 可监控 |
注册watcher的方法
客户端与服务器端的连接状态
-
KeeperState
:通知状态 -
SyncConnected
:客户端与服务器正常连接时 -
Disconnected
:客户端与服务器断开连接时 -
Expired
:会话session
失效时 -
AuthFailed
:身份认证失败时 -
事件类型为:
None
-
案例
-
public class ZkConnectionWatcher implements Watcher { @Override public void process(WatchedEvent watchedEvent) { Event.KeeperState state = watchedEvent.getState(); if(state == Event.KeeperState.SyncConnected){ // 正常 System.out.println("正常连接"); }else if (state == Event.KeeperState.Disconnected){ // 可以用Windows断开虚拟机网卡的方式模拟 // 当会话断开会出现,断开连接不代表不能重连,在会话超时时间内重连可以恢复正常 System.out.println("断开连接"); }else if (state == Event.KeeperState.Expired){ // 没有在会话超时时间内重新连接,而是当会话超时被移除的时候重连会走进这里 System.out.println("连接过期"); }else if (state == Event.KeeperState.AuthFailed){ // 在操作的时候权限不够会出现 System.out.println("授权失败"); } countDownLatch.countDown(); } private static final String IP = "192.168.133.133:2181" ; private static CountDownLatch countDownLatch = new CountDownLatch(1); public static void main(String[] args) throws Exception { // 5000为会话超时时间 ZooKeeper zooKeeper = new ZooKeeper(IP, 5000, new ZkConnectionWatcher()); countDownLatch.await(); // 模拟授权失败 zooKeeper.addAuthInfo("digest1","itcast1:123451".getBytes()); byte[] data = zooKeeper.getData("/hadoop", false, null); System.out.println(new String(data)); TimeUnit.SECONDS.sleep(50); } }
-
watcher检查节点
exists
-
exists(String path, boolean b)
-
exists(String path, Watcher w)
-
NodeCreated
:节点创建 -
NodeDeleted
:节点删除 -
NodeDataChanged
:节点内容-
案例
-
public class EventTypeTest { private static final String IP = "192.168.133.133:2181"; private static CountDownLatch countDownLatch = new CountDownLatch(1); private static ZooKeeper zooKeeper; // 采用zookeeper连接创建时的监听器 public static void exists1() throws Exception{ zooKeeper.exists("/watcher1",true); } // 自定义监听器 public static void exists2() throws Exception{ zooKeeper.exists("/watcher1",(WatchedEvent w) -> { System.out.println("自定义" + w.getType()); }); } // 演示使用多次的监听器 public static void exists3() throws Exception{ zooKeeper.exists("/watcher1", new Watcher() { @Override public void process(WatchedEvent watchedEvent) { try { System.out.println("自定义的" + watchedEvent.getType()); } finally { try { zooKeeper.exists("/watcher1",this); } catch (Exception e) { e.printStackTrace(); } } } }); } // 演示一节点注册多个监听器 public static void exists4() throws Exception{ zooKeeper.exists("/watcher1",(WatchedEvent w) -> { System.out.println("自定义1" + w.getType()); }); zooKeeper.exists("/watcher1", new Watcher() { @Override public void process(WatchedEvent watchedEvent) { try { System.out.println("自定义2" + watchedEvent.getType()); } finally { try { zooKeeper.exists("/watcher1",this); } catch (Exception e) { e.printStackTrace(); } } } }); } // 测试 public static void main(String[] args) throws Exception { zooKeeper = new ZooKeeper(IP, 5000, new ZKWatcher()); countDownLatch.await(); exists4(); TimeUnit.SECONDS.sleep(50); } static class ZKWatcher implements Watcher{ @Override public void process(WatchedEvent watchedEvent) { countDownLatch.countDown(); System.out.println("zk的监听器" + watchedEvent.getType()); } } }
-
getData
getData(String path, boolean b, Stat stat)
getData(String path, Watcher w, Stat stat)
NodeDeleted
:节点删除NodeDataChange
:节点内容发生变化
getChildren
getChildren(String path, boolean b)
getChildren(String path, Watcher w)
NodeChildrenChanged
:子节点发生变化NodeDeleted
:节点删除
配置中心案例
工作中有这样的一个场景:数据库用户名和密码信息放在一个配置文件中,应用读取该配置文件,配置文件信息放入缓存
若数据库的用户名和密码改变时候,还需要重新加载媛存,比较麻烦,通过 Zookeeper
可以轻松完成,当数据库发生变化时自动完成缓存同步
使用事件监听机制可以做出一个简单的配置中心
设计思路
- 连接
zookeeper
服务器 - 读取
zookeeper
中的配置信息,注册watcher
监听器,存入本地变量 - 当
zookeeper
中的配置信息发生变化时,通过watcher
的回调方法捕获数据变化事件 - 重新获取配置信息
分布式唯一id案例
在过去的单库单表型系统中,通常第可以使用数据库字段自带的auto_ increment
属性来自动为每条记录生成个唯一的ID
。但是分库分表后,就无法在依靠数据库的auto_ increment
属性来唯一标识一条记录了。此时我们就可以用zookeeper
在分布式环境下生成全局唯一ID
public class IdGenerate {
private static final String IP = "192.168.133.133:2181";
private static CountDownLatch countDownLatch = new CountDownLatch(1);
private static ZooKeeper zooKeeper;
public static String generateId() throws Exception {
return zooKeeper.create("/id", new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
}
public static void main(String[] args) throws Exception {
zooKeeper = new ZooKeeper(IP, 5000, new ZKWatcher());
countDownLatch.await();
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(5, 5, 0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
for (int i = 0; i < 10; i++) {
threadPoolExecutor.execute(() -> {
try {
System.out.println(generateId());
} catch (Exception e) {
e.printStackTrace();
}
});
}
TimeUnit.SECONDS.sleep(50);
threadPoolExecutor.shutdown();
}
static class ZKWatcher implements Watcher {
@Override
public void process(WatchedEvent watchedEvent) {
countDownLatch.countDown();
System.out.println("zk的监听器" + watchedEvent.getType());
}
}
}
分布式锁
分布式锁有多种实现方式,比如通过数据库、redis都可实现。作为分布式协同工具Zookeeper
,当然也有着标准的实现方式。下面介绍在zookeeper
中如果实现排他锁
设计思路
- 每个客户端往
/Locks
下创建临时有序节点/Locks/Lock_
,创建成功后/Locks
下面会有每个客户端对应的节点,如/Locks/Lock_000000001
- 客户端取得/Locks下子节点,并进行排序,判断排在前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
- 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点
Lock_000000002
,那么则监听Lock_000000001
- 当前一位锁节点
(Lock_000000001)
对应的客户端执行完成,释放了锁,将会触发监听客户端(Lock_000000002)
的逻辑 - 监听客户端重新执行第
2
步逻辑,判断自己是否获得了锁 - zookeeper是有工具包的(这里采用手写)
// 线程测试类
public class ThreadTest {
public static void delayOperation(){
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
static interface Runable{
void run();
}
public static void run(Runable runable,int threadNum){
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(30, 30,
0, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10));
for (int i = 0; i < threadNum; i++) {
threadPoolExecutor.execute(runable::run);
}
threadPoolExecutor.shutdown();
}
public static void main(String[] args) {
// DistributedLock distributedLock = new DistributedLock();
// distributedLock.acquireLock();
// delayOperation();
// distributedLock.releaseLock();
DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
// 每秒打印信息
run(() -> {
for (int i = 0; i < 999999999; i++) {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
String format = dateTimeFormatter.format(LocalDateTime.now());
System.out.println(format);
}
},1);
// 线程测试
run(() -> {
DistributedLock distributedLock = new DistributedLock();
distributedLock.acquireLock();
delayOperation();
distributedLock.releaseLock();
},30);
}
}
public class DistributedLock {
private String IP = "192.168.133.133:2181";
private final String ROOT_LOCK = "/Root_Lock";
private final String LOCK_PREFIX = "/Lock_";
private final CountDownLatch countDownLatch = new CountDownLatch(1);
private final byte[] DATA = new byte[0];
private ZooKeeper zookeeper;
private String path;
private void init(){
// 初始化
try {
zookeeper = new ZooKeeper(IP, 200000, w -> {
if(w.getState() == Watcher.Event.KeeperState.SyncConnected){
System.out.println("连接成功");
}
countDownLatch.countDown();
});
countDownLatch.await();
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
// 暴露的外部方法,主逻辑
public void acquireLock(){
init();
createLock();
attemptLock();
}
// 暴露的外部方法,主逻辑
public void releaseLock(){
try {
zookeeper.delete(path,-1);
System.out.println("锁释放了" + path);
} catch (InterruptedException | KeeperException e) {
e.printStackTrace();
}
}
private void createLock(){
try {
// 创建一个目录节点
Stat root = zookeeper.exists(ROOT_LOCK, false);
if(root == null)
zookeeper.create(ROOT_LOCK, DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 目录下创建子节点
path = zookeeper.create(ROOT_LOCK + LOCK_PREFIX, DATA, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
private Watcher watcher = new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType() == Event.EventType.NodeDeleted){
synchronized (this){
this.notifyAll();
}
}
}
};
private void attemptLock(){
try {
// 获取正在排队的节点,由于是zookeeper生成的临时节点,不会出错,这里不能加监视器
// 因为添加了监视器后,任何子节点的变化都会触发监视器
List<String> nodes = zookeeper.getChildren(ROOT_LOCK,false);
nodes.sort(String::compareTo);
// 获取自身节点的排名
int ranking = nodes.indexOf(path.substring(ROOT_LOCK.length() + 1));
// 已经是最靠前的节点了,获取锁
if(ranking == 0){
return;
}else {
// 并不是靠前的锁,监视自身节点的前一个节点
Stat status = zookeeper.exists(ROOT_LOCK+"/"+nodes.get(ranking - 1), watcher);
// 有可能这这个判断的瞬间,0号完成了操作(此时我们应该判断成功自旋才对),但是上面的status变量已经获取了值并且不为空,1号沉睡
// 但是,请注意自行测试,虽然1号表面上沉睡了,但是实际上watcher.wait()是瞬间唤醒的
if(status == null){
attemptLock();
}else {
synchronized (watcher){
watcher.wait();
}
attemptLock();
}
}
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
}