ZooKeeper由雅虎研究院开发,是Google Chubby的开源实现,后来托管到Apache,于2010年11月正式成为Apache的顶级项目。
ZooKeeper是一个经典的分布式数据一致性解决方案,致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务。
分布式应用程序可以基于ZooKeeper实现数据发布与订阅、负载均衡、命名服务、分布式协调与通知、集群管理、Leader选举、分布式锁、分布式队列等功能。
ZooKeeper致力于为分布式应用提供一个高性能、高可用,且具有严格顺序访问控制能力的分布式协调服务
ZooKeeper将全量数据存储在内存中,并直接服务于客户端的所有非事务请求,尤其适用于以读为主的应用场景
ZooKeeper一般以集群的方式对外提供服务,一般3 ~ 5台机器就可以组成一个可用的Zookeeper集群了,每台机器都会在内存中维护当前的服务器状态,并且每台机器之间都相互保持着通信。只要集群中超过一般的机器都能够正常工作,那么整个集群就能够正常对外服务
对于来自客户端的每个更新请求,ZooKeeper都会分配一个全局唯一的递增编号,这个编号反映了所有事务操作的先后顺序
ZooKeeper一般以集群的方式对外提供服务,一个集群包含多个节点,每个节点对应一台ZooKeeper服务器,所有的节点共同对外提供服务,整个集群环境对分布式数据一致性提供了全面的支持,具体包括以下五大特性:
从同一个客户端发起的请求,最终将会严格按照其发送顺序进入ZooKeeper中
所有请求的响应结果在整个分布式集群环境中具备原子性,即要么整个集群中所有机器都成功的处理了某个请求,要么就都没有处理,绝对不会出现集群中一部分机器处理了某一个请求,而另一部分机器却没有处理的情况
无论客户端连接到ZooKeeper集群中哪个服务器,每个客户端所看到的服务端模型都是一致的,不可能出现两种不同的数据状态,因为ZooKeeper集群中每台服务器之间会进行数据同步
一旦服务端数据的状态发送了变化,就会立即存储起来,除非此时有另一个请求对其进行了变更,否则数据一定是可靠的
当某个请求被成功处理后,ZooKeeper仅仅保证在一定的时间段内,客户端最终一定能从服务端上读取到最新的数据状态,即ZooKeeper保证数据的最终一致性
在分布式系统中,集群中每台机器都有自己的角色,ZooKeeper没有沿用传统的Master/Slave模式(主备模式),而是引入了Leader、Follower和Observer三种角色
集群通过一个Leader选举过程从所有的机器中选举一台机器作为”Leader”,Leader能为客户端提供读和写服务
Leader服务器是整个集群工作机制的核心,主要工作:
顾名思义,Follower是追随者,主要工作:
Observer是ZooKeeper自3.3.0版本开始引入的一个全新的服务器角色,充当一个观察者角色,工作原理和Follower基本是一致的,和Follower唯一的区别是Observer不参与任何形式的投票
所以Observer可以在不影响写性能的情况下提升集群的读性能
ZooKeeper并非采用经典的分布式一致性协议 - Paxos,而是参考了Paxos设计了一种更加轻量级的支持崩溃可恢复的原子广播协议-Zab(ZooKeeper Atomic Broadcast)。
ZAB协议分为两个阶段 - Leader Election(领导选举)和Atomic Broadcast(原子广播)
当集群启动时,会选举一台节点为Leader,而其他节点为Follower,当Leader节点出现网络中断、崩溃退出与重启等异常情况,ZAB会进入恢复模式并选举产生新的Leader服务器,当集群中已有过半机器与该Leader服务器完成数据状态同步,退出恢复模式
当领导选举完成后,就进入原子广播阶段。此时集群中已存在一个Leader服务器在进行消息广播,当一台同样遵循ZAB协议的服务器启动后加入到集群中,新加的服务器会自动进入数据恢复阶段
在ZooKeeper中,事务是指能够改变ZooKeeper服务器状态的请求,一般指创建节点、更新数据、删除节点以及创建会话操作
为了保证事务请求被顺序执行,从而确保ZooKeeper集群的数据一致性,所有的事务请求必须由Leader服务器处理,ZooKeeper实现了非常特别的事务请求转发机制:
所有非Leader服务器如果接收到来自客户端的事务请求,必须将其转发给Leader服务器来处理
在分布式系统中,事务请求可能存在依赖关系,如变更C需要依赖变更A和变更B,这样就要求ZAB协议能够保证如果一个状态变更成功被处理了,那么其所有依赖的状态变更都应该已经提前被处理掉了。
在ZooKeeper中对每一个事务请求,都会为其分配一个全局唯一的事务ID,使用ZXID表示,通常是一个64位的数字。每一个ZXID对应一次事务,从这些ZXID可以间接识别出ZooKeeper处理这些事务请求的全局顺序
ZooKeeper内部拥有一个树状的内存模型,类似文件系统,只是在ZooKeeper中将这些目录与文件系统统称为ZNode,ZNode是ZooKeeper中数据的最小单元,每个ZNode上可以保存数据,还可以挂载子节点,因此构成了一个层次化的命名空间
ZooKeeper中使用斜杠(/)分割的路径表示ZNode路径,斜杠(/)表示根节点
在ZooKeeper中,每个数据节点ZNode都是有生命周期的,其生命周期的长短取决于ZNode的节点类型
为了有效保障ZooKeeper中数据的安全,避免因误操作而带来数据随意变更导致分布式系统异常,ZooKeeper提供了一套完善的ACL(Access Contro List)权限控制机制来保障数据的安全。
可以从三个方面理解ACL机制,分别是:权限模式(Scheme)、授权对象(ID)和权限(Permission),通常使用”scheme:id:permission”来标识一个有效的ACL信息
每个数据节点ZNode除了存储数据内容外,还存储了数据节点本身的一些状态信息
ZooKeeper为数据节点引入版本的概念,对个数据节点都具有三种类型的版本信息,对数据节点的任何更新操作都会引起版本号的变化
在分布式系统中,在运行过程中往往需要保证数据访问的排他性。Java并发中是实现了对CAS的指令支持,即对于值V,每次更新前都会比对其值是否是预期值A,只有符合预期,才会将V原子化的更新到新值B
而ZooKeeper每个节点都有数据版本的概念,在调用更新操作的时候,先从请求中获取当前请求的版本version,同时获取服务器上该数据最新版本currentVersion,如果无法匹配,就无法更新成功,这样可以有效避免一些分布式更新的并发问题
在ZooKeeper中,引入Watcher机制来实现分布式数据的发布/订阅功能。ZooKeeper允许客户端向服务器注册一个Watcher监听,当服务器的一些指定事件触发了这个Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能
Watcher机制为以下三个过程:
在创建一个ZooKeeper客户端对象实例时,可以向构造方法中传入一个Watcher,这个Watcher将作为整个ZooKeeper会话期间的默认Watcher,一致保存在客户端,并向ZooKeeper服务器注册Watcher
客户端并不会把真实的Watcher对象传递到服务器,仅仅只是在客户端请求中使用boolean类型属性进行标记,降低网络开销和服务器内存开销
服务端执行数据变更,当Watcher监听的对应数据节点的数据内容发生变更,如果找到对应的Watcher,会将其提取出来,同时从管理中将其删除(说明Watcher在服务端是一次性的,即触发一次就失效了),触发Watcher,向客户端发送通知
客户端获取通知,识别出事件类型,从相应的Watcher存储中去除对应的Watcher(说明客户端也是一次性的,即一旦触发就会失效)
Session是指客户端连接 - 客户端和服务器之间的一个TCP长连接
Session是ZooKeeper中的会话实体,代表了一个客户端会话,其包含4个属性:
为了保证客户端会话的有效性,客户端会在会话超时时间范围内向服务器发送PING请求来保持会话的有效性,即心跳检测。
服务器接收到客户端的这个心跳检测,就会重新激活对应的客户端会话
服务器的超级检查线程会在指定时间点进行检查,整理出一些已经过期的会话后,就要开始进行会话清理了:
当客户端和服务器之间网络连接断开,客户端会自动进行反复的重连,直到最终成功连接上ZooKeeper集群中的一台机器
《从Paxos到Zookeeper 分布式一致性原理与实践》
《轻量级微服务架构(上册)》