ZooKeeper 分布式应用的分布式协调服务。
ZooKeeper为分布式应用(distributed applications)提供开源的,分布式的协调服务。分布式应用使用ZooKeeper提供的一组简单的原语来构建更高层次的服务,比如同步,配置维护,组和命名。ZooKeeper容易编程,数据模型类似于文件系统的目录树结构。ZooKeeper背后的动机就是为了解除分布式应用从头构建协调服务的职责。ZooKeeper替你做了。
设计目标
简洁 ZooKeeper允许分布式的进程通过组织上类似于标准文件系统的共享层次空间来进行相互协调。命名空间由znode组成,在ZooKeeper中znode指的是存储数据的东东,znode类似于文件和目录。不像普通的文件系统,被设计用来存储,ZooKeeper的数据放在内存中,因此ZooKeeper能获得很高的吞吐量和低延迟。
ZooKeeper的实现高度重视高性能,高可用,严格有序访问。性能特点指的是ZK能用在大规模的,分布式的系统中。稳定性意味着ZK没有单点问题。严格的有序访问意味着客户端可以高级的同步原语。
复制 就像ZK协调的分布式进程一样,ZK本身被复制到一组主机(ensemble)。
组成ZK服务的主机相互之间都知道(某一个Server知道组中其他Server的地址)。他们维护状态的内存镜像,事务日志和持久存储快照。只要大多数服务器可用,ZK服务就可用。Client连接到一个ZK Server,维护一个TCP连接,通过它来发送请求,得到响应,得到监控事件和发送心跳。如果到这个Server的连接中断,Client会连接到一个不同的Server。
有序 ZK为每一次更新(update)标记一个数字(类似于版本号),用来反应ZK事务的顺序。后续的操作可以使用这种有序来实现更高层次的抽象,比如同步原语。
快速 在大多数读的负载中ZK特别快。ZK应用运行在数千台机器上,在读比写更普遍,读写比例大约时10:1的情况下,应用运行的很好。
数据模型和层次命名空间
ZK提供的命名空间(namespace)类似于标准的文件系统。名字(name)就是被/分割的一系列路径元素。ZK命名空间中的znode由路径标识。
节点和临时节点
不同于标准文件系统的是,ZK命名空间中的每一个节点都有数据和子节点。就像是你有一个文件系统,文件同样也是目录。(ZK被设计用来存储协调的数据:状态信息,配置,位置信息,因此存储在znode中的数据通常很小)。
znode维护一个统计结构,含有数据改变的版本号、ACL(access control list)改变的版本号、时间戳,允许缓存验证和协调更新。每当znode节点数据发生变化,版本号增加。例如,client每次检索数据,它同样会获取数据的版本号。
命名空间中每一个节点的数据读和写都是原子的。Read会拿到和这个节点相关联的所有的数据。Write会替换掉所有的数据。znode有一个访问控制列表ACL来限制谁可以做什么。
ZK还有临时节点。只要创建这些节点的会话Session是活跃的,这些节点就一直存在。会话结束,节点被删除。
条件更新和监控
ZK支持监控。Client在一个znode上设置监控(watch)。当znode改变的时候,监控被触发和删除。当监控触发的时候,Client会收到一个数据包,说“数据发生改变”。如果Client和ZK Server之间的连接断掉,会收到一个本地(local)通知。
保证
ZK非常快,非常简单。因为他的目标是构建更复杂服务的基础,比如同步,所以他提供一系列保证。
顺序一致性 来自于Client的更新按照他们发送的顺序被应用
原子 更新要么成功,要么失败,没有部分结果
唯一的系统镜像 不管Client连接到哪个ZK Server,他看到相同的服务视图
可靠 一旦更新被应用,在某一个Client重写这个更新之前,这个更新会持久化
及时 保证Client看到的服务视图在一定时间范围内是最新的
简单的api
ZK其中的一个设计目标就是提供简单的编程接口。
create 创建一个节点(path) delete 删除一个节点 exists 判断节点是否存在 get data 从节点中读取数据 set data 为节点设置数据 get children 获取节点的字节点 sync 等待数据传播
实现
上图展示ZK服务的高层组件。除了请求处理器(Request Processor),组成ZK服务的每一个Server会复制这些组件的副本。
复制的数据库是包含完整数据的内存数据库。为了恢复,更新被记录到磁盘。写被应用到内存数据库之前,先被序列化到磁盘。
每一个ZK Server服务多个Clients。Client连接到一个Server来提交请求。读请求的处理,使用每一个Server数据库的本地(Client连接到哪个Server,哪个Server就处理这个Client的Read请求)。写请求,改变服务器的状态,需要通过一致性协议(agreement protocol)来处理。
作为一致性协议的一部分,来自Clients的所有写请求被路由到一个单独的Server,叫做leader。ZK组中其他Servers叫做follower,负责接收来自于leader的消息提议,在消息回复上取得一致。消息层处理leader失效,同步followers和leader。
ZK使用自定义的原子消息协议。因为消息层是原子的,ZK保证本地副本不会偏离。当leader接收到写请求,它计算系统更新后的状态,把请求转变成获取新状态的事务。
使用
ZK的接口故意定义的非常简单。然而,使用它你可以实现更高层次的有序操作,例如,同步原语,组成员资格,拥有者。
性能
ZK是高性能的。下图时ZK随读写比率变化的吞吐量变化图。
在读超过写的应用中,ZK是性能非常高的,因为写要涉及所有服务器的状态同步。(读超过写就是典型的协调服务的场景)。
可靠
测试同样表明ZK非常可靠。下图,“当出现错误时的可靠性分析”,显示调度如何响应各种各样的失败。在图片中标记的事件是:
1、一个follower的失效和恢复
2、另一个follower的失效和恢复
3、leader的失效
4、两个followers的失效和恢复
5、另一个leader的失效
测试场景是,让写保持30%的比例,这是一个我们期望工作模式的保守估计。
得出结论如下: 1、如果follower失效,并且快速恢复,那么ZK仍旧可以保持很高的吞吐量。2、更重要的是,leader选举算法允许系统快速恢复,抑制吞吐量的下降。3、在我们的观察中,选举新leader花费的时间少于200ms。4、当follower恢复的时候,一但他们开始处理请求,ZK能够再一次恢复吞吐量。