简介
ZooKeeper 是什么
ZooKeeper 是开源的分布式协调服务,由雅虎创建,Google Chubby 的开源实现。它的设计目标是封装那些复杂且容易出错的分布式一致性服务,构成一个高效可靠的原语集,然后提供简单易用的接口给用户使用。
设计目标
ZooKeeper 是一个典型的分布式数据一致性解决方案,ZooKeeper 主要有四个设计目标:
顺序一致性
从同一个客户端发起的事务请求,最终将会严格按照其发起顺序被应用到 ZooKeeper 中去。
简单的数据模型
ZooKeeper 通过一个共享的,树形结构名字空间来进行相互协调。树形结构的数据模型,是由一系列 ZNode 数据节点组成,它类似于文件系统,Znode 之间的层级关系,就像文件系统的目录结构一样。
构建集群
一个 ZooKeeper 集群通常由多台机器组成,一般是 3 ~ 5 台机器(因为仲裁模式,一般选用奇数台机器)就可以组成一个可用的 ZooKeeper 集群,集群中每台机器被赋有不同角色:Leader,Follower, Observer。
组成 ZooKeeper 的每台机器都会在 内存 中维护当前 ZooKeeper 的树形结构,并且每台机器之间都互相保持通信,同步系统数据。
ZooKeeper 的客户端会选择和其中任何一台机器创建一个 TCP 连接,而当这个连接断开后,客户端会自动连接到集群中的其他机器。
高性能
机器将全量数据存储在内存中,并直接服务于客户端的所有非事物请求(只读请求),它非常适合于以读操作为主的应用场景。
不适用
因为 ZooKeeper 是将全量数据(协同数据)存储在内存中以提高性能。所以 ZooKeeper 不适合用作海量的数据存储,最佳实践应该将应用数据和协同数据独立存储。
架构
仲裁模式
ZooKeeper 系统可以运行于两种模式之下:独立模式(standalone)和仲裁模式(quorum)。独立模式是一个单独的服务器,ZooKeeper 状态无法复制,通常用于开发阶段。
生产中 ZooKeeper 的机器集群运行于仲裁模式。机器之间可以进行状态复制,同时为客户端的请求服务。
仲裁模式下,ZooKeeper 复制集群中所有机器的数据树。但是如果让一个客户端等待每个服务器完成数据保存后再继续,请求的延迟问题将无法接受。为了解决这个问题,系统会指定一个服务器的最小数量,来使 ZooKeeper 可以有效运行,这个数量一般叫做“法定人数”。
若客户端提交某个写操作,Leader 会将该操作提议到 Follower,如果达到法定数量的 Follower 认可这个操作,就可认为该操作写成功。法定人数的大小至关重要。
如果法定人数过小,那么当产生网络分区时,会产生每个分区下 ZooKeeper 都可以正常独立运行,整个系统的状态产生不一致,也就是脑裂。假设有 5 台机器构成 ZooKeeper 集群,法定人数设置成 2,如果通信故障,将集群分割成两个分区,分别包含 2 台机器和 3 台机器,因为机器数量都不小于法定人数,所以能独立运行
如果法定人数过大,那么会降低集群可靠性和分区容错。假设有 5 台机器的 ZooKeeper 集群,如果法定人数设置为 4,那么只允许 1 台机器发生故障。
ZooKeeper 的法定人数可配置,默认采用多数原则。为了提高可靠性,一般采用奇数个机器组成集群。
集群角色
ZooKeeper 没有选用典型的 Master / Slave 模式,而是引入了 Leader,Follower 和 Observer 三种角色。
Leader
也叫群首,Leader 的作用主要是对客户端发起的写请求进行排序,包括:create,setData 和 delete 操作。Leader 将每一个请求转换成一个事务,将这些事务发送给 Follower 和 Observer,确保集群按照 Leader 确定的书讯接受并处理这些事务。当然 Leader 也提供读服务。
Follower
除了 Leader,其他机器都属于 Follower 和 Observer。Follower 也叫跟随者,它们只提供读服务,但是参与仲裁,也就是说会进行 Leader 选举过程和写操作的仲裁。
Observer
也叫观察者,只提供读服务。它不参与 Leader 选举和写操作仲裁。仅仅复制 Leader 发布的状态变更。
Observer 的主要作用是提高读请求的可扩展性。通过加入多个 Observer,我们可以在不牺牲写操作吞吐率的前提下提供更多的读服务。
会话(Session)
会话概念是对于集群和客户端而言的,在对 ZooKeeper 执行任何操作前,一个客户端必须先与服务器建立会话。客户端提交给 ZooKeeper 的所有操作均关联在一个会话上。会话提供了顺序保证,同一个会话的请求会以先进先出的顺序执行。
设置会话是会有 sessionTimeout 这个参数设置一个客户端会话的超时时。当由于服务器压力太大,网络故障或者是其他原因导致客户端断开连接,只要在 sessionTimeout 时间内能重新连上集群中任意一台服务器,那么之前这个会话仍然有效。
设 sessionTimeout 为 t,ZooKeeper 客户端这边,经过 t / 3 时间内未收到消息,客户端会向服务器发送心跳。在经过 2t / 3 时间后,客户端开始找寻其他的服务器,此时它还有 t / 3 时间去寻找。
重连时,客户端不能连接到这样的服务器:它未发现更新而客户端已经发现更新。ZooKeeper 通过比较事务id(zxid),来判断服务器是否合格。
基本概念
数据节点(znode)
ZooKeeper 将所有数据存储在内存中,称之为“znode”。数据模型是一棵树(znode tree),由斜杠(/)进行分割的路径,就是znode。每个 znode 上都会保存自己的数据内容,同时还会保存一些列属性信息。
如上图所示,NameService,Server1,Server2,Configuration 等每个都是一个 znode。
持久节点和临时节点
根据持久性分,znode 可以分为:
- 持久节点(persistent)。持久节点只能通过 delete 接口才能删除;
- 临时节点(ephemeral)。临时节点不仅通过 delete,当创建该节点的客户端会话过期或关闭后,节点也会被删除。
有序节点
一个 znode 还能设置为有序节点(sequential)。当创建有序节点时,一个序号会被追加到路径之后。例如,当客户端创建一个有序节点 /tasks/task-,那么 ZooKeeper 会分配一个序号,如 1,并将该序号追加到路径之后,所以这个 znode,最后会是 /tasks/task-1,这个序号是由“父节点”维护的自增数字。
版本
znode 上都会存储数据,对于每个 znode,ZooKeeper 都会维护一个叫做 Stat 的数据结构,Stat 中记录了 znode 的三个数据版本,分别是:
- version,当前 znode 版本。
- cversion,当前 znode 子节点的版本。
- aversion,当前 znode ACL 版本。
版本信息表示对数据节点的数据内容,子节点列表,或节点 ACL 信息的修改次数。版本信息用来实现乐观锁机制的“写入校验”。
如果 version 为“-1”,则说明客户端并不要求使用乐观锁,写操作时忽略版本比较。
如果 version 不为 “-1”,那么就要对比客户端写操作时携带的 version,和 znode 当前的版本,如果两个版本不匹配,就抛出异常,拒绝执行写操作。
监视点(Watcher)和通知(Notification)
ZooKeeper 提供远程服务的方式被访问,如果通过轮询的方式不断查询 ZooKeeper 的 znode tree 状态,没有必要且导致较高的延迟。为了替代轮询操作,ZooKeeper 提供基于通知(notification)的机制:客户端向 ZooKeeper 注册需要接收通知的 znode,通过对 znode 设置监视点(watcher)来接受通知。
监视点是单次出发的操作,即一次通知后,该监视点就作废。所以为了接收多次通知,客户端必须每次通知后设置一个新的监视点。
为了保证没有数据变更被遗漏,通常操作时,会在设置监视点之前,先读一次 znode 的状态。
内容来源
从 Paxos 到 ZooKeeper 分布式一致性原理与实践
ZooKeeper 分布式过程协同技术详解