👀ZooKeeper - 系统模型 - 图1

数据模型

Redis的数据结构是键值对,Zookeeper的数据结构是树状结构,类似Linux的文件系统。
点击查看【processon】
ZooKeeper里一个数据节点通常包含三部分:

  • 节点数据,默认情况下只能保存不超过1MB的数据
  • 节点类型,决定一个数据节点的生命周期和ID生成规则
  • 节点元数据,记录数据的状态信息

因为后两个部分比较重要,所以只分析后两个

节点类型

ZooKeeper里一个节点的类型有以下四种:

  1. 持久节点
  2. 持久顺序节点
  3. 临时节点
  4. 临时顺序节点

持久节点(Persitent)

此类节点一经创建,除非人为删除,否则将一直存在于ZooKeeper服务器里

持久顺序节点(Persitent | Sequential)

此类节点拥有持久特性+顺序特性。其中顺序特性是指:每个父节点会为自己的一级子节点维护一份顺序用于记录每个一级子节点的创建先后顺序
基于这个顺序特性,每当一个父节点下的一级子节点被创建时,ZooKeeper会自动给这个新节点重命名成一个新的名字,重命名规则:
新节点名 = 给定的节点名 + 一个数字后缀(数字大小是整形的上限)
数字后缀来源于父节点维护的顺序记录。
注意,顺序特性不区分临时 OR 持久,只要是相同的父节点,就使用相同的顺序记录。
**

临时节点(Ephemeral)

此类节点仅在一个 session 范围内有效,如果 session 结束,该节点自动移除session 是客户端和服务端共同建立的会话,会话中断并不意味着客户端与服务端的TCP连接端口另外在临时节点下无法创建子节点!
**

临时顺序节点

在临时节点的基础上,增加了顺序特性。

TTL节点(从3.6版本起)

只有节点是 持久 或 持久顺序节点才能添加 TTL 特性。
TTL 特性指:在 TTL 时间内该节点没有被修改 也没有子节点,那么该节点会被设定为删除候选人,在未来优先被服务器删除。

Note: TTL Nodes must be enabled via System property as they are disabled by default. See the Administrator’s Guide for details. If you attempt to create TTL Nodes without the proper System property set the server will throw KeeperException.UnimplementedException.

节点元数据

  • **czxid** ,当该节点被创建时的事务ID,即创建这个节点的操作的事务ID
  • **mzxid** ,保存最近一次对该节点执行更新操作的事务ID。比如修改该节点的数据等,对于该节点的子节点进行操作时不影响该值
  • **pzxid** ,该节点的一级子节点列表最后一次被修改时的事务ID。只有子节点列表变更了(新增、删除子节点)才会变更 pzxid ,子节点的数据变更不会影响
  • **ctime** ,该节点创建时的时间
  • **mtime** ,该节点最后一次被修改的时间
  • **version(dataVersion)** ,该节点里数据的版本号,每次修改数据都会导致它的递增(即使前后两次数据没发生变更,该值也会递增)
  • **cversion** ,子节点(直接子节点,子节点的子节点不算)的新增、删除次数。子节点的内容变更不算
  • **aversion(aclVersion)** , 结点的ACL(权限)更改次数——类似 linux 的权限列表,维护的是当前结点的权限列表被修改的次数
  • **ephemeralOwner** 该节点的会话ID,只有节点是临时节点才存在会话ID;如果节点是持久化节点,该属性一直为0
  • **dataLength** ,该节点的数据的长度
  • **numChildren** ,该节点的子节点个数

这里解释一下:为什么发生子节点增删才会更新 pzxid ,而子节点数据发生变化不行呢?可以通过类比 linux 里的文件夹概念,比如我现在的 /home/codeleven 文件夹下有这些列表:

  1. [codeleven@localhost home]$ ll /home/codeleven/
  2. total 0
  3. drwxr-xr-x. 4 codeleven codeleven 40 Aug 30 15:05 Desktop
  4. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Documents
  5. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Downloads
  6. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Music
  7. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Pictures
  8. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Public
  9. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Templates
  10. drwxr-xr-x. 2 codeleven codeleven 6 Aug 1 10:53 Videos

我通过 vim 编辑 /home/codeleven

  1. " ============================================================================
  2. " Netrw Directory Listing (netrw v149)
  3. " /home/codeleven
  4. " Sorted by name
  5. " Sort sequence: [\/]$,\<core\%(\.\d\+\)\=\>,\.h$,\.c$,\.cpp$,\~\=\*$,*,\.o$,\.obj$,\.info$,\.swp$,\.bak$,\~$
  6. " Quick Help: <F1>:help -:go up dir D:delete R:rename s:sort-by x:exec
  7. " ============================================================================
  8. ../
  9. ./
  10. .cache/
  11. .config/
  12. .dbus/
  13. .local/
  14. .mozilla/
  15. .pki/
  16. .vim/
  17. Desktop/
  18. Documents/
  19. Downloads/
  20. Music/
  21. Pictures/
  22. Public/
  23. Templates/
  24. Videos/
  25. .ICEauthority
  26. .Xauthority
  27. .bash_history
  28. .bash_logout
  29. .bash_profile
  30. .bashrc
  31. .esd_auth
  32. .viminfo

/home/codeleven 记录了 /home/codeleven 下的所有子文件名称,并不关心这些子文件的情况。
所以回过头看 ZooKeeper一个父节点也是保存着这样一份子文件列表(一级子文件),如果有增删子节点,父节点维护的这份子文件列表就会更新。

事务ID

在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的操作,比如增加/删除/更新节点;客户端会话创建与失效等等。对于每一个事务请求,ZooKeeper都会分配一个全局唯一的事务ID,即 ZXID ,通常是一个64位的数字。每一个 ZXID 都对应一次更新操作,通过 ZXID 可以间接判断各个事务执行的先后顺序。

