ZK 的应用场景
统一命名服务
命名服务也是分布式系统中比较常见的一类场景,在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务地址、提供者信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等,这些我们都可以统称它们为名字(Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表,通过 ZK 提供的创建节点的 API,能够很容易创建一个全局的唯一 PATH,这个 PATH 就可以作为一个名称
配置中心
配置的管理在分布式应用环境中很常见,例如同一个应用系统需要多个 PC Server 运行,但是它们运行应用的某些配置项是相同的,如果要修改这些相同的配置项,那么就必须同时修改每一台 PC Server,这样很麻烦,而且容易出错,像这样的配置信息完全可以交给 ZooKeeper 来管理,将配置信息保存在 ZK 中的某一个节点中,然后将所有需要修改的机器监控配置信息的状态,一旦发生改变,每台机器就会收到 ZK 的通知,然后从 ZK 中获取最新的配置信息并应用到系统中去
集群管理与 Master 选举
集群机器监控:这通常用于那种对集群中机器状态,机器在线率有较高要求的场景,能够快速对集群中机器的变化做出响应。这样的场景中,往往有一个监控中心,实时检测集群机器是否存活。过去的做法是:监控中心通过某种手段(比如:ping,心跳)定时检测每个机器,或者每个机器定时向监控中心发送心跳。
利用 ZK 有两个特性,就可以实时与另外一台集群机器存活性进行监控:
- 客户端在节点 X 上注册一个 Watcher,那么如果 X 的子节点变化了,会通知客户端
- 创建 Ephemeral 类型的节点,一旦客户端和服务器会话结束或过期,那么该节点就会消失
例如:监控系统在 /clusterServer 节点注册一个 Watcher,以后每动态添加机器,那么就往 /clusterServer 下创建一个 Ephemeral 类型的节点,如:/clusterServer/{hostname},这样,监控系统就能实时的知道机器增减情况
在分布式环境中,相同业务应用分布在不同的机器中,有些业务逻辑(如耗时计算,网络 I/O 处理),往往只要让集群中的某一台机器进行执行,其余机器可以共享这个结果,这样可以大大减少重复劳动,提高性能,于是 master 选举便是这种场景下碰到的主要问题
分布式锁
分布式锁,这个主要得益于 ZK 为我们保证了数据强一致性。锁服务一般分为两类,一个是保持独占,另一个是控制时序。
所谓保持独占:就是所有试图来获取这个锁的客户端,最终只有一个可以成功获取到这把锁,通常的做法就是把 ZK 上的 znode 看作一把锁,通过 create znode 方式来实现,所有客户端都去创建 /distribute_lock 节点,最终成功创建的那个客户端就拥有了这把锁。
控制时序:就是所有试图来获取这个锁的客户端,最终都是会被安排执行,只是有个全局时序罢了,做法和独占类似,只是这里的 /distribute_lock 节点已经预先存在,客户端在它的下面创建临时有序节点(这个可以通过节点的属性控制:CreateMode.EPHEMERAL_SEQUENTIAL 来指定),ZK 的父节点(/distribute_lock)维护一份 sequence,保证子节点创建的时序性,从而也形成了每个客户端的全局时序,然后依次执行
结点类型
ZK 有 4 种类型的节点,分别为持久节点,临时节点,持久顺序节点,临时顺序节点
持久节点
所谓持久**节**点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点
create /test 1
临时节点(-e)
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会被清除掉,注意:这里说的会话失效,而非断开连接。另外,临时节点下面不能创建子节点的
create -e /temp 1
客户端连接服务端,会创建一个 session,当创建一个临时节点的时候,会将临时节点与 session 绑定,同时客户端会一直与服务端保持心跳,保证 session 不会过期,当调用 quit 指令的时候,表示通知服务端,断开与客户端的绑定,并删除 session,而当强行退出客户端的时候,服务端并没有接收到断开指令,同时此时也收不到客户端的心跳,那么 session 就会开始倒计时,一直到 session 过期,这就解释了,为什么断开连接的时候,再连接上,会看到临时节点,过一会又看不到临时节点的原因
持久顺序节点(-s)
这个节点的基本特性和持久节点是一致的,额外的特性是,在 ZK 中,每个父节点会为他的第一级子节点维护一份时序,会记录每个节点创建的先后顺序,基于这个特性,在创建子节点的时候,可以设置 -s 这个属性,那么在创建节点过程中,ZK 会自动为给定节点名加上一个数字后缀,作为新的节点名称,这个数字后缀的范围是整形的最大值。
$ create -s /seq/seq_ 1
Created /seq/seq_0000000000
临时顺序节点
临时顺序节点,类似临时节点和顺序节点
create -e -s /seq/seqt_ 1
ZK 对每个结点最大的数据量默认上限是 1M
stat
ZK 命名空间中的每一个 znode 都有一个与之关联的 stat 结构,类似于 unix/linux 文件系统中文件的 stat 结构,znode 的 stat 结构中的字段显示如下:
[zk: localhost:2181(CONNECTED) 4] get /test
content # 数据
cZxid = 0x22 # 创建 znode 的事务 ID
ctime = Tue Feb 18 22:15:52 CST 2020 # 创建时间
mZxid = 0x22 # 最后修改 znode 的事务 ID
mtime = Tue Feb 18 22:15:52 CST 2020 # 最近修改时间
pZxid = 0x22 # 最后修改或删除子节点的事务 ID
cversion = 0 # 对此 znode 的子节点进行更改的次数
dataVersion = 0 # 对此 znode 的数据进行更改的次数
aclVersion = 0 # 对此 znode 的 ACL 进行更改的次数
ephemeralOwner = 0x0 # 如果 znode 是 ephemeral 类型,则是 znode 的所有者的 session ID,否则为 0
dataLength = 7 # 此 znode 的数据字段长度
numChildren = 0 # 此 znode 的子结点数量
Zxid
类似于 RDBMS 中的事务 ID,用于标识一个更新操作的 Proposal ID,为了保证顺序性,该 zkid 必须单条递增。因此 ZK 使用一个 64 位的数来标识,高 32 位是 Leader 的 epoch,从 1 开始,每次选出新的 Leader,epoch 加 1,低 32 位为该 epoch 内的序号,每次 epoch 变化,都将低 32 位的序号重置,这样就保证了 zkid 的全局递增性
watch
一个 ZK 的结点可以被监控,包括这个目录中存储的数据,子节点目录的变化,一旦变化可以通知监控的客户端,这个功能是 ZK 对应应用最重要的特性,通过这个特性可是实现的功能包括配置的集中管理,集群管理,分布式锁等。
watch 机制官方说明:一个 watch 事件是一个一次性的触发器,当被设置了 watch 的数据发生了改变的时候,则服务器将这个改变发送给设置了 watch 的客户端,以便通知他们
- 可以注册 watcher 的方法:getData、exists、getChildren
- 可以触发 watcher 的方法:create、delete、setData。断开连接的情况下触发的 watcher 会丢失
一个 watcher 实例就是一个回调函数,被回调一次后就被移除了,如果还需要关注数据的变化,就需要再次注册 watcher。New Zookeeper 时注册的 watcher 叫 default watcher,它不是一次性的,只对 client 的连接状态变化而做出反应
| Action | event For “/path/child” | event For “/path/child” |
|---|---|---|
| create(“/path”) |
| EventType.NodeCreated | 无 |
| delete(“/path”) | EventType.NodeDataChanged | 无 |
| setData(“/path”) | EventType.NodeDataChanged | 无 |
| create(“/path/child”) | EventType.NodeChildrenChange(getChild) | EventType.NodeCreated |
| delete(“/path/child”) | EventType.NodeChildrenChange(getChild) | EventType.NodeDeleted |
| setData(“/path/child”) | 无 | EventType.NodeDeleted |
| event For “/path” | Default Watcher |
exists(“/path”) | getData(“/path”) | getChildren(“/path”) |
|---|---|---|---|---|
| EventType.None | √ | √ | √ | √ |
| EventType.NodeCreated | √ | √ | ||
| EventType.NodeDeleted | √ | √ | ||
| EventType.NodeDataChanged | √ | √ | ||
| EventType.NodeChildrenChanged | √ |
exists 和 getData 设置数据监视,而 getChildren 设置子节点监视
**
常用命令
创建结点(znode)
用给定路径创建一个结点,flag 参数指定创建的节点时临时的、持久的还是顺序的。默认情况下,所有节点都是持久的。当会话过期或客户端断开连接时,临时节点(-e)将会被自动删除;顺序节点保证节点将是唯一的。ZK 集合将向节点路径填充 10 位序列号。例如:节点路径 /seq/seq_ 将转换位 /seq/seq_0000000001,下一个序列号将为 /seq/seq_0000000002,如果没有指定 flag,则节点默认是持久的
# 持久节点
create /path data
# 临时节点
create -e /path data
# 顺序节点
create -s /path/prefix data
获取数据
它返回节点的关联数据和指定节点的元数据,此 CLI 还用于分配监视器以显示数据相关的通知
$ get /test
content
cZxid = 0x22
ctime = Tue Feb 18 22:15:52 CST 2020
mZxid = 0x22
mtime = Tue Feb 18 22:15:52 CST 2020
pZxid = 0x22
cversion = 0
dataVersion = 0
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
设置数据
设置指定 znode 的数据,完成此操作后,可以用 get 命令检查数据
$ set /test someting
cZxid = 0x22
ctime = Tue Feb 18 22:15:52 CST 2020
mZxid = 0x23
mtime = Wed Feb 19 00:03:04 CST 2020
pZxid = 0x22
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 8
numChildren = 0
创建子节点
创建子节点类似创建新的 znode,唯一区别是,子节点的路径也将具有父路径
$ create /parent/children data
列出子节点
用于显示 znode 的子项
$ ls /path
状态检查
状态描述指定的 znode 元数据,它包含时间戳,版本号,ACL、数据长度、znode子项等明细
$ stat /path
cZxid = 0x22
ctime = Tue Feb 18 22:15:52 CST 2020
mZxid = 0x23
mtime = Wed Feb 19 00:03:04 CST 2020
pZxid = 0x22
cversion = 0
dataVersion = 1
aclVersion = 0
ephemeralOwner = 0x0
dataLength = 7
numChildren = 0
移除节点
移除指定的 znode 并递归其所有子节点,只有在 znode 可用的情况下才会发生
$ rmr /path
ACL
ZK 作为分布式架构中的重要中间件,通常会在上面以节点的方式存储一些关键信息,默认情况下,所有应用都可以读写任何节点,在复杂的应用中,这不太安全,ZK 通过 ACL 机制来解决访问权限的问题
- ZK 的权限控制是基于每一个 znode 节点的,需要对每个节点设置权限
- 每个 znode 支持设置多种权限控制方案和多个权限
- 子节点不会继承父节点的权限,客户端无权访问某个节点,但可能可以访问它的子节点
ACL 权限控制,使用 schema:id:permission 来标识,主要包括 3 个方面:
- 权限模式(schema):鉴权的策略
- 授权对象(id)
- 权限(permission)
schema
- world:只有一个用户:anyone,代表所有人(默认)
- ip:使用 IP 地址认证
- auth:使用已添加认证的用户认证
- digest:使用”用户名/密码”的方式认证
id
- world:只有一个 id,即 anyone
- ip:通常是一个 ip 地址段,比如:192.168.0.124 或 192.168.0.1/24
- auth:用户名
- degest:自定义,通常是 username:BASE64(SHA-1(username:password))
权限
- CREATE:简写 c,可以创建子节点
- DELETE:简写 d,可以删除子节点
- READ:简写 r,可以读取节点数据及显示子节点列表
- WRITE:简写 w,可以设置节点数据
- ADMIN:简写 a,可以设置节点访问控制列表
查看 ACL
$ getAcl /path
'world,'anyone
: cdrwa
默认创建的节点的权限是最开放的,所有都可以进行增删改查管理
设置 ACL
设置节点对所有人都有写和管理权限
# 语法
$ setAcl /parent world:anyone:wa
# 添加/登录 用户
$ addauth digest zhangsan:12345
# 再设置权限,这个节点只有 zhangsan 这个用户拥有权限
$ setAcl auth:zhangsan:12345:rdwca
Curator 客户端
- Recipes:ZK 典型应用场景的实现,这些实现是基于 Curator Framework
- Framework:ZK API 的高层封装,大大简化了 ZK 客户端编程,添加了 ZK 连接管理,重试机制等
- Utilities:为 ZK 提供各种实用程序
- Client:ZK Client 的封装,用于取代原生的 ZK 客户端(ZooKeeper 类),提供一些非常有用的客户端特性
- Errors:Curator 如何处理错误,连接问题,可恢复的例外等
Curator 主要解决了三类问题
- 封装 ZooKeeper Client 与 ZooKeeper Server 之间的连接处理
- 提供了一套 Fluent 风格的操作 API
- 提供 ZooKeeper 各种应用场景(Recipes、共享锁服务,集群领导选举机制)的抽象封装
原生 ZK 客户端存在的问题
客户端在连接服务端是会设置一个 sessionTimeout(session 过期时间),并且客户端会给服务端以心跳的形式刷新服务端 session 时间,当网络断开后,服务端无法收到客户端心跳,会进行 session 倒计时,判断是否超过了 session 过期时间,一旦过期,就算后来网络连通了,就会接到 session 过期的事件,从而删除临时节点和 watcher 等,原生客户端不会重建 session
