一、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. 操作mysql(import pymysql模块)

    pymysql模块 import pymysql #1.连上数据库.账号.密码.ip.端口号.数据库 #2.建立游标 #3.执行sql #4.获取结果 #5.关闭游标 #6.连接关闭 #charest ...

  2. mapState ,mapGetters ,mapMutations,mapActions

    参考 http://www.imooc.com/article/14741

  3. JavaScript 中的FileReader对象(实现上传图片预览)

    方法一:使用js的FileReader对象 1.FileReader对象简介 1.检测浏览器对FileReader的支持 if(window.FileReader) { var fr = new Fi ...

  4. Web Driver 8中定位方法 ——基于python语言

    WebDriver提供了八种元素定位方法,在python 语言中,方法如下:  id定位:find_element_by_id("id值"):id属性是唯一的.  1 driver ...

  5. 关于 vuex 的使用忠告

    第一.看明白这张图在说话 简单解释一下,actions接收到components的行为后actions请求api 等获取数据,提交到mutations,然后mutations中才改变state ,反映 ...

  6. Windows下搭建kafka运行环境

    完整解决方案请参考: Setting Up and Running Apache Kafka on Windows OS   在环境搭建过程中遇到两个问题,在这里先列出来,以方便查询: 1. \Jav ...

  7. sqlmap常用命令

    sqlmap也是渗透中常用的一个注入工具,其实在注入工具方面,一个sqlmap就足够用了,只要你用的熟,秒杀各种工具,只是一个便捷性问题,sql注入另一方面就是手工党了,这个就另当别论了.今天把我一直 ...

  8. cve-2018-4878漏洞复现

    CVE-2018-4878Flash 0day漏洞1.漏洞概述2018年2月1号,Adobe官方发布安全通报(APSA18-01),声明Adobe Flash 28.0.0.137及其之前的版本,存在 ...

  9. jmeter之regular expression extractor ,并循环调用匹配到的多个值

    jmeter之regular expression extractor 官方介绍:http://jmeter.apache.org/usermanual/regular_expressions.htm ...

  10. linux 逆向映射

    逆向映射用于建立物理内存页和使用该页的进程的对应页表项之间的联系,在换出页时以便更新所有涉及的进程.得到物理页基址后,根据pfn_to_page可以将页框转换为page实例,page实例中的mappi ...