说了那么久的理论,但是其实对于大多数人来说,会用才是王道,在会用的基础上再去深入探讨原理可能是最佳的实践方式,但是对于笔记来说,先原理再实践可能更为稳妥,本文介绍zk最基本的一些操作,比如查看节点、增加一个节点、Watch机制以及ACL权限控制等基本命令,最后还介绍了一下为了方便而产生的四字命令。

基本数据模型

  • 树形结构,每个节点成为znode,它可以有子节点,也可以有数据
  • 临时节点和永久节点,临时节点在客户端断开后消失
  • 每个zk节点都有各自的版本号,可以通过命令行来显示节点信息
  • 每当节点数据发生变化,那么该节点的版本号会累加(乐观锁
  • 删除/修改过时节点,版本号不匹配则会报错
  • 每个zk节点存储的数据不宜过大,几k即可
  • 节点可以设置acl,可以通过权限来限制用户的访问

zk的作用

  • master选举,保证集群是高可用的
  • 统一配置文件管理,即只需要部署一台服务器,则可以把相同的配置文件同步更新到其他所有服务器
  • 发布与订阅,dubbo发布者把数据存在znode上,订阅者可以读取这个数据
  • 分布式锁
  • 集群管理,集群中保证数据的强一致性

zk的基本操作

  • ls / 显示根节点名称
  • ls2 / 显示了根节点的状态信息(stat也可以看状态)
  • get / 拿出节点的数据和信息
  • create [-s] [-e] path data acl 创建节点,如果是默认创建,则是非顺序的、 持久的节点。加上-e则是临时节点;加上-s表示顺序节点
  • 【注1】:如果是持久节点,状态信息中的ephemeralOwner=0x0;临时节点的这个属性,是后面一串比较长的字符
  • 【注2】:客户端断开连接了,一段时间之后,那么临时节点就会消失(主要是有个时效,超出这个时间还不收到来自客户端的心跳包则才认定客户端挂了)
  • 【注3】:在加上-s后,创建的节点会重命名为一个累加的名称
  • set path newData 每次修改值后dataVersion数据版本号会增1
  • 【注4】:如何实现乐观锁?set path data version,就是说带上版本号,如果这个版本不对应,那么就修改失败
  • delete path version 删除节点

watcher机制

  • 针对每个节点的操作,都会有一个监督者watcher
  • 当监控的某个对象(znode)发生了变化,则触发watcher事件
  • zk中watcher是一次性的,触发后立即销毁(用其他的开源客户端开源让其不会销毁,重复触发)
  • 父节点以及他的子孙们的 增 删 改 都能够触发其watcher
  • 针对不同类型的事件,触发的watcher事件也不同:
    • (子)节点创建事件
    • (子)节点删除事件
    • (子)节点数据变化事件
  • 通过get path [watch]或者stat path [watch]或者ls path [watch]都可以设置watcher
  • 父节点 增 删 改 操作触发watcher
  • 子节点 增 删 改 操作触发watcher

【创建父节点触发】:NodeCreated

1
2
3
4
5
6
7
8
[zk: localhost:2181(CONNECTED) 24] stat /hello watch  
Node does not exist: /hello
[zk: localhost:2181(CONNECTED) 25] create /hello world

WATCHER::
Created /hello

WatchedEvent state:SyncConnected type:NodeCreated path:/hello

【修改父节点数据触发】:NodeDataChanged

1
2
3
4
5
6
7
8
9
10
[zk: localhost:2181(CONNECTED) 26] stat /hello watch

[zk: localhost:2181(CONNECTED) 27] get /hello
world

[zk: localhost:2181(CONNECTED) 28] set /hello lalala

WATCHER::cZxid = 0x300000011

WatchedEvent state:SyncConnected type:NodeDataChanged path:/helloctime = Sat Dec 08 20:00:53 CST 2018

【删除父节点触发】:NodeDeleted

1
2
3
4
5
6
7
[zk: localhost:2181(CONNECTED) 32] get /hello watch

[zk: localhost:2181(CONNECTED) 33] delete /hello

WATCHER::
[zk: localhost:2181(CONNECTED) 34]
WatchedEvent state:SyncConnected type:NodeDeleted path:/hello

【创建子节点触发】:ls为父节点设置watcher,创建子节点触发NodeChildrenChanged

1
2
3
4
5
6
7
8
9
10
11
12
[zk: localhost:2181(CONNECTED) 52] ls /
[zookeeper]
[zk: localhost:2181(CONNECTED) 53] create /hello world
Created /hello
[zk: localhost:2181(CONNECTED) 54] ls /hello watch
[]
[zk: localhost:2181(CONNECTED) 55] create /hello/helloson worldson

WATCHER::Created /hello/helloson


WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hello

【删除子节点触发】:ls为父节点设置watcher,删除子节点触发NodeChildrenChanged

1
2
3
4
5
6
7
8
9
[zk: localhost:2181(CONNECTED) 56] ls /hello
[helloson]
[zk: localhost:2181(CONNECTED) 57] ls /hello watch
[helloson]
[zk: localhost:2181(CONNECTED) 58] delete /hello/helloson

WATCHER::
[zk: localhost:2181(CONNECTED) 59]
WatchedEvent state:SyncConnected type:NodeChildrenChanged path:/hello

【更新子节点触发】:ls为父节点设置watcher,更新子节点不触发事件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
[zk: localhost:2181(CONNECTED) 59] ls /hello
[]
[zk: localhost:2181(CONNECTED) 60] create /hello/helloson worldson
Created /hello/helloson
[zk: localhost:2181(CONNECTED) 61] ls /hello watch
[helloson]
[zk: localhost:2181(CONNECTED) 62] set /hello/helloson worldsonhahaha
cZxid = 0x300000020
ctime = Sat Dec 08 20:15:05 CST 2018
mZxid = 0x300000021
mtime = Sat Dec 08 20:16:06 CST 2018
pZxid = 0x300000020
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 14
numChildren = 0
[zk: localhost:2181(CONNECTED) 63] get /hello/helloson
worldsonhahaha

就算是设置成ls /hello/helloson watch也不行。只有这样才可以触发watcher:

1
2
3
4
5
6
7
8
9
[zk: localhost:2181(CONNECTED) 72] get /swg/swgson watch
8080

[zk: localhost:2181(CONNECTED) 74] set /swg/swgson 7070

WATCHER::
cZxid = 0x300000024

WatchedEvent state:SyncConnected type:NodeDataChanged path:/swg/swgsonctime = Sat Dec 08 20:18:53 CST 2018

为什么更新子节点的时候ls不行,但是get就行呢?客户端要想收到更新子节点内容的消息的话,不能通过子节点的事件来触发,必须把子节点当作父节点来做。然而ls path [watch] :查询指定路径下的子节点所以是针对的子节点,所以不能触发这个事件;而get path [watch] : 查询指定节点中的数据,如果节点中有数据或者stat path [watch] : 查询指定节点的一些描述信息这些直接是操作这个节点,把这个节点当作是父节点,所以能起作用。

watcher使用场景

Watcher是ZK中很重要的特性,ZK允许用户在指定节点上注册一些Watcher,在该节点相关特定事件(比如节点添加、删除、子节点变更等)发生时Watcher会监听到,ZK服务端会将事件通知到感兴趣的客户端上去,该机制是ZK实现分布式协调服务的重要特性。

通知的时候服务端只会告诉客户端一个简单的事件(通知状态、事件类型、节点路径)而不包含具体的变化信息(如原始数据及变更后的数据),客户端如要具体信息再次主动去重新获取数据;此外,无论是服务端还是客户端,只要Watcher被触发ZK就会将其删除,因此在Watcher的使用上需要反复注册,这样轻量的设计有效减轻了服务端压力,如果Watcher一直有效,节点更新频繁时服务端会不断向客户端发送通知,对网络及服务端性能影响会非常大。

比如统一资源配置。

ACL 权限控制列表

  • 针对节点可以设置相关读写等权限,目的是为了保障数据安全性
  • 权限permissions可以指定不同的权限范围以及角色
  • getAcl:获取某个节点的acl权限信息
  • setAcl:设置某个节点的acl权限信息
  • addauth:注册某个用户,要把某个用户的用户名和密码输入到系统中进行注册,用户才能登陆。
  • 默认权限:
1
2
3
[zk: localhost:2181(CONNECTED) 76] getAcl /swg
'world,'anyone
: cdrwa
  • ACL构成:zk的acl通过[scheme🆔permissions]来构成权限列表,其中scheme指采用的某种权限机制;id指允许访问的用户;permissions指权限组合字符串
  • scheme:主要是四种
    • worldworld下只有一个id,即只有一个用户,也就是anyone,那么组合的写法就是world:anyone:[permissions]
    • auth:代表认证登陆,需要注册用户有权限就可以,形式为auth:user:password:[permissions],密码是明文
    • degest:需要对密码加密才能访问,组合形式为digest:username:BASE64(SHA(password)):[permissions],密码是加密的
    • ip:当设置为ip指定的ip地址,此时限制ip进行访问,比如ip:192.168.1.1:[permissions]
    • super:代表超级管理员,拥有所有权限
  • id:代表允许访问的用户
  • permissions
    • c:create,创建当前节点的子节点权限
    • r:read,获取当前节点或者子节点列表
    • w:write,设置当前节点的数据
    • d:delete,删除子节点
    • a:admin,是比较高的权限,可以去设置和修改权限,即拥有分配权限的权限
  • world:anyone:cdrwa:对于默认权限,我们可以修改他的权限字符串,如setAcl path world:anyone:crwa
  • authdigest:先addauth digest username:password(明文密码)注册用户,然后setAcl path auth:username:password(明文密码):cdrwa就可以设置ACL了。再去getAcl path查询到的密码时加密后的。
  • digest:要先退出刚才的auth的账号,直接重启当前客户端即可。setAcl path digest:username:password(密文密码):cdrwa。再去getAcl path查询到的密码时加密后的。此时访问、删除、创建节点比如get path是需要登陆的,即先addauth digest username:password(明文密码)登陆。
  • ipsetAcl path ip:192.168.1.1:cdrwa
  • super:最高权限,修改zkServer.sh增加super管理员,重启zkServer.sh。到bin目录下修改zkServer.sh增加配置:

找到这一行:

1
nohup $JAVA "-Dzookeeper.log.dir=${ZOO_LOG_DIR}" "-Dzookeeper.root.logger=${ZOO_LOG4J_PROP}"

在后面继续添加:

1
"-Dzookeeper.DigestAuthenticationProvider.superDigest=username:xQJmxLMiHGwaqBvst5y6rkB6HQs="

ACL常用使用场景

  • 开发/测试环境分离,开发者无权限操作测试库的节点,只能看。比如分为开发节点和测试节点。
  • 生产环境上控制指定ip的服务可以访问相关节点防止混乱

zk四字命令

  • zk可以通过它自身提供的简写命令来和服务器进行交互
  • 需要使用到 nc 命令,yum install nc
  • echo [command] | nc [ip] [port]
    • 【stat】查看zk的状态信息,以及是单机还是集群状态:echo stat | nc ip或者localhost 2181
    • 【ruok】查看当前zkServer是否启动,正常返回imok:echo ruok | nc ip 2181
    • 【dump】列出未经处理的会话和临时节点:echo dump | nc ip 2181
    • 【conf】查看服务配置
    • 【cons】展示连接到服务器的客户端信息
    • 【envi】环境变量,显示jdk和zk等环境变量的信息
    • 【mntr】监控zk健康信息
    • 【wchs】展示watch的信息
    • 【wchc】与【wchp】:分别展示session与watch及path与watcher的信息,默认这两个命令是不能访问的,需要将他们列入白名单才行