一、关于zookeeper
Zookeeper 作为一个分布式的服务框架,主要用来解决分布式集群中应用系统的一致性问题,它能提供基于类似于文件系统的目录节点树方式的数据存储, Zookeeper 作用主要是用来维护和监控存储的数据的状态变化,通过监控这些数据状态的变化,从而达到基于数据的集群管理。
简单的说,zookeeper=文件系统+通知机制。
1. ZNode节点
ZNode被分为持久(persistent)节点,顺序(sequential)节点和临时(ephemeral)节点。
- 持久节点:即使在创建该特定znode的客户端断开连接后,持久节点仍然存在。默认情况下,除非另有说明,否则所有znode都是持久的。
- 顺序节点:顺序节点可以是持久的或临时的。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的序列号附加到原始名称来设置znode的路径。例如,如果将具有路径 /test 的znode创建为顺序节点,则ZooKeeper会将路径更改为 /test0000000001 ,并将下一个序列号设置为0000000002。如果两个顺序节点是同时创建的,那么ZooKeeper不会对每个znode使用相同的数字。顺序节点在锁定和同步中起重要作用。
- 临时节点:客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集合断开连接时,临时节点会自动删除。因此,只有临时节点不允许有子节点。如果临时节点被删除,则下一个合适的节点将填充其位置。临时节点在leader选举中起着重要作用。
2. Session回话
会话对于ZooKeeper的操作非常重要。会话中的请求按FIFO顺序执行。一旦客户端连接到服务器,将建立会话并向客户端分配会话ID 。
客户端以特定的时间间隔发送心跳以保持会话有效。如果ZooKeeper集合在超过服务器开启时指定的期间(会话超时)都没有从客户端接收到心跳,则它会判定客户端死机。
会话超时通常以毫秒为单位。当会话由于任何原因结束时,在该会话期间创建的临时节点也会被删除。
3.Watcher监视
监视是一种简单的机制,使客户端收到关于ZooKeeper集合中的更改的通知。客户端可以在读取特定znode时设置Watches。Watches会向注册的客户端发送任何znode(客户端注册表)更改的通知。
Znode更改是与znode相关的数据的修改或znode的子项中的更改。只触发一次watches。如果客户端想要再次通知,则必须通过另一个读取操作来完成。当连接会话过期时,客户端将与服务器断开连接,相关的watches也将被删除。
二、Zookeeper客户端命令
执行bin/zkCli.sh连接zookeeper服务器后,我们随便敲一个aa然后回车,可以看到支持如下命令:
stat path [watch] --- 查看节点状态 set path data [version] --- 设置节点数据 ls path [watch] --- 列出子节点 delquota [-n|-b] path --- 删除节点配额 ls2 path [watch] --- 列出子节点,并显示当前节点信息 setAcl path acl --- 设置节点权限 setquota -n|-b val path --- 设置节点配额 history --- 列出历史命令 redo cmdno --- 执行历史命令 printwatches on|off delete path [version] --- 删除节点,如果待删除节点包含子节点则删除失败 sync path listquota path --- 列出节点配额 get path [watch] --- 获取节点数据 create [-s] [-e] path data acl --- 创建子节点 addauth scheme auth --- 增加zookeeper用户认证 quit --- 退出zkCli getAcl path --- 查询节点权限 close --- 关闭当前连接 connect host:port --- 连接zkServer
1. ls
此命令用于列出和显示znode的子项。ls命令要求路径以/开头,并支持tab键自动补全。例如:
- 执行:ls / 输出[dubbo, task, zookeeper, test, disconf]: 说明根节点下包含5个子节点。
- 执行:ls /test 输出[test1-2, test1-1]:说明/test节点下面有2个节点。
- 执行:ls /test/test1-2 输出[]:说明/test/test1-2节点下面没有子节点。
2. ls2
此命令也用于列出和显示znode的子项,与ls命令不同的是,该命令同时列出该节点的time、version等信息。例如:
执行 ls /test , 输出
[test1-2, test1-1] cZxid = 0x69043 ctime = Mon Sep 03 06:37:21 CST 2018 mZxid = 0x69043 mtime = Mon Sep 03 06:37:21 CST 2018 pZxid = 0x69045 cversion = 2 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 3 numChildren = 2
3. create
命令格式: create [-s] [-e] path data acl
- -s:创建顺序节点。当一个新的znode被创建为一个顺序节点时,ZooKeeper通过将10位的序列号附加到原始名称来设置znode的路径。
- -e:创建临时节点。客户端活跃时,临时节点就是有效的。当客户端与ZooKeeper集合断开连接时,临时节点会自动删除。
- path:待创建的节点路径。以/开头。
- data:带创建的节点存储的数据内容。
- acl:权限控制。后面再说。
例如:
- 创建永久节点:create /test/test1-3 "nodeData"
- 创建临时节点:create -e /test/test1-4 "nodeData"
- 创建循序节点:create -s /test/test1-5 "nodeData"
执行完以上命令以后,再执行 ls /test,可以看到输出结果:[test1-4, test1-2, test1-3, test1-1, test1-50000000004],可以看到创建的顺序节点test1-5被增加了序列号。我们执行quit退出zkCli,再重新执行bin/zkCli.sh重新连接,执行 ls /test,输出结果:[test1-2, test1-3, test1-1, test1-50000000004],可以看到创建的临时节点test1-4已经没有了。
4. get
命令格式: get path [watch]。它返回znode的关联数据和指定znode的元数据。你将获得信息,例如上次修改数据的时间,修改的位置以及数据的相关信息。
- path:节点路径。以/开头。
- watch:监控。后面再说。
例如:执行 get /test/test1-3 输出
"nodeData" cZxid = 0x69046 ctime = Mon Sep 03 07:00:25 CST 2018 mZxid = 0x69046 mtime = Mon Sep 03 07:00:25 CST 2018 pZxid = 0x69046 cversion = 0 dataVersion = 0 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 10 numChildren = 0
5. set
命令格式 set path data [version]。设置指定znode的数据。
- path:节点路径。以/开头。
- data:节点数据内容。
- version: 当前数据版本号。如果该参数和当前数据版本号不一致则会抛异常。
例如:执行 set /test/test1-3 "dataNode-New",然后我们再执行 get /test/test1-3 查看数据,输出:
"dataNode-New" cZxid = 0x69046 ctime = Mon Sep 03 07:00:25 CST 2018 mZxid = 0x6904b mtime = Mon Sep 03 07:42:46 CST 2018 pZxid = 0x69046 cversion = 0 dataVersion = 1 aclVersion = 0 ephemeralOwner = 0x0 dataLength = 14 numChildren = 0
可以看到dataVersion已经发生了改变,当前 dataVersion = 1。
6. delete
命令格式:delete path [version]。删除指定节点,如果要删除的节点包含子节点则会删除失败。
- path:节点路径。以/开头。
- version: 当前数据版本号。如果该参数和当前数据版本号不一致则会抛异常。
例如:执行 delete /test/test1-3,再执行 ls /test,输出:[test1-2, test1-1, test1-50000000004],可以看到test1-3这个节点已经被删除掉了。
7. addauth
命令格式:addauth scheme auth。该命令为zookeeper增加一个用户认证,例如:addauth digest zookeeper:zookeeper-pass。其中zookeeper为用户名,zookeeper-pass为密码。
8. history
命令格式:history。该命令用来查看历史命令,查询到的历史命令前会有编号,提供redo操作。
9. redo
命令格式:redo cmdno。再次执行历史命令,配合history命令使用。参数cmdno即为history命令执行结果中的编号
三、Zookeeper权限控制
zookeeper支持的命令中,有如下两条命令是和权限相关的:
- setAcl path acl
- getAcl path
- create [-s] [-e] path data acl
zookeeper共支持5中权限,分别是
- create:创建节点
- delete:删除节点
- read:读取节点数据以及子节点
- write:修改节点数据
- admin:设置节点权限
zookeeper的ACL,可以从三个维度来理解:一是scheme;二是user;三是permission。通常表示为scheme:id:permissions。
授权模式
- world: 表示所有,创建节点时默认此权限范围。有唯一的id是anyone。例如:setAcl /test/test1-1 world:anyone:cdrwa。表示/test/test1-1节点所有人都可以执行cdrwa操作。
- auth:它不需要id,只要是通过认证的user都有权限。例如:setAcl /test/test1-2 auth:zookeeper:zookeeper-pass:cdrwa。表示通过密码验证的用户名为zookeeper的用户可执行cdrwa操作。当然,执行此命令的前提是已经通过addauth命令添加了该用户名密码的用户。执行完setAcl命令后,再执行getAcl /test/test1-2,返回输出结果:
'digest,'zookeeper:hdtnoeyerDQpPGLJ41lW1u1lCSA= : cdrwa
- digest:类似于auth授权。与auth授权的区别在于,输入命令的时候,用户密码需要先进行SHA-1加密再进行Base64编码。首先,先生成加密后的密码,执行:echo -n zookeeper:zookeeper-pass | openssl dgst -binary -sha1 | openssl base64,输出:hdtnoeyerDQpPGLJ41lW1u1lCSA=。可以看到该输出值即为auth授权后重新getAcl得到的值。然后,执行digest授权:setAcl /test/test1-3 digest:zookeeper:hdtnoeyerDQpPGLJ41lW1u1lCSA=:cdrwa。通过 getAcl /test/test1-3 可以看到授权结果和 getAcl /test/test1-2完全一致
- ip:它对应的id为客户机的IP地址,设置的时候可以设置一个ip段,比如执行:setAcl /test/test1-4 ip:192.168.1.0/16:cdrwa, 表示匹配前16个bit的IP段。
四、quota配额
zookeeper可以在znode上设置配额限制。如果超出了配置限制,zookeeper将会在log日志中打印WARN日志。如果超出配额限制,并不会停止行为操作。
1. setquota
命令格式:setquota -n|-b val path。为节点设定配额。例如命令:setquota -n 10 /test/test1-1
- -n :限制子节点个数
- -b :限制节点数据长度
- val : 限制的具体的值
- path : 要做限制的节点路径
2. delquota
命令格式:delquota [-n|-b] path。删除节点配额。例如命令:delquota -n /test/test1-1
- -n|-b : 要删除的配额类型
- path : 要做限制的节点路径
3. listquota
命令格式:listquota path。列出节点设置的配额。例如命令:listquota /test/test1-1
五、zookeeper in java
@Test public void testClient() throws IOException, InterruptedException, KeeperException { final String zookeeperHost = "10.5.31.155"; final String zookeeperPort = "2181"; // 链接zk服务器 final CountDownLatch countDownLatchConnect = new CountDownLatch(1); final ZooKeeper zooKeeper = new ZooKeeper(zookeeperHost + ":" + zookeeperPort, 60000, event -> { if (event.getState().equals(Watcher.Event.KeeperState.SyncConnected)) { countDownLatchConnect.countDown(); } }); countDownLatchConnect.await(); // watch节点 并执行创建 zooKeeper.exists("/clientTestNode", System.out::println); zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); // watch节点 并执行删除 zooKeeper.exists("/clientTestNode", System.out::println); zooKeeper.delete("/clientTestNode", -1); // 1. 创建节点 2. watch节点数据 3. 修改节点数据 4. 删除节点 zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); byte[] dataOld = zooKeeper.getData("/clientTestNode", System.out::println, null); System.out.println(new String(dataOld)); zooKeeper.setData("/clientTestNode", "clientTestNodeDataNew".getBytes(), -1); byte[] dataNew = zooKeeper.getData("/clientTestNode", System.out::println, null); System.out.println(new String(dataNew)); zooKeeper.delete("/clientTestNode", -1); // 1. 创建节点 2. 监控节点的子节点 3. 创建子节点 4. 监控节点子节点 5. 删除子节点 zooKeeper.create("/clientTestNode", "clientTestNodeData".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.create("/clientTestNode/child1", "child1Data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.create("/clientTestNode/child2", "child2Data".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.delete("/clientTestNode/child1", -1); zooKeeper.getChildren("/clientTestNode", System.out::println, null); zooKeeper.delete("/clientTestNode/child2", -1); zooKeeper.delete("/clientTestNode", -1); }
需要注意的点:
- 由于zookeeper的连接是异步过程,所以执行new Zookeeper之后不要紧接着执行zookeeper命令。否则可能会出现丢失连接异常。
- 由于zookeeper的watcher是一次性有效(即zookeeper回调watcher后该watcher即被删除),所以会出现重复注册watcher的现象