ZooKeeper分布式锁

分布式锁的由来
在程序开发过程中不重点内容得不考虑的就是并发问题。在java中对于同一个jvm而言,jdk已经提供了lock和同步等。但是在分布式情况下,往往存在多个进程对一些资源产生竞争关系,而这些进程往往在不同的机器上,这个时候jdk中提供的已经不能满足。分布式锁顾明思议就是可以满足分布式情况下的并发锁。 下面我们讲解怎么利用zk实现分布式锁。

2.实现思路:
2.1 zk简单介绍:
ZooKeeper是Apache软件基金会的一个软件项目,他为大型分布式计算提供开源的分布式配置服务、同步服务和命名注册。在 ZooKeeper 中,节点类型可以分为持久节点(PERSISTENT )、临时节点(EPHEMERAL),以及时序节点(SEQUENTIAL ),具体在节点创建过程中,一般是组合使用,可以生成 4 种节点类型:持久节点(PERSISTENT),持久顺序节点(PERSISTENT_SEQUENTIAL),临时节点(EPHEMERAL),临时顺序节点(EPHEMERAL_SEQUENTIAL);具体节点含义,谷歌之。

2.2 利用zk实现:
当很多进程需要访问共享资源时,我们可以通过zk来实现分布式锁。主要步骤是:
1.建立一个节点,假如名为:lock 。节点类型为持久节点(PERSISTENT)
2.每当进程需要访问共享资源时,会调用分布式锁的lock()或tryLock()方法获得锁,这个时候会在第一步创建的lock节点下建立相应的顺序子节点,节点类型为临时顺序节点(EPHEMERAL_SEQUENTIAL),通过组成特定的名字name+lock+顺序号。
3.在建立子节点后,对lock下面的所有以name开头的子节点进行排序,判断刚刚建立的子节点顺序号是否是最小的节点,假如是最小节点,则获得该锁对资源进行访问。
4.假如不是该节点,就获得该节点的上一顺序节点,并给该节点是否存在注册监听事件。同时在这里阻塞。等待监听事件的发生,获得锁控制权。
5.当调用完共享资源后,调用unlock()方法,关闭zk,进而可以引发监听事件,释放该锁。
实现的分布式锁是严格的按照顺序访问的并发锁。

Zookeeper主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储,但是 Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管理
客户端要连接 Zookeeper 服务器可以通过创建 org.apache.zookeeper.ZooKeeper 的一个实例对象,然后调用这个类提供的接口来和服务器交互。
前面说了 ZooKeeper 主要是用来维护和监控一个目录节点树中存储的数据的状态,所有我们能够操作 ZooKeeper 的也和操作目录节点树大体一样,如创建一个目录节点,给某个目录节点设置数据,获取某个目录节点的所有子目录节点,给某个目录节点设置权限和监控这个目录节点的状态变化。

常见方法:

String create(String path, byte[] data, List acl,CreateMode createMode) 创建一个给定的节点 path, 并给它设置数据,
CreateMode 标识有四种形式的目录节点:

PERSISTENT:持久化目录节点,这个目录节点存储的数据不会丢失;
PERSISTENT_SEQUENTIAL:顺序自动编号的目录节点,这种目录节点会根据当前已近存在的节点数自动加 1,然后返回给客户端已经成功创建的目录节点名;
EPHEMERAL:临时目录节点,一旦创建这个节点的客户端与服务器端口也就是 session 超时,这种节点会被自动删除;
EPHEMERAL_SEQUENTIAL:临时自动编号节点

  • Stat exists(String path, boolean watch) 判断某个 path
    是否存在,并设置是否监控这个目录节点,这里的 watcher 是在创建 ZooKeeper 实例时指定的 watcher
  • Stat exists(String path,Watcher watcher) 重载方法,这里给某个目录节点设置特定的
    watcher,Watcher 在 ZooKeeper 是一个核心功能,Watcher
    可以监控目录节点的数据变化以及子目录的变化,一旦这些状态发生变化,服务器就会通知所有设置在这个目录节点上的
    Watcher,从而每个客户端都很快知道它所关注的目录节点的状态发生变化,而做出相应的反应
  • void delete(String path, int version) 删除 path 对应的目录节点,version 为 -1
    可以匹配任何版本,也就删除了这个目录节点所有数据

  • List getChildren(String path, boolean watch) 获取指定 path
    下的所有子目录节点,同样 getChildren方法也有一个重载方法可以设置特定的 watcher 监控子节点的状态

  • Stat setData(String path, byte[] data, int version) 给 path
    设置数据,可以指定这个数据的版本号,如果 version 为 -1 怎可以匹配任何版本
  • byte[] getData(String path, boolean watch, Stat stat) 获取这个 path
    对应的目录节点存储的数据,数据的版本等信息可以通过 stat 来指定,同时还可以设置是否监控这个目录节点数据的状态
  • void addAuthInfo(String scheme, byte[] auth)
    客户端将自己的授权信息提交给服务器,服务器将根据这个授权信息验证客户端的访问权限。
  • Stat setACL(String path,List acl, int version)
    给某个目录节点重新设置访问权限,需要注意的是 Zookeeper
    中的目录节点权限不具有传递性,父目录节点的权限不能传递给子目录节点。目录节点 ACL 由两部分组成:perms 和 id。 Perms 有
    ALL、READ、WRITE、CREATE、DELETE、ADMIN 几种,而 id
    标识了访问目录节点的身份列表,默认情况下有以下两种:ANYONE_ID_UNSAFE = new Id(“world”,
    “anyone”) 和 AUTH_IDS = new Id(“auth”, “”) 分别表示任何人都可以访问和创建者拥有访问权限。
  • List getACL(String path,Stat stat) 获取某个目录节点的访问权限列表
