zoukankan      html  css  js  c++  java
  • 基于Zookeeper实现多进程分布式锁

    一、zookeeper简介及基本操作

    Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。当对目录节点监控状态打开时,一旦目录节点的状态发生变化,Watcher 对象的 process 方法就会被调用。

    创建Zookeeper实例时即可绑定一个Watcher对象,如 ZooKeeper zk = new ZooKeeper(zookeeperQuorum, sessionTimeout, Watcher; 任何实现org.apache.zookeeper.Watcher接口的类都可作为一个Watcher对象。
    zookeeperQuorum=IP+端口(xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181,xxx.xxx.xxx.xxx:2181)多个逗号隔开
    可以设置观察的操作:exists,getChildren,getData
    可以触发观察的操作:create,delete,setData

    二、基于zookeeper的分布式锁原理

    让我们来回顾一下Zookeeper节点的概念:

    Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Znode。

    Znode分为四种类型:

    1.持久节点 (PERSISTENT)

    默认的节点类型。创建节点的客户端与zookeeper断开连接后,该节点依旧存在 。

    2.持久节点顺序节点(PERSISTENT_SEQUENTIAL)

    所谓顺序节点,就是在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号:


    3.临时节点(EPHEMERAL)

    和持久节点相反,当创建节点的客户端与zookeeper断开连接后,临时节点会被删除:

     

    4.临时顺序节点(EPHEMERAL_SEQUENTIAL)

    顾名思义,临时顺序节点结合和临时节点和顺序节点的特点:在创建节点时,Zookeeper根据创建的时间顺序给该节点名称进行编号;当创建节点的客户端与zookeeper断开连接后,临时节点会被删除。

    Zookeeper分布式锁的原理

    Zookeeper分布式锁恰恰应用了临时顺序节点。具体如何实现呢?让我们来看一看详细步骤:

    获取锁

    首先,在Zookeeper当中创建一个持久节点ParentLock。当第一个客户端想要获得锁时,需要在ParentLock这个节点下面创建一个临时顺序节点 Lock1。


    之后,Client1查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock1是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。


    这时候,如果再有一个客户端 Client2 前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock2。


    Client2查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock2是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。

    于是,Client2向排序仅比它靠前的节点Lock1注册Watcher,用于监听Lock1节点是否存在。这意味着Client2抢锁失败,进入了等待状态。


    这时候,如果又有一个客户端Client3前来获取锁,则在ParentLock下载再创建一个临时顺序节点Lock3。


    Client3查找ParentLock下面所有的临时顺序节点并排序,判断自己所创建的节点Lock3是不是顺序最靠前的一个,结果同样发现节点Lock3并不是最小的。

    于是,Client3向排序仅比它靠前的节点Lock2注册Watcher,用于监听Lock2节点是否存在。这意味着Client3同样抢锁失败,进入了等待状态。


    这样一来,Client1得到了锁,Client2监听了Lock1,Client3监听了Lock2。这恰恰形成了一个等待队列,很像是Java当中ReentrantLock所依赖的

    释放锁

    释放锁分为两种情况:

    1.任务完成,客户端显示释放

    当任务完成时,Client1会显示调用删除节点Lock1的指令。


    2.任务执行过程中,客户端崩溃

    获得锁的Client1在任务执行过程中,如果Duang的一声崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点Lock1会随之自动删除。


    由于Client2一直监听着Lock1的存在状态,当Lock1节点被删除,Client2会立刻收到通知。这时候Client2会再次查询ParentLock下面的所有节点,确认自己创建的节点Lock2是不是目前最小的节点。如果是最小,则Client2顺理成章获得了锁。


    同理,如果Client2也因为任务完成或者节点崩溃而删除了节点Lock2,那么Client3就会接到通知。


    最终,Client3成功得到了锁。

    三、基于zookeeper的分布式锁代码实现

    import java.util.Collections;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;

    import org.apache.zookeeper.CreateMode;
    import org.apache.zookeeper.KeeperException;
    import org.apache.zookeeper.WatchedEvent;
    import org.apache.zookeeper.Watcher;
    import org.apache.zookeeper.ZooDefs;
    import org.apache.zookeeper.ZooKeeper;
    import org.apache.zookeeper.data.Stat;

    public class DistributeLock implements Watcher{
      private ZooKeeper zk;
      //当前锁
      private String current_lock;
      //竞争的资源
      private String lockName;
      //根节点
      private String ROOT_LOCK = "/dlock";
      //由于zookeeper监听节点状态会立即返回,所以需要使用CountDownLatch(也可使用信号量等其他机制)
      private CountDownLatch latch;

      public DistributeLock(String zkAddress, String lockName) {
        this.lockName = lockName;
        try {
          zk = new ZooKeeper(zkAddress, 30000, this);
          //获取根节点状态
          Stat stat = zk.exists(ROOT_LOCK, false);
          //如果根节点不存在,则创建根节点,根节点类型为永久节点
          if(stat == null) {
            System.out.println("根节点不存在");
            zk.create(ROOT_LOCK, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,  CreateMode.PERSISTENT);
          }
        } catch (Exception e) {
          e.printStackTrace();
        }
      }

      //获取锁
      public void lock() {
        try {
          //在根节点下创建临时顺序节点,返回值为创建的节点路径
          current_lock = zk.create(ROOT_LOCK + "/" + lockName, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,  CreateMode.EPHEMERAL_SEQUENTIAL);
          //获取根节点下的所有临时顺序节点,不设置监视器
          List<String> children = zk.getChildren(ROOT_LOCK, false);
          //对根节点下的所有临时顺序节点进行从小到大排序
          Collections.sort(children);
          //判断当前节点是否为最小节点,如果是则获取锁,若不是,则找到自己的前一个节点,监听其存在状态
          int curIndex = Collections.binarySearch(children, current_lock.substring(current_lock.lastIndexOf("/") + 1));
          // if(current_lock.equals(ROOT_LOCK + "/" + children.get(0))) {
          if(curIndex == 0) {
            System.out.println("获取锁成功");
            return;
          }else {
            //获取当前节点前一个节点的路径
            // String prev = children.get(Collections.binarySearch(children, current_lock) - 1);
            String prev = children.get(curIndex - 1);
            //监听当前节点的前一个节点的状态
            Stat stat = zk.exists(ROOT_LOCK + "/" + prev, true);
            //此处再次判断该节点是否存在,该步骤也可省略
            if(stat == null) {
              System.out.println("获取锁成功");
              return;
            }else {
              System.out.println("等待锁......");
              latch = new CountDownLatch(1);
              //进入等待锁状态
              latch.await();
              System.out.println("获取锁成功");
              latch = null;
            }
          }
        } catch (KeeperException e) {
          e.printStackTrace();
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
      //释放锁
      public void unlock() {
        try {
          //删除创建的节点
          zk.delete(current_lock, -1);
          current_lock = null;
          //关闭zookeeper连接
          zk.close();
        } catch (InterruptedException e) {
          e.printStackTrace();
        } catch (KeeperException e) {
          e.printStackTrace();
        }
      }

      @Override
      public void process(WatchedEvent event) {
        if(this.latch != null) {
          latch.countDown();
        }
      }
    }

    启动多个进程进行测试,将以下代码复制多份,启动多个进程,观察输出结果,可以看出已成功实现多进程分布式锁

    import java.text.SimpleDateFormat;
    import java.util.Date;

    public class Test1{

      public static void main(String[] args) throws Exception {
        DistributeLock lock = new DistributeLock("127.0.0.1:2181", "lock");
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        lock.lock();
        System.out.println(sdf.format(new Date()) + "开始执行业务......");
        Thread.sleep(30000);
        System.out.println(sdf.format(new Date()) + "业务处理结束......");
        lock.unlock();
      }
    }

  • 相关阅读:
    登琴台石有感
    天台之约
    游小九寨有感
    又见白沫江
    大学生创业 提前要做好三项准备
    游十八里香草沟有感
    观响水滩瀑布有感
    创业十把金钥匙,看了非常有启发~(转)
    2009年春季商务人士最关注的经管图书
    有了你,就有了诗意的人生
  • 原文地址:https://www.cnblogs.com/jiangwangxiang/p/9957939.html
Copyright © 2011-2022 走看看