Java操作Zookeeper

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>

zondezookeeper集合的核心组件,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)
    
  • 参数解释
    pathznode路径
    data数据
    acl要创建的节点的访问控制列表。zookeeper API提供了一个静态接口 ZooDefs.Ids 来获取一些基本的acl列表。例如,ZooDefs.Ids.OPEN_ACL_UNSAFE返回打开znodeacl列表
    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接口的类就算一个新的WatcherWatcher内部包含了两个枚举类:KeeperStateEventType

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(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
    NodeCreatedWatcher监听的数据节点被创建时
    NodeDeletedWatcher监听的数据节点被删除时
    NodeDataChangedWatcher监听的数据节点内容发生更改时(无论数据是否真的变化)
    NodeChildrenChangedWatcher监听的数据节点的子节点列表发生变更时
  • 注意:客户端接收到的相关事件通知中只包含状态以及类型等信息,不包含节点变化前后的具体内容,变化前的数据需业务自身存储,变化后的数据需要调用get等方法重新获取

捕获相应的事件

上面讲到zookeeper客户端连接的状态和zookeeperznode节点监听的事件类型,下面我们来讲解如何建立zookeeper的***watcher监听***。在zookeeper中采用zk.getChildren(path,watch)、zk.exists(path,watch)、zk.getData(path,watcher,stat)这样的方式来为某个znode注册监听 。

下表以node-x节点为例,说明调用的注册方法和可用监听事件间的关系:

注册方式createdchildrenChangedChangedDeleted
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可以轻松完成,当数据库发生变化时自动完成缓存同步

使用事件监听机制可以做出一个简单的配置中心

设计思路

  1. 连接zookeeper服务器
  2. 读取zookeeper中的配置信息,注册watcher监听器,存入本地变量
  3. zookeeper中的配置信息发生变化时,通过watcher的回调方法捕获数据变化事件
  4. 重新获取配置信息
分布式唯一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中如果实现排他锁

设计思路

  1. 每个客户端往/Locks下创建临时有序节点/Locks/Lock_,创建成功后/Locks下面会有每个客户端对应的节点,如/Locks/Lock_000000001
  2. 客户端取得/Locks下子节点,并进行排序,判断排在前面的是否为自己,如果自己的锁节点在第一位,代表获取锁成功
  3. 如果自己的锁节点不在第一位,则监听自己前一位的锁节点。例如,自己锁节点Lock_000000002,那么则监听Lock_000000001
  4. 当前一位锁节点(Lock_000000001)对应的客户端执行完成,释放了锁,将会触发监听客户端(Lock_000000002)的逻辑
  5. 监听客户端重新执行第2步逻辑,判断自己是否获得了锁
  6. 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();
        }
    }
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值