版本号

节点元数据信息里保存了三个版本号:

  • version(dataVersion)
  • cversion
  • aversion(aclVersion)

它们的存在是为了保证分布式数据的原子性。怎么保证呢?通过锁机制。而这种使用版本号的锁机制,就是我们常常说的乐观锁(Optimistic Concurrency Control,OCC)了~每次更新节点前,比对该节点的实际版本号和我们预期的版本号是否一致,如果一直就原子性更新。注意,从比较到更新这一个过程都是原子的,由底层硬件指令支持。
相关文章可以看:
👉《多线程与高并发 - CAS》

Watcher机制

Wacther由三个部分共同配合完成:

  • Zookeeper的客户端
  • Zookeeper的服务端
  • 客户端的WatchManager对象

客户端首先将Watcher注册到服务端,同时将Watcher对象保存到客户端的WatchManager对象里。当Zookeeper服务端监听到数据变化时,服务端会主动通知客户端,接着客户端的WatchManager就会触发相关Watcher来回调相应的处理逻辑,从而完成整体的数据 发布/订阅 流程。

Watcher接口

  1. public interface Watcher {
  2. // 当服务端有数据变更时,客户端的WatchManager会回调process()
  3. void process(WatchedEvent event);
  4. }

Watcher事件

KeeperState EventType 触发条件 说明
SyncConnected(3) None(-1) 客户端与服务器正常连接时 此时客户端和服务端处于连接状态
NodeCreated(1) Watcher监听的数据节点发生创建
NodeDeleted(2) Watcher监听的数据节点被删除
NodeDataChanged(3) Watcher监听的数据节点内的数据发生变化
NodeChildrenChanged(4) Watcher监听的数据节点的子节点列表发生变化
Disconnected(0) None(-1) 无法连接服务器的时候就会Disconnected 此时客户端和服务端处于断开状态
Expired(-112) None(-1) 会话session失效时 此时客户端的会话失效,通常会收到SessionExpireException异常
AuthFailed(4) None(-1) 身份认证失败时 通常会受到AuthFailedException异常
Closed(7) None(-1) 客户端主动close了连接 连接关闭

详细的使用可以看《🔧Zookeeper - JavaAPI》

ACL权限控制

目前笔者所接触到的权限控制方式有两种:

  1. UGO(User、Group、Others) ,在 Unix 上常用的权限控制,权限控制粒度较粗
  2. ACL(Access Control List) ,权限控制粒度较细,现在绝大多数 Unix 都支持的权限模式,在 Linux 2.6 版本以后也开始支持该模式

ACL机制由下面三个部分组成:
scheme:expression:acl

  • scheme ,授权模式
  • expression ,授权对象,根据授权模式的不同而不同
  • acl ,授权权限

    授权模式(scheme)

    world模式

    此种模式下,只有一个授权对象(ID),即 anyone 。简单来说,该模式是给所有普通大众的权限,如果需要额外🐂🍺的权限,需要额外设定。

ip模式

此种模式下,授权对象是IP地址。只有在该IP上的客户端才能访问。
另外该模式支持网段配置ip:192.168.0.1/24 ,即 192.168.0.* 的所有IP都拥有该权限。
最后没有配置的IP地址无任何权限

auth模式

对已经 addauth 的用户进行授权。在此种模式下, ZooKeeper 会对我们 addauth 的账号密码进行比较判断,如果一致则认证成功;反之认证失败。
提一嘴——关于这个模式许多培训班都讲错了!

auth is a special scheme which ignores any provided expression and instead uses the current user, credentials, and scheme. Any expression (whether user like with SASL authentication or user:password like with DIGEST authentication) provided is ignored by the ZooKeeper server when persisting the ACL. However, the expression must still be provided in the ACL because the ACL must match the form scheme:expression:perms. This scheme is provided as a convenience as it is a common use-case for a user to create a znode and then restrict access to that znode to only that user. If there is no authenticated user, setting an ACL with the auth scheme will fail.

auth 模式下,是会忽略我们的 expression ,而会使用当前 addauth 的用户替代(如果 addauth 了多个,就给多个用户设置权限)。

digest模式

在此种模式下, Zookeeper 会对我们认证的账号密码分别进行 SHA-1 加密、 BASE64 编码,然后对加密内容进行比较,如果和设置权限时的一致则认证通过;反之认证失败。

super模式

超级管理员模式,超级管理员的创建和 digest 的有些类似,只是授权方式不同。
相关使用教程可以看🔧Zookeeper - 常用Shell命令

授权对象(id)

不同模式下的授权对象不同:

  • world 的授权对象只能是是 anyone 用户
  • ip 的授权对象 是某个具体的ip地址 或者 IP网段
  • authdigest 模式下的授权对象是具体要认证的某个用户

权限(permission)

  • create( c ),在当前节点下创建子节点的权限
  • delete( d ),在当前节点删除子节点(仅直接节点,子节点的子节点无效)的权限
  • read( r ),读取节点数量及显示子节点列表的权限
  • write( w ),设置节点数据的权限
  • admin( a ),可以设置节点访问控制列表权限(就是管理权限的权限)

这里千万注意,权限别写错了,别给其他字符!

其使用方式如下所示:

命令 使用示例 描述
getAcl [-s] PATH getAcl /zoo01 查看某个节点的ACL权限
setAcl [-s] [-v <version>] PATH SCHEME:ID:ACL setAcl /zoo world:anyone:craw 设置ACL权限
addauth addauth 添加认证用户

相关使用教程可以看🔧Zookeeper - 常用Shell命令