Introduction to Zookeeper
ZooKeeper 是一个基于
Google Chubby
论文实现的一款解决分布式数据一致性问题的开源实现,方便了依赖 ZooKeeper 的应用实现 数据发布 / 订阅、负载均衡、服务注册与发现、分布式协调、事件通知、集群管理、Leader 选举、 分布式锁和队列等功能。
基本概念
集群角色
一般的,在分布式系统中,构成集群的每一台机器都有自己的角色,最为典型的集群模式就是 Master / Slave 主备模式。在该模式中,我们把能够处理所有写操作的机器称为 Master 节点,并把所有通过异步复制方式获取最新数据、提供读服务的机器称为 Slave 节点。
而 ZooKeeper 中,则是引入了 领导者(Leader)、跟随者(Follower)、观察者(Observer) 三种角色 和 领导(Leading)、跟随(Following)、观察(Observing)、寻找(Looking) 等相应的状态。在 ZooKeeper 集群中的通过一种 Leader 选举的过程,来选定某个节点作为 Leader 节点,该节点为客户端提供读和写服务。而 Follower 和 Observer 节点,则都能提供读服务,唯一的区别在于,Observer 机器不参与 Leader 选举过程 和 写操作的”过半写成功”策略,Observer 只会被告知已经 commit 的 proposal。因此 Observer 可以在不影响写性能的情况下提升集群的读性能(详见下文 “性能优化 - 优化策略 - Observer 模式” 部分)
会话
Session 指客户端会话。在 ZooKeeper 中,一个客户端会话是指 客户端和服务器之间的一个 TCP 长连接。客户端启动的时候,会与服务端建立一个 TCP 连接,客户端会话的生命周期,则是从第一次连接建立开始算起。通过这个连接,客户端能够通过心跳检测与服务器保持有效的会话,并向 ZooKeeper 服务器发送请求并接收响应,以及接收来自服务端的 Watch 事件通知。
Session 的 sessionTimeout 参数,用来控制一个客户端会话的超时时间。当服务器压力太大 或者是网络故障等各种原因导致客户端连接断开时,Client 会自动从 ZooKeeper 地址列表中逐一尝试重连(重试策略可使用 Curator 来实现)。只要在 sessionTimeout 规定的时间内能够重新连接上集群中任意一台服务器,那么之前创建的会话仍然有效。如果,在 sessionTimeout 时间外重连了,就会因为 Session 已经被清除了,而被告知 SESSION_EXPIRED,此时需要程序去恢复临时数据;还有一种 Session 重建后的在新节点上的数据,被之前节点上因网络延迟晚来的写请求所覆盖的情况,在 ZOOKEEPER-417 中被提出,并在该 JIRA 中新加入的 SessionMovedException,使得 用同一个 sessionld/sessionPasswd 重建 Session 的客户端能感知到,但是这个问题到 ZOOKEEPER-2219 仍然没有得到很好的解决。
数据模型
在 ZooKeeper 中,节点分为两类,第一类是指 构成集群的机器,称之为机器节点;第二类则是指 数据模型中的数据单元,称之为数据节点 ZNode。ZooKeeper 将所有数据存储在内存中,数据模型的结构类似于树(ZNode Tree),由斜杠(/)进行分割的路径,就是一个 ZNode,例如 /foo/path1。每个 ZNode 上都会保存自己的数据内容 和 一系列属性信息。
ZNode 可以分为持久节点(PERSISTENT)和临时节点(EPHEMERAL)两类。所谓持久节点是指一旦这个 ZNode 被创建了,除非主动进行移除操作,否则这个节点将一直保存在 ZooKeeper 上。而临时节点的生命周期,是与客户端会话绑定的,一旦客户端会话失效,那么这个客户端创建的所有临时节点都会被移除。在 HBase 中,集群则是通过 /hbase/rs/* 和 /hbase/master 两个临时节点,来监控 HRegionServer 进程的加入和宕机 和 HMaster 进程的 Active 状态。
另外,ZooKeeper 还有一种 顺序节点(SEQUENTIAL)。该节点被创建的时候,ZooKeeper 会自动在其子节点名上,加一个由父节点维护的、自增整数的后缀(上限:Integer.MAX_VALUE)。该节点的特性,还可以应用到 持久 / 临时节点 上,组合成 持久顺序节点(PERSISTENT_SEQUENTIAL) 和 临时顺序节点(EPHEMERAL_SEQUENTIAL)。
版本
ZooKeeper 的每个 ZNode 上都会存储数据,对应于每个 ZNode,ZooKeeper 都会为其维护一个叫做 Stat 的数据结构,Stat 中记录了这个 ZNode 的三个数据版本,分别是 version(当前 ZNode 数据内容的版本),cversion(当前 ZNode 子节点的版本)和 aversion(当前 ZNode 的 ACL 变更版本)。这里的版本起到了控制 ZooKeeper 操作原子性的作用(详见下文 “源码分析 - 落脚点 - ZooKeeper 乐观锁” 部分)
如果想要让写入数据的操作支持 CAS,则可以借助 Versionable#withVersion 方法,在 setData() 的同时指定当前数据的 verison。如果写入成功,则说明在当前数据写入的过程中,没有其他用户对该 ZNode 节点的内容进行过修改;否则,会抛出一个 KeeperException.BadVersionException,以此可以判断本次 CAS 写入是失败的。而这样做的好处就是,可以避免“并发局部更新 ZNode 节点内容”时,发生相互覆盖的问题。
Watcher
Watcher(事件监听器)是 ZooKeeper 提供的一种 发布/订阅的机制。ZooKeeper 允许用户在指定节点上注册一些 Watcher,并且在一些特定事件触发的时候,ZooKeeper 服务端会将事件通知给订阅的客户端。该机制是 ZooKeeper 实现分布式协调的重要特性。
ACL
类似于 Unix 文件系统,ZooKeeper 采用 ACL(Access Control Lists)策略来进行权限控制。
Command | Comment |
---|---|
CREATE [c] | 创建子节点的权限 |
READ [r] | 获取节点数据和子节点列表的权限 |
WRITE [w] | 更新节点数据的权限 |
DELETE [d] | 删除当前节点的权限 |
ADMIN [a] | 管理权限,可以设置当前节点的 permission |
Scheme | ID | Comment |
---|---|---|
world | anyone | ZooKeeper 中对所有人有权限的结点就是属于 world:anyone |
auth | 不需要 id | 通过 authentication 的 user 都有权限 |
digest | username:BASE64 (SHA1(password)) | 需要先通过 username:password 形式的 authentication |
ip | id 为客户机的 IP 地址(或者 IP 地址段) | ip:192.168.1.0/14,表示匹配前 14 个 bit 的 IP 段 |
super | 对应的 id 拥有超级权限(CRWDA) |
常用命令
Command | Comment |
---|---|
conf | 输出相关服务配置的详细信息 |
cons | 列出所有连接到服务器的客户端的完全的连接 / 会话的详细信息(包括“接受 / 发送”的包数量、会话 id 、操作延迟、最后的操作执行等等信息) |
envi | 输出关于服务环境的详细信息 (区别于 conf 命令) |
dump | 列出未经处理的会话和临时节点 |
stat | 查看哪个节点被选择作为 Follower 或者 Leader |
ruok | 测试是否启动了该 Server,若回复 imok 表示已经启动 |
mntr | 输出一些运行时信息(latency / packets / alive_connections / outstanding_requests / server_state / znode + watch + ephemerals count …) |
reqs | 列出未经处理的请求 |
wchs | 列出服务器 watch 的简要信息 |
wchc | 通过 session 列出服务器 watch 的详细信息(输出是一个与 watch 相关的会话的列表) |
wchp | 通过路径列出服务器 watch 的详细信息(输出一个与 session 相关的路径) |
srvr | 输出服务的所有信息(可以用来检查当前节点同步完毕集群数据,处于 Follower 状态) |
srst | 重置服务器统计信息 |
kill | 关掉 Server |
命令执行
echo <four-letter command> | nc IP 2181
常用配置
- dataDir: ZooKeeper 保存服务器存储快照文件的目录,默认情况,ZooKeeper 将 写数据的日志文件也保存在这个目录里(default:/tmp/zookeeper)
- dataLogDir: 用来存储服务器事务日志
- clientPort: 客户端连接 ZooKeeper 服务器的端口,ZooKeeper 会监听这个端口,接受客户端的访问请求(default:2181)
- tickTime(SS / CS): 用来指示 服务器之间或客户端与服务器之间维护心跳机制的 最小时间单元,Session 最小过期时间默认为两倍的 tickTime(default:2000ms)
- initLimit(LF): 集群中的 Leader 节点和 Follower 节点之间初始连接时能容忍的最多心跳数(default:5 tickTime)
- syncLimit(LF): 集群中的 Leader 节点和 Follower 节点之间请求和应答时能容忍的最多心跳数(default:2 tickTime)
- minSessionTimeout & maxSessionTimeout: 默认分别是 2 x tickTime - 20 x tickTime,来用控制 客户端设置的 Session 超时时间。如果超出或者小于,将自动被服务端强制设置为 最大或者最小
- maxClientCnxns: 控制单个客户端(以 IP 地址为唯一标识)创建连接数的上限(default:60),设置为 0 则不作限制
参考资料
https://zookeeper.apache.org
https://yuzhouwan.com/posts/31915
https://static.googleusercontent.com/media/research.google.com/zh-CN//archive/chubby-osdi06.pdf
Disclaimer
- License under
CC BY-NC 4.0
- Copyright issue feedback
me#imzye.me
, replace # with @ - Not all the commands and scripts are tested in production environment, use at your own risk
- No privacy information is collected here