Because Coordinating Distributed Systems is a Zoo
ZooKeeper 提供了一种分布式应用程序的高性能协同服务。它封装了一些基础公共服务,例如:命名,配置管理,同步和组服务,并提供简单易用的开发接口。我们可以利用 ZooKeeper 来直接实现一致性,组管理,领导选举和存在协议等功能。当然,我们也可以利用它实现我们特殊的需求。
ZooKeeper 是一种分布式、开源的协同服务。它暴露了一组简单的原语,分布式应用程序可以基于该原语组,实现更高级的服务,如同步、配置维护、组别和命名。它是一种编程友好型的设计,并采用大家熟悉的文件系统的目录树结构作为数据模型,可以用 Java 运行,也可以和 C 绑定。
协同服务很难正确运行,经常出现竞争危害和死锁。ZooKeeper 的目的就是降低协同服务实现与维护的成本。
易用性,ZooKeeper 允许分布式进程通过与标准文件系统类似的共享层次命名空间互相协同。命名空间由数据节点(znodes)组成,类似于文件与目录。与专为存储设计的典型文件系统不同,ZooKeeper的数据都保存在内存中,以获得高吞吐和低时延的特性。
ZooKeeper实现了高性能、高可用和严格有序。高性能意味着可以被应用在大型分布式系统,高可用则避免了单点故障的风险,严格有序保证了复杂的同步原语可以在客户端实现。
可复制,就像它所协同的分布式进程一样,ZooKeeper 的组件也是可复制的
构成 ZooKeeper 服务的服务器必须互相感知,它们各自维护了一张相同的内存状态镜像,以及持久存储的事务日志与快照,只要大部分的服务器可用,ZooKeeper 的服务就可以正常运行。
客户端会维护与某一台 ZooKeeper 服务器的 TCP 长连接,并通过该连接进行请求发送,响应接收,监听事件获取与心跳检测。如果当前 TCP 长连接断开,客户端会向另一台服务器发起连接请求。
有序性,ZooKeeper 为每一次更新标记了一个序号,以反映所有事务的顺序。后续操作可以使用该序号实现更高级的抽象,例如同步原语。
速度快,ZooKeeper 在“读频繁”工作流中表现非常快,在数千台集群中运行,读写比为 10:1 时,性能最好。
ZooKeeper 提供的命名空间类似于标准文件系统,名称是以斜杠(/)分隔的路径元素序列。ZooKeeper 命名空间中的每个节点都有唯一的路径标识。
不同于标准文件系统,ZooKeeper 命名空间中的每个节点都可以持有数据与子节点(文件亦是目录)。ZooKeeper 被设计用于存储协同数据,如状态信息,配置,位置信息等,因此存储在每个节点上的数据量都比较小,一般不到1KB。接下来,我们采用术语 znode 表明我们正在谈论 ZooKeeper 中的数据节点。
znode 维护一了个统计结构,其中包含数据变更,ACL变更与时间戳的版本号,以允许缓存验证和协调更新。每次 znode 的数据发生变化时,它的版本号都会增加。例如,每当客户端检索数据时,它也会收到该数据的版本信息。
命名空间中每个 znode 存储的数据,都以原子方式进行读取与写入。读取将获得该 znode 关联的所有数据字节,写入则替换所有数据。每个节点都有一个访问控制列表(ACL),它限制谁可以做什么。
ZooKeeper 还提供了一种临时节点,它们的生命周期取决于创建它们的会话(session),当会话结束时,临时节点将会被删除。
ZooKeeper 支持监听功能。客户端可以在 znode 上设置一个监听器,当 znode 发生变更时,监听器将被触发并移除,与此同时,客户端将收到一个表示 znode 变更的数据包。此外,如果客户端与 ZooKeeper 服务器的 TCP 连接断开时,客户端将收到一个本地通知。
ZooKeeper 既简单又快速,由于其目标是提供基础原语,以构建更为复杂的服务,因此,它提供了一系列的保证。
顺序一致性,客户端的更新将按照他们发送的顺序执行;原子性,更新成功或失败,没有中间状态;单一系统映像,无论客户端连接的是哪一台服务器,它们看到的服务视图都是相同的;可靠性,一旦更新成功,它将一直持续到被下一个客户端更新覆盖;及时性,客户端看到的系统视图,在一定时间范围内保证是最新的。ZooKeeper 的设计目标之一是提供非常简单的可编程接口,因此,它只提供如下操作:
创建(creat),在目录树中某个位置创建一个节点;删除(delete),删除一个节点;存在性检查(exists),测试节点是否存在于某个位置;获取数据(get data),从节点中读取数据;设置数据(set data),向节点中写入数据;获取子节点(get children),检索节点的子节点列表;同步(syc),等待数据传播。下图展示了 ZooKeeper 服务的高级组件。除了请求处理器之外,组成 ZooKeeper 服务的每个服务器都会对自身的组件进行复制备份。
复制数据库是包含完整数据树的内存数据库,它将更新记录到磁盘以获取可恢复性,并将写入数据也序列化到磁盘上,然后再更新内存数据库。
每个 ZooKeeper 服务器都为客户端提供服务。客户端需要连接到一个服务器上,才能提交请求。客户端的读请求,实际上是请求存储在本地的服务端数据库的备份。服务状态更改请求与写入请求则是通过一种[一致性协议]()处理。
一致性协议规定了所有客户端写请求都将被转发至单个服务器,即领导节点(Leader)。其他的 ZooKeeper 服务器被称为 追随者(Followers),它们接收来自 Leader 的消息提议,然后对消息的传递进行表决。消息传递层负责将 Followers 同步给 Leader,并在 Leader 发生故障时暂代履责。
ZooKeeper 使用自定义的原子消息协议,由于消息层是原子的,因此可以保证 ZooKeeper 的本地副本不会发散(保持统一),当 Leader 收到写请求时,它会计算写请求执行时的系统状态是什么,并将其转换成捕获该新状态的事务操作。