ZooKeeper服务-操作(API、集合更新、观察者、ACL)

操作

create:创建一个znode(必须要有父节点)
delete:删除一个znode(该znode不能有任何子节点)
exists:测试一个znode是否存在并且查询它的元数据
getACL,setACL:获取/设置一个znode的ACL
getChildren:获取一个znode的子节点列表
getData,setData:获取/设置一个znode所保存的数据
sync:将客户端的znode视图与ZooKeeper同步

ZooKeeper中的更新操作是有条件的。在使用delete或setData操作时必须提供被更新znode的版本号(可以通过exists操作获得)。如果版本号不匹配,则更新操作会失败。更新操作是非阻塞操作,因此一个更新失败的客户端(由于其他进程同时在更新同一个znode)可以决定是否重试,或执行其他操作,并不会因此而阻塞其他进程的执行。
虽然ZooKeeper可以被看作是一个文件系统,但出于简单性的需要,有一些文件系统的基本操作被它摒弃了。由于ZooKeeper中的文件较小并且总是被整体读写,因此没有必要提供打开、关闭或查找操作。
Sync操作与POSIX文件系统中的fsync()操作是不同的。如前所述,ZooKeeper中的写操作具有原子性,一个成功的写操作会保证将数据写到ZooKeeper服务器的持久存储介质中。然而,ZooKeeper允许客户端读到的数据滞后于ZooKeeper服务的最新状态,因此客户端可以使用sync操作来获取数据的最新状态。

1. 集合更新(Multiupdate)

ZooKeeper中有一个被称为multi的操作,用于将多个基本操作集合成一个操作单元,并确保这些基本操作同时被成功执行,或者同时失败,不会发生其中部分基本操作被成功执行而其他基本操作失败的情况。
集合更新可以被用于在ZooKeeper中构建需要保持全局一致性的数据结构,例如构建一个无向图。在ZooKeeper中用一个znode来表示无向图中的一个顶点,为了在两个顶点之间添加或删除一条边,我们需要同时更新两个顶点所分别对应的两个znode,因为每个znode中都有指向对方的引用。如果我们只用ZooKeeper的基本操作来实现边的更新,可能会让其他客户端发现无向图处于不一致的状态,即一个顶点具有指向另一个顶点的引用而对方却没有对应的应用。将针对两个znode的更新操作集合到一个multi操作中可以保证这组更新操作的原子性,也就保证了一对顶点之间不会出现不完整的连接。

2. 关于API

对于ZooKeeper客户端来说,主要有两种语言绑定(binding)可以使用:Java和C;当然也可以使用Perl、Python和REST的contrib绑定。对于每一种绑定语言来说,在执行操作时都可以选择同步执行或异步执行。看exists的签名
public Stat exists(final String path, Watcher watcher) throws KeeperException, InterruptedException
它返回一个封装有znode元数据的Stat对象(如果znode不存在,则返回null)
异步执行的签名如下

1
public void exists(final String path, Watcher watcher, StatCallback cb, Object ctx)

因为所有操作的结果都是通过回调来传送的,因此在Java API中异步方法的放回类型都是void。调用者传递一个回调的实现,当ZooKeeper相应时,该回调方法被调用。这种情况下,回调采用StatCallback接口,它有以下方法:

1
2
3
4
5
6
7
8
9
10
11
12
/**
* 回调
* @param rc 返回代码,对应于KeeperException的代码。每个非零代码都代表一个异常
* @param path   对应于客户端传递给exists方法的参数,用于识别这个回调所相应的请求。当path参数不能提供足够的信息时,客户端可以通过ctx参数来区分不同请求。如果path参数提供了足够的信息,ctx可以设为null
* @param ctx    对应于客户端传递给exists方法的参数,用于识别这个回调所相应的请求。可以是任意对象
* @param stat   这种情况下,stat参数是null
*/
public void processResult(int rc, String path, Object ctx, Stat stat) {
System.out.println("rc:" + rc);
System.out.println("path:" + path);
System.out.println("ctx:" + ctx);
}

异步API允许以流水线方式处理请求,这在某些情况下可以提供更好的吞吐量。

3. 观察触发器

在exists、getChildren和getData这些读操作上可以设置观察,这些观察可以被写操作create、delete和setData触发。ACL相关的操作不参与触发任何观察。当一个观察被触发时会产生一个观察事件,这个观察和触发它的操作共同决定着观察事件的类型。
当做观察的znode被创建、删除或其数据被更新时,设置在exists操作上的观察将被触发。
当所观察的znode被删除或其数据被更新时,设置在getData操作上的观察将被触发。创建znode不会触发getData操作上的观察,因为getData操作成功执行的前提是znode必须已经存在。
当所观察的znode的一个子节点被创建或删除时,或所观察的znode自己被删除时,设置在getChildren操作上的观察将会被触发。可以通过观察时间的类型来判断被删除的是znode还是其子节点:NodeDelete类型代表znode被删除;NodeChildrenChanged类型代表一个子节点被删除。

