【Zookeeper】Zookeeper实现分布式锁

zookeeper实现分布式锁

一:zookeeper分布式锁原理

1:原子性

Zookeeper有写操作有原子性,利用这个特性可以实现分布式锁。

对于来自客户端的每个更新请求,ZooKeeper 具备严格的顺序访问控制能力。

为了保证事务的顺序一致性,ZooKeeper 采用了递增的事务 id 号(zxid)来标识事务。

2:Watcher 回调机制

客户端注册监听它关心的 znode,当 znode 状态发生变化(数据变化、子节点增减变化)时,ZooKeeper 服务会通知客户端。

客户端和服务端保持连接一般有两种形式:

  • 客户端向服务端不断轮询
  • 服务端向客户端推送状态

Zookeeper 的选择是服务端主动推送状态,也就是观察机制( Watch )。

二:实现分布式锁

我们可以通过Zookeeper的临时节点和watcher机制实现分布式锁

分布式锁

1:实现tryLock

  public static ThreadLocal<CuratorZookeeperClient>  zkClient = null;

    public static String LOCK_NAME = "/lock";

    public static ThreadLocal<String> CURRENT_NODE = new ThreadLocal<>();

    static {
        zkClient.set(new CuratorZookeeperClient("localhost:8080",2000,2000,null,new RetryOneTime(1)));
    }


@Override
    public boolean tryLock()  {
        String nodeName = LOCK_NAME + "/zk_";
        try {
            CURRENT_NODE.set(zkClient.get().getZooKeeper().create(nodeName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL));

            List<String> children = zkClient.get().getZooKeeper().getChildren(nodeName, false);

            Collections.sort(children);

            // 判断是否获得锁
            String minNode = children.get(0);
            if ((LOCK_NAME +"/"+minNode).equals(CURRENT_NODE.get())){
                // 获取到锁
                return true;
            }
            // 没有获取到锁,监听前一个节点是否存在
            // 获取前一个节点的index
          int i = children.indexOf(CURRENT_NODE.get().substring(CURRENT_NODE.get().lastIndexOf("/") + 1));
            // 获取前一个节点
          String preNodeName = children.get(i);
          // 增加countDownLatch 同步线程
          CountDownLatch countDownLatch = new CountDownLatch(1);
          zkClient.get().getZooKeeper().exists(LOCK_NAME + "/" + preNodeName, new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
              // 监听前一个节点,前一个临时节点消失就是代表当前线程为最小的那个节点,可以获取到锁
              if (Event.EventType.NodeDeleted.equals(watchedEvent.getType())){
                countDownLatch.countDown();
              }
            }
          });
           countDownLatch.await();

        } catch (Exception e) {
            e.printStackTrace();
        }
        return true;
    }

二:解锁:删除节点就可以了

 @Override
    public void unlock() {
      try {
        zkClient.get().getZooKeeper().delete(CURRENT_NODE.get(),-1);
        CURRENT_NODE.remove();
      } catch (Exception e) {
        e.printStackTrace();
      }finally{
        zkClient.get().close();
      }
    }

这只是个demo没有经过测试的,主要展示一下逻辑

三:和Redis分布式锁的优劣势

优点:不存在redis的超时、数据同步(zookeeper是同步完以后才返回)、主从切换(zookeeper主从切换的过程中服务是不可用的)的问题,可靠性很高。

缺点:依赖中间件,保证了可靠性的同时牺牲了一部分效率(但是依然很高)。性能不如redis。

原文地址:https://www.cnblogs.com/simple-flw/p/14825210.html