ZooKeeper: 分布式应用的分布式协调服务
ZooKeeper是一个分布式应用下的分布式、开源的协调服务。分布式应用依赖ZooKeeper提供的基础稳固的服务,可以很容易地实现更高层的服务,实现同步、配置信息维护、分组和命名。它的设计目标就是可以易于编程并使用一种类似树形结构的文件系统设计数据模型。运行在Java虚拟机上,同时支持Java语言和C语言。协调服务特别的难于实现正确。特别容易出现竞争条件和死锁。ZooKeeper的动机就是减少分布式系统实现协调服务的开发困难,防止从新开发。
设计目标
ZooKeeper很简单。ZooKeeper通过共享集中式的命名空间互相协调,该命名空间的组织结构很类似于标准的文件系统,由称作znodes的数据节点组成,在ZooKeeper的用法下,他们很类似文件和目录。不像典型的存储用的文件系统,Zookeeper的数据是存储在内存中,这就意味着zookeeper具有吞吐量和低延迟的特点。
Zookeeper实现天生具有优势,具有高性能、高可用和严格的顺序访问的特性。它的这些性能表现表明它可以使用在大的分布式系统中,它的可靠性可以防止单点故障,它的严格的顺序意味着可靠的同步特性可以在客户端中使用。
ZooKeeper是集群的。像它协调的分布式进程,它本身也倾向于通过一部分主机实现集群。
所有组成ZooKeeper服务的主机必须能够互相感知。它们在内存中保存着一份内存镜像,伴随着一条事务日志和永久存储的快照。只要大部分的服务器可用,那么Zookeeper服务就是可用的。客户端可以连接到一个单一的Zookeeper节点,通过维护一个TCP连接,客户端可以发送请求、获取响应、获取监听事件和发送心跳。如果断开和服务器的连接,客户端就会连接另一个服务器。
ZooKeeper是有序的。ZooKeeper用一个数字戳标记每一个更新操作,这个数字反应了所有的ZK事务。后来的操作可以使用这个顺序实现更高层次的超类,如同步命令。
ZooKeeper效率很高。ZooKeeper在只读模式下效率特别高。Zookeeper应用运行在成千上万的机器上,相较于写操作,读操作表现最好,比例达到10:1.
数据模型和层次命名空间
Zookeeper的命名空间特别像一个标准的文件系统。一个名称就是一个用(/)分割的目录集合,每一个命名空间都是一个路径。
节点和临时节点
和标准文件系统不一样的是,Zookeeper中的每一个命名空间和它的子节点都关联着数据,它的文件也像目录一样。(ZooKeeper设计时允许存储协调数据:状态信息、配置信息和位置信息等等,所以每个节点存储的数据都很小,都是kilobyte到byte级别的数据。)下文将用znode来表示我们讨论的zookeeper数据节点。
znodes 保存着结构体,包含数据变化的版本号,ACL变化和时间戳,用于缓存校验信息和协调信息。每次数据节点数据发生变化,版本号就会增加。例如,无论何时客户端获取数据,也获取了数据的版本号。
存储在每个节点的数据的读写操作都是原子的。读操作读数据节点会获取数据,写操作进行写时也会替换所有该节点数据。每个节点都有一个权限控制列表(ACL),约束访问权限。
Zookeeper也有临时节点的概念。只要创建节点的连接存在,这个数据节点就存在,当连接断开时,数据节点就被删除了。当你用的时候,这个节点就是可用的.
有选择的更新和监听
Zookeeper支持观察者的概念。客户端可以给一个节点设置一个观察者,当节点变化的时候,这个观察者就会触发并删除。当观察者被触发时,客户端会受到一个表示数据节点变化的数据包。。与服务器的连接断开时,客户端就会收到一个本地通知,这些以后会用到。
保障
Zookeeper很高效,也很简单。尽管它的目标是成为更加复杂的服务(如状态同步)的结构的基础部分,但是它也有很多保障,如下:
按序存取 - 更新操作会按照客户端发送的顺序执行
原子性 - 更新操作要么成功,要么失败,没有别的情况。
一致性 - 无论客户端连接到集群中的那个服务器,看到的结果都是一样的
可靠性 - 一旦更新成功,数据永远不变,知道有客户端更新它
实时性 - 客户端能够及时看到数据的更新
简单的API
Zookeeper的一个设计目标就是提供简单的变成接口。结果,它支持如下操作:
create
creates a node at a location in the tree
delete
deletes a node
exists
tests if a node exists at a location
get data
reads the data from a node
set data
writes data to a node
get children
retrieves a list of children of a node
sync
waits for data to be propagated
For a more in-depth discussion on these, and how they can be used to implement higher level operations, please refer to[tbd]
实现
ZooKeeper Components展示了zookeeper服务的高级组件。为了防止请求异常,集群中的每个服务器都拥有一份备份。
zookeeper的整个数据树都保存在内存中,每一个更新操作都会先写入可用于恢复的磁盘日志,每一个写操作都会先序列化到磁盘,最后才会更新到内存数据库中。每个zookeeper服务器可以为多个客户端提供服务,实际上,客户端只提交请求到一台服务器。读操作只从集群中的一台服务器获取数据。修改服务状态的读操作和写操作都是通过一致性协议处理。一致性协议保障所有的写操作请求都会发送到称作leader的服务器。其他的服务器被称作追随者,它们都从leader处获取消息提议,完成消息传递。消息层在leader失败时会替换leaders,并同步追随者和领导者的状态。
Zookeeper使用定制的一致性消息协议,由于消息层是原子的,zookeeper可以保证本地副本是正确的。当领导者收到写操作请求时,它马上计算写操作应用时系统的状态,并装换成一个事务记录这个新的状态。
用法
zookeeper的编程接口已经相当简单,你可以很轻松就实现高度有序的操作,例如同步服务,会员分组,制度等等。一些分布式应用已经使用了这些功能。
性能
zookeeper天生高性能。是这样吗?请看下图:
上图展示了Zookeeper不同读写利率下的吞吐量,测试版本为3.2正式版,硬件参数是2Ghz Xeon和15000转硬盘。磁盘用来存储zk的日志,快照数据存储在系统盘。读写操作都是1000个并发,不同颜色的曲线展示了对应数量的zk组成的服务,大约30个其他的服务用来模拟客户端,集群leader不对客户端提供连接服务。
注意
和版本3.1相比,版本3.2的读写性能有了很大的提升。
压力测试也表明它很可靠,这张图演示了在各种各样的错误出现的情况下,zk服务是如何响应的。图中出现的错误事件如下:
一个追随者的异常和恢复
另一个追随者的异常和恢复
领导者的异常
两个追随者的异常和恢复
另一个领导者的异常
可靠性
为了展示当部分服务器宕机时系统的表现,我们用7台机器搭建了zookeeper服务,使用和以前一样的高压测试,但是这次写操作占比30%,这是一个保守的比例
错误出现时zookeeper的可靠性
上图展示了一些比较重要的关注点,首先,如果追随者宕机并很快就恢复了,这时,zookeeper服务器会忽略这个异常并保持高吞吐量。更重要的是,领导者选举算法让系统能够迅速恢复,防止吞吐量大幅下降。在这次观察中,zookeeper在不到200ms的时间内就选举出了新的领导者。最后,追随者一旦恢复并开始处理请求时,zookeeper服务就能再次提高吞吐量。