一个观察事件中包含涉及该事件的znode的路径,因此对于NodeCreated和NodeDeleted事件来说,可通过路径来判断哪一个节点被创建或删除。为了能够在NodeChildrenChanged事件发生之后判断是哪些子节点被修改,需要重新调用getChildren来获取新的子节点列表。与之类似,为了能够在NodeDataChanged事件之后获取新的数据,需要调用getData。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package com.zhen.zookeeper.existsAndWatcher;
 
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
 
import org.apache.zookeeper.AsyncCallback.StatCallback;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Ids;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
 
/**
 * @author FengZhen
 * @date 2018年10月13日
 * exists与观察者
 *  state=-112 会话超时状态
    state= -113 认证失败状态
    state=  1 连接建立中
    state= 2 (暂时不清楚如何理解这个状态,ZOO_ASSOCIATING_STATE)
    state=3 连接已建立状态
    state= 999 无连接状态
     
     
    type=1 创建节点事件
    type=2 删除节点事件
    type=3 更改节点事件
    type=4 子节点列表变化事件
    type= -1 会话session事件
    type=-2 监控被移除事件
 
 */
public class ExistsAndWatcher implements Watcher{
 
    private static final int SESSION_TIMEOUT = 5000;
     
    private ZooKeeper zk;
    private CountDownLatch connectedSignal = new CountDownLatch(1);
     
    public void connect(String hosts) throws IOException, InterruptedException {
        /**
         * hosts:ZooKeeper服务的主机地址(可指定端口,默认是2181)
         * SESSION_TIMEOUT:以毫秒为单位的会话超时参数(此处为5秒)
         * this:Watcher对象的实例。Watcher对象接收来自于ZooKeeper的回调,以获得各种事件的通知。
         */
        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
        connectedSignal.await();
    }
     
    /**
     * 当客户端已经与ZK建立连接后,Watcher的process方法会被调用
     * 参数是一个用于表示该连接的事件。
     */
    public void process(WatchedEvent event) {
        int type = event.getType().getIntValue();
        System.out.println("watchedEven--" + event.getState().getIntValue() + " : " + type);
        //连接事件
        if (event.getState() == KeeperState.SyncConnected) {
            /**
             * 通过调用CountDownLatch的countDown方法来递减它的计数器。
             * 锁存器(latch)被创建时带有一个值为1的计数器,用于表示在它释放所有等待线程之前需要发生的事件数。
             * 在调用一次countDown方法之后,计数器的值变为0,则await方法返回。
             */
            connectedSignal.countDown();
        }
        //如果为创建或者删除znode的话,需要再添加一个观察者,观察后续操作
        if (type == 1 || type == 2) {
            existsAndWatcher("/zoo");
        }
    }
 
