简介
ZooKeeper是用于维护配置信息,命名,提供分布式同步以及提供组服务的集中式服务。所有这些类型的服务都以某种形式被分布式应用程序使用。每次实施它们时,都会进行很多工作来修复不可避免的错误和竞争条件。由于难以实现这类服务,因此应用程序最初通常会跳过它们,这会使它们在发生更改时变得脆弱并且难以管理。即使部署正确,这些服务的不同实现也会导致管理复杂。
命令集
- 启动 sh zkServer.sh start
- 停止 sh zkServer.sh stop
- 启动客户端 sh zkCli.sh
- 创建 create /zk-book 123
- 列表 ls /
- 读取 get /zk-book
- 更新 set /zk-book 456
- 删除 delete /zk-book
客户端
- 原生api
- zkClient
- Curator
节点特性
持久节点(PERSISTENT)
所谓持久节点,是指在节点创建后,就一直存在,直到有删除操作来主动清除这个节点——不会因为创建该节点的客户端会话失效而消失。
持久顺序节点(PERSISTENT_SEQUENTIAL)
这类节点的基本特性和上面的节点类型是一致的。额外的特性是,在ZK中,每个父节点会为他的第一级子节点维护一份时序,会记录每个子节点创建的先后顺序。基于这个特性,在创建子节点的时候,可以设置这个属性,那么在创建节点过程中,ZK会自动为给定节点名加上一个数字后缀,作为新的节点名。这个数字后缀的范围是整型的最大值。
临时节点(EPHEMERAL)
和持久节点不同的是,临时节点的生命周期和客户端会话绑定。也就是说,如果客户端会话失效,那么这个节点就会自动被清除掉。注意,这里提到的是会话失效,而非连接断开。另外,在临时节点下面不能创建子节点。
临时顺序节点(EPHEMERAL_SEQUENTIAL)
可以用来实现分布式锁
Session
Client与Zookeeper之间的通信,需要创建一个Session,这个Session会有一个超时时间。因为Zookeeper集群会把Client的Session信息持久化,所以在Session没超时之前,client与Zookeeper server的连接可以在各个Zookeeper server之间透明地移动。在实际的应用中,如果client与server之间的通信足够频繁,Session的维护就不需要其他额外的消息了。否则,Zookeeper client会每t/3 ms发一次心跳给server,如果Client 2t/3 ms没收到来自Server的心跳回应,就会切换到一个新的Zookeeper Server上。这里t就是用户配置的Session的超时时间。
Watcher
客户端无法从该事件中获取到对应数据节点的原始数据内容以及变更后的数据内容,而是需要客户端再次主动去重新获取数据-这也是zk watcher机制的一个非常重要的特性。
FIFO
对于每一个Zookeeper客户端而言,所有的操作都是遵循FIFO顺序的,这一特性是由下面两个基本特性来保证的:一是Zookeeper client与server之间的网络通信都是基于TCP,Tcp保证了client/server之间传输包的顺序,二是Zookeeper server执行客户端请求也是严格按照FIFO顺序的。
存储
- 日志文件
- 快照文件
- 内存数据库
zkDatabase,是zk的内存数据库,负责zk的所有会话、DataTree存储和事务日志。zkDatabase会定时向磁盘dump快照数据,同时在zk服务器启动的时候,会通过磁盘上的事务日志和快照数据文件恢复成一个完整的内存数据库。
在每个ZNode上可存储少量数据(默认是1M,可以通过配置修改,通常不建议ZNode上存储大量数据)
Leader处理所有请求,而Follow和Observer可以处理非事务请求,事务请求需要转发给Leader处理,对于每个事务请求,Leader会为其分配一个全局唯一且递增的ZXID。ZXID ,通常是一个64位的数字。每一个 ZXID 对应一次更新操作,从这些 ZXID 中可以间接地识别出 ZooKeeper 处理这些事务操作请求的全局顺序。
数据同步:
在服务器启动阶段,会进行磁盘数据在恢复,完成数据恢复后就会进行Leader选举,一旦选举产生Leader服务器后,就立即开始进行集群间的数据同步。
数据同步过程:
应用
数据发布/订阅
即配置中心,zk采用推拉结合的方式实现。
数据发布与订阅,即所谓的配置中心,顾名思义就是发布者将数据发布到 ZooKeeper 节点上,供订阅者进行数据订阅,进而达到动态获取数据的目的,实现配置信息的集中式管理和动态更新。在我们平常的应用系统开发中,经常会碰到这样的需求:系统中需要使用一些通用的配置信息,例如机器列表信息、数据库配置信息等。这些全局配置信息通常具备以下3个特性。数据量通常比较小。数据内容在运行时动态变化。集群中各机器共享,配置一致。
命名服务
- 类似JNDI
- 分布式全局唯一ID(利用ZK的顺序节点
命名服务也是分布式系统中比较常见的一类场景。在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务,远程对象等等——这些我们都可以统称他们为名字(Name)。其中较为常见的就是一些分布式服务框架(如RPC、RMI)中的服务地址列表。通过在ZooKeepr里创建顺序节点,能够很容易创建一个全局唯一的路径,这个路径就可以作为一个名字。ZooKeeper 的命名服务即生成全局唯一的ID。
分布式协调/通知
ZooKeeper 中特有 Watcher 注册与异步通知机制,能够很好的实现分布式环境下不同机器,甚至不同系统之间的通知与协调,从而实现对数据变更的实时处理。使用方法通常是不同的客户端都对ZK上同一个 ZNode 进行注册,监听 ZNode 的变化(包括ZNode本身内容及子节点的),如果 ZNode 发生了变化,那么所有订阅的客户端都能够接收到相应的Watcher通知,并做出相应的处理。ZK的分布式协调/通知,是一种通用的分布式系统机器间的通信方式。
- mysql 数据复制总线
- 通用的分布式系统间通信方式
- 心跳检测-利用临时节点,减少系统耦合。
机器间的心跳检测机制是指在分布式环境中,不同机器(或进程)之间需要检测到彼此是否在正常运行,例如A机器需要知道B机器是否正常运行。在传统的开发中,我们通常是通过主机直接是否可以相互PING通来判断,更复杂一点的话,则会通过在机器之间建立长连接,通过TCP连接固有的心跳检测机制来实现上层机器的心跳检测,这些都是非常常见的心跳检测方法。下面来看看如何使用ZK来实现分布式机器(进程)间的心跳检测。基于ZK的临时节点的特性,可以让不同的进程都在ZK的一个指定节点下创建临时子节点,不同的进程直接可以根据这个临时子节点来判断对应的进程是否存活。通过这种方式,检测和被检测系统直接并不需要直接相关联,而是通过ZK上的某个节点进行关联,大大减少了系统耦合。
- 工作进度汇报-利用临时节点
在一个常见的任务分发系统中,通常任务被分发到不同的机器上执行后,需要实时地将自己的任务执行进度汇报给分发系统。这个时候就可以通过ZK来实现。在ZK上选择一个节点,每个任务客户端都在这个节点下面创建临时子节点,这样便可以实现两个功能:通过判断临时节点是否存在来确定任务机器是否存活。各个任务机器会实时地将自己的任务执行进度写到这个临时节点上去,以便中心系统能够实时地获取到任务的执行进度。
- 系统调度
使用zk实现分布式系统机器间的通信,不仅能省去大量底层网络通信和协议设计上的重复工作,更为重要的一点是大大降低了系统之间的耦合,能够非常方便地实现异构系统之间的灵活通信。
Master选举
Master 选举可以说是 ZooKeeper 最典型的应用场景了。比如 HDFS 中 Active NameNode 的选举、YARN 中 Active ResourceManager 的选举和 HBase 中 Active HMaster 的选举等。针对 Master 选举的需求,通常情况下,我们可以选择常见的关系型数据库中的主键特性来实现:希望成为 Master 的机器都向数据库中插入一条相同主键ID的记录,数据库会帮我们进行主键冲突检查,也就是说,只有一台机器能插入成功——那么,我们就认为向数据库中成功插入数据的客户端机器成为Master。依靠关系型数据库的主键特性确实能够很好地保证在集群中选举出唯一的一个Master。但是,如果当前选举出的 Master 挂了,那么该如何处理?谁来告诉我 Master 挂了呢?显然,关系型数据库无法通知我们这个事件。但是,ZooKeeper 可以做到!利用 ZooKeepr 的强一致性,能够很好地保证在分布式高并发情况下节点的创建一定能够保证全局唯一性,即 ZooKeeper 将会保证客户端无法创建一个已经存在的 ZNode。也就是说,如果同时有多个客户端请求创建同一个临时节点,那么最终一定只有一个客户端请求能够创建成功。利用这个特性,就能很容易地在分布式环境中进行 Master 选举了。成功创建该节点的客户端所在的机器就成为了 Master。同时,其他没有成功创建该节点的客户端,都会在该节点上注册一个子节点变更的 Watcher,用于监控当前 Master 机器是否存活,一旦发现当前的Master挂了,那么其他客户端将会重新进行 Master 选举。这样就实现了 Master 的动态选举。
分布式锁
- 共享锁
- 排他锁(Exclusive Locks,简称X锁),又称为写锁或独占锁。如果事务T1对数据对象O1加上了排他锁,那么在整个加锁期间,只允许事务T1对O1进行读取和更新操作,其他任何事务都不能在对这个数据对象进行任何类型的操作(不能再对该对象加锁),直到T1释放了排他锁。可以看出,排他锁的核心是如何保证当前只有一个事务获得锁,并且锁被释放后,所有正在等待获取锁的事务都能够被通知到。如何利用 ZooKeeper 实现排他锁?定义锁 ZooKeeper 上的一个 ZNode 可以表示一个锁。例如 /exclusive_lock/lock节点就可以被定义为一个锁。获得锁 如上所说,把ZooKeeper上的一个ZNode看作是一个锁,获得锁就通过创建 ZNode 的方式来实现。所有客户端都去 /exclusive_lock节点下创建临时子节点 /exclusive_lock/lock。ZooKeeper 会保证在所有客户端中,最终只有一个客户端能够创建成功,那么就可以认为该客户端获得了锁。同时,所有没有获取到锁的客户端就需要到/exclusive_lock节点上注册一个子节点变更的Watcher监听,以便实时监听到lock节点的变更情况。释放锁 因为 /exclusive_lock/lock 是一个临时节点,因此在以下两种情况下,都有可能释放锁。当前获得锁的客户端机器发生宕机或重启,那么该临时节点就会被删除,释放锁。正常执行完业务逻辑后,客户端就会主动将自己创建的临时节点删除,释放锁。无论在什么情况下移除了lock节点,ZooKeeper 都会通知所有在 /exclusive_lock 节点上注册了节点变更 Watcher 监听的客户端。这些客户端在接收到通知后,再次重新发起分布式锁获取,即重复『获取锁』过程。