一、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();
  }
}

基于Zookeeper实现多进程分布式锁的更多相关文章

  1. 基于zookeeper实现的分布式锁

    基于zookeeper实现的分布式锁 2011-01-27 • 技术 • 7 条评论 • jiacheo •14,941 阅读 A distributed lock base on zookeeper ...

  2. 基于zookeeper实现高性能分布式锁

    实现原理:利用zookeeper的持久性节点和Watcher机制 具体步骤: 1.创建持久性节点 zkLock 2.在此父节点下创建子节点列表,name按顺序定义 3.Java程序获取该节点下的所有顺 ...

  3. 基于zookeeper简单实现分布式锁

    https://blog.csdn.net/desilting/article/details/41280869 这里利用zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watc ...

  4. 基于Zookeeper实现的分布式互斥锁 - InterProcessMutex

    Curator是ZooKeeper的一个客户端框架,其中封装了分布式互斥锁的实现,最为常用的是InterProcessMutex,本文将对其进行代码剖析 简介 InterProcessMutex基于Z ...

  5. 基于数据库、redis和zookeeper实现的分布式锁

    基于数据库 基于数据库(MySQL)的方案,一般分为3类:基于表记录.乐观锁和悲观锁 基于表记录 用表主键或表字段加唯一性索引便可实现,如下: CREATE TABLE `database_lock` ...

  6. java使用zookeeper实现的分布式锁示例

    java使用zookeeper实现的分布式锁示例 作者: 字体:[增加 减小] 类型:转载 时间:2014-05-07我要评论 这篇文章主要介绍了java使用zookeeper实现的分布式锁示例,需要 ...

  7. 基于redis实现的分布式锁

    基于redis实现的分布式锁 我们知道,在多线程环境中,锁是实现共享资源互斥访问的重要机制,以保证任何时刻只有一个线程在访问共享资源.锁的基本原理是:用一个状态值表示锁,对锁的占用和释放通过状态值来标 ...

  8. 如何用Zookeeper来实现分布式锁?

    什么是Zookeeper临时顺序节点? 例如 : / 动物 植物 猫 仓鼠 荷花 松树 Zookeeper的数据存储结构就像一棵树,这棵树由节点组成,这种节点叫做Zonde.# Znode分为四种类型 ...

  9. ZooKeeper 笔记(6) 分布式锁

    目前分布式锁,比较成熟.主流的方案有基于redis及基于zookeeper的二种方案. 大体来讲,基于redis的分布式锁核心指令为SETNX,即如果目标key存在,写入缓存失败返回0,反之如果目标k ...

随机推荐

  1. linux(debian) arm-linux-g++ v4.5.1交叉编译 embedded arm 版本的QtWebkit (browser) 使用qt 4.8.6 版本 以及x64上编译qt

    最近需要做一个项目 在arm 架构的linux下 没有桌面环境的情况下拉起 有界面的浏览器使用. 考虑用qt 的界面和 qtwebikt 的库去实现这一系列操作. 本文参考: Qt移植到ARM Lin ...

  2. nexus的jar包上传与下载

    1. hosted,宿主仓库,部署自己的jar到这个类型的仓库,包括releases和snapshot两部分,Releases公司内部发布版本仓库. Snapshots 公司内部测试版本仓库 2. p ...

  3. Selenium 3----警告框处理+下拉框选择

    警告框处理 在WebDriver中处理JavaScript所生成的alert.confirm以及prompt十分简单,具体做法是使用 switch_to.alert 方法定位到 alert/confi ...

  4. | dp-the Treasure Hunter

    题目: A. Mr. Kitayuta, the Treasure Hunter time limit per test 1 second memory limit per test 256 mega ...

  5. Unicode编码范围

  6. vue 实现子向父传值

    父组件 <template> <div id="app"> <child @onChange='onChildValue'></child ...

  7. promise 的学习

    promise 是为了解决异步操作的顺序问题而产生的 特性 promise 的实例一旦创建就会执行里面的异步操作 promise 的实例状态一旦改变就变成凝固的了, 无法再对其作出修改,  (不明白为 ...

  8. OO第一单元总结与心得体会

    一.结构度量 1. UML类图 第一次作业 第二次作业 第三次作业 2. 复杂度分析 (1)方法复杂度 ​ ev, iv, v这几栏,分别代指基本复杂度(Essential Complexity (e ...

  9. Oracle归档开启和更改

    运用 Xshell  客户端工具链接所在的  oracle  服务器 1.先进入数据库里面去 [root@DBSTANDBY ~]# su - oracle [oracle@DBSTANDBY ~]$ ...

  10. APICloud开发者进阶之路 | txLive模块(直播类)试用分享

    本文出自APICloud官方论坛,感谢论坛版主uoaccw的分享 txLive 模块封装了腾讯云直播服务 https://docs.apicloud.com/Client-API/Open-SDK/t ...