    public void create(String groupName) throws KeeperException, InterruptedException {
        String path = "/" + groupName;
        /**
         * 用ZK的create方法创建一个新的ZK的znode
         * path:路径(用字符串表示)
         * null:znode的内容(字节数组,此处为空值)
         * Ids.OPEN_ACL_UNSAFE:访问控制列表(简称ACL,此处为完全开放的ACL,允许任何客户端对znode进行读写)
         * CreateMode.PERSISTENT:znode类型
         * znode类型可以分为两种:1.短暂的(ephemeral)   2.持久的(persistent)
         * 创建znode的客户端断开连接时,无论客户端是明确断开还是因为任何原因而终止,短暂znode都会被ZK服务删除。持久znode不会被删除。
         * create方法的返回值是ZK所创建的节点路径
         */
        String createdPath = zk.create(path, null, Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
        System.out.println("Created " + createdPath);
    }
     
    public void close() throws InterruptedException {
        zk.close();
    }
     
    public boolean exists(String path) throws KeeperException, InterruptedException {
        Stat exists = zk.exists(path, true);
        return null != exists;
    }
     
    public void existsAndWatcher(String path) {
        zk.exists(path, this, new StatCallback() {
            /**
             * 回调
             * @param rc    返回代码,对应于KeeperException的代码。每个非零代码都代表一个异常
             * @param path  对应于客户端传递给exists方法的参数,用于识别这个回调所相应的请求。当path参数不能提供足够的信息时,客户端可以通过ctx参数来区分不同请求。如果path参数提供了足够的信息,ctx可以设为null
             * @param ctx   对应于客户端传递给exists方法的参数,用于识别这个回调所相应的请求。可以是任意对象
             * @param stat  这种情况下,stat参数是null
             */
            public void processResult(int rc, String path, Object ctx, Stat stat) {
                System.out.println("rc:" + rc);
                System.out.println("path:" + path);
                System.out.println("ctx:" + ctx);
            }
        }, "标记回调所相应的请求");
    }
     
    public void delete(String groupName) {
        String path = "/" + groupName;
        try {
            List<String> children = zk.getChildren(path, false);
            for (String child : children) {
                zk.delete(path + "/" + child, -1);
            }
            /**
             * delete方法有两个参数
             * path:节点路径
             * -1:版本号
             * 如果所提供的版本号与znode的版本号一致,ZK会删除这个znode。
             * 这是一种乐观的加锁机制,使客户端能够检测出对znode的修改冲突。
             * 通过将版本号设置为-1,可以绕过这个版本检测机制,不管znode的版本号是什么而直接将其删除。
             * ZK不支持递归删除,因此在删除父节点之前必须先删除子节点
             */
            zk.delete(path, -1);
        } catch (KeeperException e) {
            System.out.printf("Group %s does not exist\n", groupName);
            System.exit(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
     
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException {
        String hosts = "localhost:2181";
        String groupName = "zoo";
        ExistsAndWatcher existsAndWatcher = new ExistsAndWatcher();
        existsAndWatcher.connect(hosts);
        //同步
        boolean exists = existsAndWatcher.exists("/zoo");
        System.out.println("exists zoo:" + exists);
        //异步
        existsAndWatcher.existsAndWatcher("/zoo");
        existsAndWatcher.create(groupName);
        existsAndWatcher.delete(groupName);
     
        existsAndWatcher.close();
    }
     
}

  

4. ACL列表

每个znode被创建时都会带有一个ACL列表,用于决定谁可以对它执行何种操作。
ACL依赖于ZooKeeper的客户端身份验证机制。ZooKeeper提供了以下几种身份验证方式
 Digest:通过用户名和密码来识别客户端
 Sasl:通过Kerberos来识别客户端
 Ip:通过客户端的IP地址来识别客户端

在建立一个ZooKeeper会话之后,客户端可以对自己进行身份验证。虽然znode的ACL列表会要求所有的客户端是经过验证的,但ZooKeeper的身份验证过程却是可选的,客户端必须自己进行身份验证来支持对znode的访问。
使用digest方式进行身份验证的例子
zk.addAuthInfo("digest", "fz:secret".getBytes());
每个ACL都是身份验证方式、符合该方式的一个身份和一组权限的组合。例如,如果打算给IP地址为10.0.0.1的客户端对某个znode的读权限,可以使用IP验证方式、10.0.0.1和READ权限在该znode上设置一个ACL。

测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package com.zhen.zookeeper.ACL;
 
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
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.ZooKeeper;
import org.apache.zookeeper.data.ACL;
import org.apache.zookeeper.data.Id;
import org.apache.zookeeper.server.auth.DigestAuthenticationProvider;
import org.apache.zookeeper.Watcher.Event.KeeperState;
import org.apache.zookeeper.ZooDefs.Perms;
 
 
/**
 * @author FengZhen
 * @date 2018年11月25日
 * ACL
 */
public class ACLTest implements Watcher{
 
private static final int SESSION_TIMEOUT = 5000;
     
    private ZooKeeper zk;
    private CountDownLatch connectedSignal = new CountDownLatch(1);
     
    public void connect(String hosts) throws IOException, InterruptedException {
        /**
         * hosts:ZooKeeper服务的主机地址(可指定端口,默认是2181)
         * SESSION_TIMEOUT:以毫秒为单位的会话超时参数(此处为5秒)
         * this:Watcher对象的实例。Watcher对象接收来自于ZooKeeper的回调,以获得各种事件的通知。
         */
        zk = new ZooKeeper(hosts, SESSION_TIMEOUT, this);
        //根据IP
        zk.addAuthInfo("ip", "192.168.1.103".getBytes());
        //根据用户密码
        zk.addAuthInfo("digest", "fz:123456".getBytes());
        connectedSignal.await();
    }
     
    /**
     * 当客户端已经与ZK建立连接后,Watcher的process方法会被调用
     * 参数是一个用于表示该连接的事件。
     */
    public void process(WatchedEvent event) {
        //连接事件
        if (event.getState() == KeeperState.SyncConnected) {
            /**
             * 通过调用CountDownLatch的countDown方法来递减它的计数器。
             * 锁存器(latch)被创建时带有一个值为1的计数器,用于表示在它释放所有等待线程之前需要发生的事件数。
             * 在调用一次countDown方法之后,计数器的值变为0,则await方法返回。
             */
            connectedSignal.countDown();
        }
    }
 
    public void createACLIP(String groupName) throws KeeperException, InterruptedException {
        String path = "/" + groupName;
        /**
         * 用ZK的create方法创建一个新的ZK的znode
         * path:路径(用字符串表示)
         * null:znode的内容(字节数组,此处为空值)
         * Ids.OPEN_ACL_UNSAFE:访问控制列表(简称ACL,此处为完全开放的ACL,允许任何客户端对znode进行读写)
         * CreateMode.PERSISTENT:znode类型
         * znode类型可以分为两种:1.短暂的(ephemeral)   2.持久的(persistent)
         * 创建znode的客户端断开连接时,无论客户端是明确断开还是因为任何原因而终止,短暂znode都会被ZK服务删除。持久znode不会被删除。
         * create方法的返回值是ZK所创建的节点路径
         */
        //添加权限,设置IP
        ACL aclIP = new ACL(Perms.ALL, new Id("ip""192.168.1.103"));
        System.out.println(aclIP);
        List<ACL> acls = new ArrayList<ACL>();
        acls.add(aclIP);
        String createdPath = zk.create(path, null, acls, CreateMode.PERSISTENT);
        System.out.println("Created " + createdPath);
    }
     
    public void createACLDigest(String groupName) throws KeeperException, InterruptedException, NoSuchAlgorithmException {
        String path = "/" + groupName;
        /**
         * 用ZK的create方法创建一个新的ZK的znode
         * path:路径(用字符串表示)
         * null:znode的内容(字节数组,此处为空值)
         * Ids.OPEN_ACL_UNSAFE:访问控制列表(简称ACL,此处为完全开放的ACL,允许任何客户端对znode进行读写)
         * CreateMode.PERSISTENT:znode类型
         * znode类型可以分为两种:1.短暂的(ephemeral)   2.持久的(persistent)
         * 创建znode的客户端断开连接时,无论客户端是明确断开还是因为任何原因而终止,短暂znode都会被ZK服务删除。持久znode不会被删除。
         * create方法的返回值是ZK所创建的节点路径
         */
        //添加权限,设置IP
        ACL aclIP = new ACL(Perms.ALL, new Id("digest", DigestAuthenticationProvider.generateDigest("fz:123456")));
        System.out.println(aclIP);
        List<ACL> acls = new ArrayList<ACL>();
        acls.add(aclIP);
        String createdPath = zk.create(path, null, acls, CreateMode.PERSISTENT);
        System.out.println("Created " + createdPath);
    }
     
    public void close() throws InterruptedException {
        zk.close();
    }
     
    public void writeZnodeACLDigest(String groupName) throws KeeperException, InterruptedException {
        String path = "/" + groupName;
        zk.setData(path, "test_digest_data".getBytes(), 0);
    }
     
    public void writeZnodeACLIP(String groupName) throws KeeperException, InterruptedException {
        String path = "/" + groupName;
        zk.setData(path, "test_ip_data".getBytes(), 0);
    }
     
    public void readZnode(String groupName) throws KeeperException, InterruptedException {
        String path = "/" + groupName;
        String data = zk.getData(path, false, null).toString();
        System.out.println("data = " + data);
    }
     
    public static void main(String[] args) throws IOException, InterruptedException, KeeperException, NoSuchAlgorithmException {
        String hosts = "192.168.1.103:2181";
        String groupNameIP = "znode_acl_test_ip";
        String groupNameDigest = "znode_acl_test_digest";
        ACLTest aclTest = new ACLTest();
        aclTest.connect(hosts);
        //digest
//      aclTest.createACLDigest(groupNameDigest);
//      aclTest.writeZnodeACLDigest(groupNameDigest);
        aclTest.readZnode(groupNameDigest);
         
        //IP
//      aclTest.createACLIP(groupNameIP);
//      aclTest.writeZnodeACLIP(groupNameIP);
//      aclTest.readZnode(groupNameIP);
         
        aclTest.close();
         
        //创建完带有ACL的znode之后,查看ACL
        //[zk: localhost:2181(CONNECTED) 7] getAcl /znode_acl_test_ip
        //'ip,'192.168.1.103
        //: cdrwa
         
        //不设置IP直接读取该znode内容,报错如下
        //Exception in thread "main" org.apache.zookeeper.KeeperException$NoAuthException: KeeperErrorCode = NoAuth for /znode_acl_test_ip
         
    }
     
     
}

  

 

posted on   嘣嘣嚓  阅读(606)  评论(0编辑  收藏  举报

编辑推荐:
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
阅读排行:
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 记一次.NET内存居高不下排查解决与启示

导航

< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5
点击右上角即可分享
微信分享提示