ZooKeeper基础命令特点和分布式锁实现的原理

版权声明:本文为zjcjava原创文章,转载请注明出处http://blog.csdn.net/zjcjava https://blog.csdn.net/zjcjava/article/details/85390942

ZooKeeper设计目的

1.最终一致性:client不论连接到哪个Server,展示给它都是同一个视图,这是zookeeper最重要的性能。

2.可靠性:具有简单、健壮、良好的性能,如果消息m被到一台服务器接受,那么它将被所有的服务器接受。

3.实时性:Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息。但由于网络延时等原因,Zookeeper不能保证两个客户端能同时得到刚更新的数据,如果需要最新数据,应该在读数据之前调用sync()接口。

4.等待无关(wait-free):慢的或者失效的client不得干预快速的client的请求,使得每个client都能有效的等待。

5.原子性:更新只能成功或者失败,没有中间状态。

6.顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面。

znode创建类型(CreateMode)

有以下四种:

PERSISTENT 持久化节点,能创建子节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。

PERSISTENT_SEQUENTIAL 顺序持久化节点,除了持久性特点外,这种节点会根据当前已存在的节点数自动加 1,如设置test,则会变成test1

EPHEMERAL 临时节点,不能创建子节点, 客户端session超时这类节点就会被自动删除
> 和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
这里还要注意一件事,就是当你客户端会话失效后,所产生的节点也不是一下子就消失了,也要过一段时间,大概是10秒以内,可以试一下,本机操作生成节点,在服务器端用命令来查看当前的节点数目,你会发现客户端已经stop,但是产生的节点还在。

EPHEMERAL_SEQUENTIAL 临时顺序节点,此节点是属于临时节点,不过带有顺序,客户端会话结束节点就消失。
>下面是一个利用该特性的分布式锁的案例流程。
(1)客户端调用create()方法创建名为“locknode/guid-lock-1”的节点,需要注意的是,这里节点的创建类型需要设置为EPHEMERAL_SEQUENTIAL。
(2)客户端调用getChildren(“locknode”)方法来获取所有已经创建的子节点,注意,这里不注册任何Watcher。
(3)客户端获取到所有子节点path之后,如果发现自己在步骤1中创建的节点序号最小,那么就认为这个客户端获得了锁。
(4)如果在步骤3中发现自己并非所有子节点中最小的,说明自己还没有获取到锁。此时客户端需要找到比自己小的那个节点,然后对其调用exist()方法,同时注册事件监听。
(5)之后当这个被关注的节点被移除了,客户端会收到相应的通知。这个时候客户端需要再次调用getChildren(“locknode”)方法来获取所有已经创建的子节点,确保自己确实是最小的节点了,然后进入步骤3。

watch机制

Zookeeper 并不是用来专门存储数据的,它的作用主要是用来维护和监控你存储的数据的状态变化。通过监控这些数据状态的变化,从而可以达到基于数据的集群管。

2、watcher实现
  可以直接写一个继承Watcher的类,然后实现process接口方法,或者直接在实现类中定义一个Watcher对象,实现process方法
1、第一种继承Watcher

public class DataMonitor implements Watcher{
     public void process(WatchedEvent event) {

     }
  }
}

2、第二种自定义对象

    Watcher wh = new Watcher() {
        public void process(org.apache.zookeeper.WatchedEvent event) {
            System.out.println(event.toString());
        }
    };

注册和触发
  我们已经知道了watcher简单实现框架,那问题来了,谁可以注册watcher,谁可以触发watcher?

注册watcher
可以注册watcher的
客户端命令有:ls,get, stat (这三个命令需要在path后面加上watch)
API方法只有getData、exists、getChildren

**触发watcher **
create、delete、setData。连接断开的情况下触发的watcher会丢失。
一个Watcher实例是一个回调函数,被回调一次后就被移除了。如果还需要关注数据的变化,需要再次注册watcher。
New ZooKeeper时注册的watcher叫default watcher,它不是一次性的,只对client的连接状态变化作出反应。

安装和服务启动方式

服务启动:
windos 直接启动 bin目录下的zkServer.cmd即可
centos bin目录下zkServer start 启动即可

登录zookeeper client


[root@NYSJHL99-54 zookeeper]# cd /usr/local/zookeeper/bin
[root@NYSJHL99-54 bin]# ./zkCli.sh -server 127.0.0.1:2181
[zk: 127.0.0.1:2181(CONNECTED) 0] ls /
[dubbo, search-card, redis, zookeeper]
 

客户端命令行工具的一些简单操作如下:

创建Znodes:

create  [-s] [-e] path data acl