// 创建一个与服务器的连接
 ZooKeeper zk = new ZooKeeper("localhost:" + CLIENT_PORT, 
        ClientBase.CONNECTION_TIMEOUT, new Watcher() { 
            // 监控所有被触发的事件
            public void process(WatchedEvent event) { 
                System.out.println("已经触发了" + event.getType() + "事件!"); 
            } 
        }); 
 // 创建一个目录节点
 zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE,
   CreateMode.PERSISTENT); 
 // 创建一个子目录节点
 zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(),
   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
 System.out.println(new String(zk.getData("/testRootPath",false,null))); 
 // 取出子目录节点列表
 System.out.println(zk.getChildren("/testRootPath",true)); 
 // 修改子目录节点数据
 zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1); 
 System.out.println("目录节点状态:["+zk.exists("/testRootPath",true)+"]"); 
 // 创建另外一个子目录节点
 zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(), 
   Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); 
 System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null))); 
 // 删除子目录节点
 zk.delete("/testRootPath/testChildPathTwo",-1); 
 zk.delete("/testRootPath/testChildPathOne",-1); 
 // 删除父目录节点
 zk.delete("/testRootPath",-1); 
 // 关闭连接
 zk.close();

分布式锁

方式1:利用名称唯一性

谁能创建某名称节点,谁就获得锁。
但释放锁时会出现惊群效应,被PASS

方式2:利用临时顺序节点
画了个思维导图如下:

这里写图片描述

利用Zookeeper的EPHEMERAL_SEQUENTIAL类型节点及watcher机制,创建的节点都是有顺序的,每次选择最小编号的节点获得锁。当前节点watcher上一个节点,当上一个节点被删除时,因为是临时节点,也就是上一个节点与zookeeper断开连接时,当前节点成为最小节点,从而获得锁。

//检查当前节点是否为最小节点
public boolean checkMinPath() throws KeeperException, InterruptedException {
        //获取锁节点下所有待争夺节点
        List<String> subNodes = zk.getChildren(GROUP_PATH, false);
        //对所有节点排序
        Collections.sort(subNodes);
        //判断此节点在所有节点中的位置
        int index = subNodes.indexOf( selfPath.substring(GROUP_PATH.length()+1));
        switch (index){
            case -1:{
                System.out.println(PREFIX_OF_THREAD+"本节点已不在了..."+selfPath);
                return false;
            }
            case 0:{
                System.out.println(PREFIX_OF_THREAD+"子节点中,我最小,可以获得锁了!哈哈"+selfPath);
                return true;
            }
            default:{
                this.waitPath = GROUP_PATH +"/"+ subNodes.get(index - 1);
                System.out.println(PREFIX_OF_THREAD+"排在我前面的节点是 "+waitPath);
                try{
                    zk.getData(waitPath, true, new Stat());
                    return false;
                }catch(KeeperException e){
                    if(zk.exists(waitPath,false) == null){
                        System.out.println(PREFIX_OF_THREAD+"排在我前面的"+waitPath+"已消失 ");
                        return checkMinPath();
                    }else{
                        throw e;
                    }
                }
            }
        }
    }
阅读更多

扫码向博主提问

twjitm

非学,无以致疑;非问,无以广识
  • 擅长领域:
  • java
  • spring
  • mybatis
  • hibernate
  • javaweb
去开通我的Chat快问
版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/baidu_23086307/article/details/80902439
个人分类: zookeeper学习
上一篇exclusions来进行排除依赖
下一篇idea 创建maven子项目报错
想对作者说点什么? 我来说一句

没有更多推荐了,返回首页

关闭
关闭
关闭