用给定的路径创建一个znode,
-s:指定该节点是一个序列节点,创建同名的节点时,会给节点自动加上编号
-e:指定该节点是一个临时节点,默认是永久节点。临时节点会在客户端与服务器断开连接时,zk会将其创建的所有临时节点全部删除
acl:设置子节点访问权限,默认所有人都可以对该节点进行读写操作

  1. 显示根目录下、文件: ls / 使用 ls 命令来查看当前 ZooKeeper 中所包含的内容
  2. 显示根目录下、文件: ls2 / 查看当前节点数据并能看到更新次数等数据
  3. 创建文件,并设置初始内容: create /zk “test” 创建一个新的 znode节点“ zk ”以及与它关联的字符串
  4. 获取文件内容: get /zk 确认 znode 是否包含我们所创建的字符串
  5. 修改文件内容: set /zk “zkbak” 对 zk 所关联的字符串进行设置
  6. 删除文件: delete /zk 将刚才创建的 znode 删除
  7. 退出客户端: quit
  8. 帮助命令: help
# 3> 创建一个永久序列节点(节点会自动加上编号)
shell> create -s /node_04 data
Created /node_040000000012

# 4> 创建一个序列临时节点
shell> create -s -e /node_03 'i is a ephemeral sequence node'
Created /node_03


# 5> 创建一个带权限的节点,限制只能IP为192.168.1.101这台机器访问
## c:创建子节点权限  
## d:删除子节点权限
## r:读取子节点列表的权限
## w:写权限,即修改子节点数据权限
## a:管理子节点权限
shell> create /node_04 mydata ip:192.168.1.101:cdrwa 

原文:https://blog.csdn.net/xyang81/article/details/53053642
版权声明:本文为博主原创文章,转载请附上博文链接!

Watch(监视)
当指定的znode或znode的子数据更改时,监视器会显示通知。你只能在 get 命令中设置watch,这样当znode变动时,会给出watch的信息,

示例如下:

#客户端1
[zk: 127.0.0.1:2181(CONNECTED) 26] create  /bamboo 'bamboo'
Created /bamboo
[zk: 127.0.0.1:2181(CONNECTED) 27] create -e -s /bamboo/t_ '0'
Created /bamboo/t_0000000000

#客户端2
[zk: 127.0.0.1:2181(CONNECTED) 28] get /bamboo/t_0000000000  watch  1

#客户端1修改
[zk: 127.0.0.1:2181(CONNECTED) 30] set /bamboo/t_0000000000 '1'

##客户端1收到的监听信息
WATCHER::
cZxid = 0x171

WatchedEvent state:SyncConnected type:NodeDataChanged path:/bamboo/t_0000000000ctime = Sun Dec 30 15:24:07 CST 2018

mZxid = 0x173
mtime = Sun Dec 30 15:25:24 CST 2018
pZxid = 0x171
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x10030d3e0fb0002
dataLength = 1
numChildren = 0

客户端1创建了一个永久节点bamboo,然后创建临时有序节点t_,
客户端2通过get方式监听该节点,
客户端1当set修改该节点时,客户端2监听该节点的客户端会自动接收到通知消息。
由于客户端命令只提供了get,ls,stat。

服务端命令

查看连接到结点上所有的client信息,被选作leader还是follower

[root@rocket zookeeper-server1]# echo stat|nc 127.0.0.1 2181

测试是否启动了该Server,若回复imok表示已经启动

[root@rocket zookeeper-server1]# echo ruok|nc 127.0.0.1 2181

Imok

查看连接到服务器的所有客户端的会话信息

[root@rocket zookeeper-server1]# echo cons|nc 127.0.0.1 2181

 /127.0.0.1:52552[0](queued=0,recved=1,sent=0)

zookeeper分布式锁的创建

基于Zookeeper实现分布式锁
大致思想即为:
需要获得锁的客户端按照以下步骤来获取锁:

1.保证锁节点(lock root node)这个父根节点的存在,这个节点是每个要获取lock客户端共用的,这个节点是PERSISTENT的。
2.第一次需要创建本客户端要获取lock的节点,调用 create( ),并设置 节点为EPHEMERAL_SEQUENTIAL类型,表示该节点为临时的和顺序的。如果获取锁的节点挂掉,则该节点自动失效,可以让其他节点获取锁。
3.在父锁节点(lock root node)上调用 getChildren( ) ,不需要设置监视标志。 (为了避免“羊群效应”).
4.按照Fair竞争的原则,将步骤3中的子节点(要获取锁的节点)按照节点顺序的大小做排序,取出编号最小的一个节点做为lock的owner,判断自己的节点id
是否就为owner id,如果是则返回,lock成功。如果不是则调用 exists( )监听比自己小的前一位的id,关注它锁释放的操作(也就是exist watch)。
5.如果第4步监听exist的watch被触发,则继续按4中的原则判断自己是否能获取到lock。
6.释放锁:需要释放锁的客户端只需要删除在第2步中创建的节点即可。

注意事项:

一个节点的删除只会导致一个客户端被唤醒,因为每个节点只被一个客户端watch,这避免了“羊群效应”。
命令端样例参考上面的命令端样例watch监听。

Zookeeper能不能解决前面提到的问题。
1.锁无法释放?使用Zookeeper可以有效的解决锁无法释放的问题,因为在创建锁的时候,客户端会在ZK中创建一个临时节点,一旦客户端获取到锁之后突然挂掉(
Session连接断开),那么这个临时节点就会自动删除掉。其他客户端就可以再次获得锁。

2.非阻塞锁?使用Zookeeper可以实现阻塞的锁,客户端可以通过在ZK中创建顺序节点,并且在节点上绑定监听器,一旦节点有变化,Zookeeper会通知客户端,客户
端可以检查自己创建的节点是不是当前所有节点中序号最小的,如果是,那么自己就获取到锁,便可以执行业务逻辑了。

3.不可重入?使用Zookeeper也可以有效的解决不可重入的问题,客户端在创建节点的时候,把当前客户端的主机信息和线程信息直接写入到节点中,下次想要获取锁的
时候和当前最小的节点中的数据比对一下就可以了。如果和自己的信息一样,那么自己直接获取到锁,如果不一样就再创建一个临时的顺序节点,参与排队。

4.单点问题?使用Zookeeper可以有效的解决单点问题,ZK是集群部署的,只要集群中有半数以上的机器存活,就可以对外提供服务。

特点:
使用Zookeeper实现分布式锁的优点: 有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单。
使用Zookeeper实现分布式锁的缺点 : 性能上不如使用缓存实现分布式锁。 需要对ZK的原理有所了解。

**Curator框架的介绍 **
为了更好的实现java操作ZK服务器,后来出现了Curator框架,非常的强大,目前已经是Apache的顶级项目,里面提供了更多丰富的操作,例如session超时重连、主从选举、分布式计数器、分布式锁等等适用于各种复杂的ZK场景的API封装。(具体想要深入了解的可以去官网查看)

实现:

java 实现可以直接使用zookeeper第三方库Curator客户端,这个客户端中封装了一个可重入的锁服务。

public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
    try {
        return interProcessMutex.acquire(timeout, unit);
    } catch (Exception e) {
        e.printStackTrace();
    }
    return true;
}
public boolean unlock() {
    try {
        interProcessMutex.release();
    } catch (Throwable e) {
        log.error(e.getMessage(), e);
    } finally {
        executorService.schedule(new Cleaner(client, path), delayTimeForClean, TimeUnit.MILLISECONDS);
    }
    return true;
}

Curator提供的InterProcessMutex是分布式锁的实现。acquire方法用户获取锁,release方法用于释放锁。

使用ZK实现的分布式锁好像完全符合了本文开头我们对一个分布式锁的所有期望。但是,其实并不是,Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务

备注:
Zookeeper实现的分布式锁其实存在一个缺点,那就是性能上可能并没有缓存服务那么高。因为每次在创建锁和释放锁的过程中,都要动态创建、销毁瞬时节点来实现锁功能。ZK中创建和删除节点只能通过Leader服务器来执行,然后将数据同不到所有的Follower机器上。
其实,使用Zookeeper也有可能带来并发问题,只是并不常见而已。考虑这样的情况,由于网络抖动,客户端可ZK集群的session连接断了,那么zk以为客户端挂了,就会删除临时节点,这时候其他客户端就可以获取到分布式锁了。就可能产生并发问题。这个问题不常见是因为zk有重试机制,一旦zk集群检测不到客户端的心跳,就会重试,Curator客户端支持多种重试策略。多次重试之后还不行的话才会删除临时节点。(所以,选择一个合适的重试策略也比较重要,要在锁的粒度和并发之间找一个平衡。)

如果zk客户端断开,但是业务逻辑仍然正常,那么就需要抛出异常让该业务终端

总结:
使用Zookeeper实现分布式锁的优点
有效的解决单点问题,不可重入问题,非阻塞问题以及锁无法释放的问题。实现起来较为简单(要懂得其实现的原理,其实比redis要繁琐)。
使用Zookeeper实现分布式锁的缺点
性能上不如使用缓存实现分布式锁。 需要对ZK的原理有所了解。

猜你喜欢

转载自blog.csdn.net/zjcjava/article/details/85390942