简介
Zookeeper 是一个分布式服务协调框架,可以用来维护分布式配置中心,服务注册中心,实现分布式锁等。在 Hbase、Hadoop、kafka 等项目中都有广泛的应用
概念解释:Zookeeper 是一个分布式协调服务,就是为用户的分布式应用程序提供协调服务的框架
zookeeper是为别的分布式程序服务的zookeeper本身就是一个分布式程序(只要有半数以上节点存活,它就能正常服务)zookeeper所提供的服务涵盖:主从协调、服务器节点动态上下线、统一配置管理、分布式共享锁、统一名称服务等- 虽然说可以提供各种服务,但是
zookeeper在底层其实只提供了两个功能:1.管理(存储,读取)用户程序提交的数据2.并为用户程序提供数据节点监听服务
zookeeper 节点类型
每个节点在 zookeeper 中叫做 znode,并且其有一个唯一的路径标识
Zookeeper 是用类似资源文件目录的方式来管理节点,每个节点可以存储数据。ZooKeeper 有四种不同类型的节点:
PERSISTENT:持久化节点,一旦创建后,即使客户端与zookeeper断开了连接,该节点依然存在PERSISTENT_SEQUENTIAL:持久化顺序节点,一旦创建后,即使客户端与zookeeper断开了连接,该节点依然存在,并且节点自动按照顺序编号EPHEMERAL:临时节点,当客户端与zookeeper断开连接之后,该节点就被删除EPHEMERAL_SEQUENTIAL:临时顺序节点,当客户端与zookeeper断开连接之后,该节点就被删除,并且节点自动按照顺序编号
zookeeper 的每一个节点,都是一个天然的顺序发号器。在每一个节点下面创建子节点时,只要选择的节点类型是顺序的(持久化顺序节点或临时顺序节点)类型,那么,在新的子节点后面会加上一个次序编号,这个次序编号,是上一个生成的次序编号加 1

zookeeper 会话 session
- 一个客户端连接一个会话,由
zookeeper分配唯一会话id - 客户端以特定的时间间隔发送心跳以保持会话有效,即配置文件
zoo.cfg的配置参数tickTime - 超过会话超时时间未收到客户端的心跳,则判定客户端服务器宕机。超时时间默认为
2倍的tickTime - 会话中的请求按
FIFO(先进先出) 顺序执行
zookeeper 特性
- 全局数据一致:集群中每个服务器保存一份相同的数据副本,
client无论连接到哪个服务器,展示的数据都是一致的,这是最重要的特征 - 可靠性:如果消息被其中一台服务器接受,那么将被所有的服务器接受
- 顺序性:包括全局有序和偏序两种:全局有序是指如果在一台服务器上消息
a在消息b前发布,则在所有Server上消息a都将在消息b前被发布;偏序是指如果一个消息b在消息a后被同一个发送者发布,a必将排在b前面 - 数据更新原子性:一次数据更新要么成功(半数以上节点成功),要么失败,不存在中间状态
- 实时性:
Zookeeper保证客户端将在一个时间间隔范围内获得服务器的更新信息,或者服务器失效的信息
zookeeper 集群角色

Leader:它是zookeeper集群工作的核心。事务请求(写数据)的唯一调度者,保证集群事务处理的顺序性。它是集群内部各个服务器的调度者。事务:对于create,setData,delete等有写操作的请求,则需要统一转发给leader处理,leader需要决定编号、执行操作,这个过程称为一个事务Follower:处理客户端非事务(读操作)请求,转发事务请求给Leader; 参与集群Leader选举投票- 此外,针对访问量比较大的
zookeeper集群,还可新增观察者角色。Observer:观察者角色,观察Zookeeper集群的最新状态变化并将这些状态同步过来,其对于非事务请求可以进行独立处理,对于事务请求,则会转发给Leader服务器进行处理。不会参与任何形式的投票只提供非事务服务,通常用于在不影响集群事务 处理能力的前提下提升集群的非事务处理能力
Zookeeper Watcher
zookeeper 提供了分布式数据发布/订阅功能,一个典型的发布/订阅模型系统定义了一种一对多的订阅关系。能让多个订阅者同时监听某一个主题对象,当这个主题对象自身状态变化时,会通知所有的订阅者,使他们能做出相应的处理
在 zookeeper 中,引入了 Watcher 机制来实现这种分布式的通知功能。zookeeper 允许客户端向服务端注册一个 Watcher 监听,当服务端的一些事件触发了这个 Watcher,那么就会向指定客户端发送一个事件通知来实现分布式的通知功能
触发事件种类很多,如节点的创建,节点的删除,节点的改变,子节点改变等
总的来说,可以概括 Watcher 的实现由一下过程,
- 客户端向服务端注册
Watcher - 服务端事件触发
Watcher - 客户端回调
Watcher得到事件触发情况
Watch 机制特点
- 一次性触发:事件发生触发监听,一个
watcher event就会被发送到设置监听的客户端, 这种效果是一次性的,后续再次发生同样的事件,不会再次触发 - 事件封装:
ZooKeeper使用WatchedEvent对象来封装服务端事件并传递。WatchedEvent包含了每一个事件的三个基本属性: 通知状态(keeperState),事件类型(EventType)和节点路径(path) event异步发送:watcher的通知事件从服务端发送到客户端是异步的- 先注册再触发:
Zookeeper中的watch机制,必须客户端先去服务端注册监听,这样事件发 送才会触发监听,通知给客户端
Zookeeper Watcher 示例
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.9</version>
</dependency>
public static void main(String[] args) throws Exception {
// 初始化 ZooKeeper 实例(zk 地址、会话超时时间,与系统默认一致、watcher)
ZooKeeper zk = new ZooKeeper("node-1:2181,node-2:2181", 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("事件类型为:" + event.getType());
System.out.println("事件发生的路径:" + event.getPath());
System.out.println("通知状态为:" +event.getState());
}
});
zk.create("/myGirls", "性感的".getBytes("UTF-8"), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
zk.close();
}
public static void main(String[] args) throws Exception {
// 初始化 ZooKeeper 实例(zk 地址、会话超时时间,与系统默认一致、watcher)
ZooKeeper zk = new ZooKeeper("node-21:2181,node-22:2181", 30000, new Watcher() {
@Override
public void process(WatchedEvent event) {
System.out.println("事件类型为:" + event.getType());
System.out.println("事件发生的路径:" + event.getPath());
System.out.println("通知状态为:" +event.getState());
}
});
// 创建一个目录节点
zk.create("/testRootPath", "testRootData".getBytes(), Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
// 创建一个子目录节点
zk.create("/testRootPath/testChildPathOne", "testChildDataOne".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath",false,null)));
// 取出子目录节点列表
System.out.println(zk.getChildren("/testRootPath",true));
// 修改子目录节点数据
zk.setData("/testRootPath/testChildPathOne","modifyChildDataOne".getBytes(),-1);
System.out.println("目录节点状态:["+zk.exists("/testRootPath",true)+"]");
// 创建另外一个子目录节点
zk.create("/testRootPath/testChildPathTwo", "testChildDataTwo".getBytes(), Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT);
System.out.println(new String(zk.getData("/testRootPath/testChildPathTwo",true,null)));
// 删除子目录节点
zk.delete("/testRootPath/testChildPathTwo",-1);
zk.delete("/testRootPath/testChildPathOne",-1);
// 删除父目录节点
zk.delete("/testRootPath",-1); zk.close();
}
ZooKeeper 选举机制
zookeeper 默认的算法是 FastLeaderElection,采用投票数大于半数则胜出的逻辑
- 服务器的
myId:比如有三台服务器,编号分别是1,2,3。 编号越大在选择算法中的权重越大 - 选举状态:
Looking是竞选状态。Following是随从状态,同步leader状态,参与投票。Observing是观察状态,同步leader状态,不参与投票。Leading是领导者状态 - 数据事务的
zxId:服务器中存放的最新数据版本。 值越大说明数据越新,在选举算法中数据越新权重越大 - 逻辑时钟:也叫投票的次数,同一轮投票过程中的逻辑时钟值是相同的。每投完一次票这个数据就会增加,然后与接收到的其它服务器返回的投票信息中的数值相比, 根据不同的值做出不同的判断
全新集群选举机制(新的服务器集群刚开始启动时进行选举)
假设目前有 5 台服务器,每台服务器均没有数据,它们的编号分别是 1,2,3,4,5。按编号依次启动,它们的选择举过程如下:
- 服务器
1启动,给自己投票,然后发投票信息。由于其它机器还没有启动所以它收不到反馈信息,服务器1的状态一直处于Looking(领导者状态) - 服务器
2启动,给自己投票,同时与之前启动的服务器1交换结果,由于服务器2的编号大,所以服务器2胜出。但此时投票数没有大于半数,所以两个服务器的状态依然是Looking(领导者状态) - 服务器
3启动,给自己投票,同时与之前启动的服务器1,2交换结果,由于服务器3的编号大,所以服务器3胜出。此时投票数正好大于半数,所以服务器3成为领导者,服务器1,2成为小弟 - 服务器
4启动,给自己投票,同时与之前启动的服务器1,2,3交换信息, 尽管服务器4的编号大,但之前服务器3已经胜出,所以服务器4只能成为小弟 - 服务器
5启动,后面的逻辑同服务器4成为小弟
总结: 在此种情况下,由于服务器集群刚开始启动,所以是没有数据事务 zxId 的。所以此时的选举机制主要是依据服务器的 myId 来进行的,服务器的 myId 越大,它的权重越大
非全新集群选举机制(该服务器集群已经提供服务一段时间之后,由于某些原因,此时要重新进行选举)
对于运行正常的 zookeeper 集群,中途有机器宕掉,需要重新选举时,选举过程就需要加入数据 ID、服务器 ID 和逻辑时钟
- 数据事务的
zxId:数据新的version越大,数据每次更新都会更新version,zxId越大说明数据越新 - 服务器的
myId:就是我们配置的myid中的值,每个机器一个
逻辑时钟:这个值从 0 开始递增,每次选举对应一个值。 如果在同一次选举中,这个值是一致的,这样选举的标准就变成:
- 逻辑时钟小的选举结果被忽略,重新投
- 统一逻辑时钟后,数据事务的
zxId大的胜出 - 数据事务的
zxId相同的情况下,服务器的myid大的胜出
根据这个规则选出 Leader
会发生 zookeeper 选举机制的情况
- 集群服务器的开始启动
Leader服务器宕掉Follower服务器宕掉,Leader服务器发现已经没有过半的Follower服务器跟随自己了
ZooKeeper 典型应用场景
- 数据发布与订阅(分布式配置中心):发布与订阅模型,即所谓的配置中心,顾名思义就是发布者将数据发布到
ZK节点上,供订阅者动态获取数据,实现配置信息的集中式管理和动态更新。应用在启动的时候会主动来获取一次配置,同时,在节点上注册一个Watcher, 这样一来,以后每次配置有更新的时候,都会实时通知到订阅的客户端,从来达到获取最新配置信息的目的 - 命名服务:在分布式系统中,通过使用命名服务,客户端应用能够根据指定名字来获取资源或服务的地址,提供者等信息。被命名的实体通常可以是集群中的机器,提供的服务地址,远程对象等等——这些我们都可以统称他们为名字(
Name)。其中较为常见的就是一些分布式服务框架中的服务地址列表。通过调用ZK提供的创建节点的API,能够很容易创建一个全局唯一的path,这个path就可以作为 一个名称。阿里开源的分布式服务框架Dubbo中使用ZooKeeper来作为其命名服务,维护全局的服务地址列表 - 分布式锁:分布式锁,这个主要得益于
ZooKeeper保证了数据的强一致性
zookeeper 数据的一致性
zookeeper 集群中各个角色的职责:
Leader:负责处理所有请求,为客户端提供数据的读取和写入的角色Follower:只提供读服务,有机会通过选举成为LeaderObserver:只提供读服务
由以上三种角色的介绍可知,zookeeper 中的所有请求都是交个 Leader 角色来处理的。因此,如果 Leader 服务宕机了,zookeeper 就无法再提供服务了,这就是单点的弊端。而在 zookeeper 集群中,如果 Leader 服务宕机了,那么 Follower 角色能够通过选举成为新的 Leader 角色。那么问题来了,Follower 是如何被选举成为新的 Leader 的? 新的 Leader 又是如何保证数据的一致性的? 这些问题的答案都在于 zookeeper 所用的分布式一致性协议 ZAB
分布式一致性协议 ZAB(原子消息广播协议,保证消息的有序性)
事务概念:zookeeper 作为一个分布式协调服务,需要 Leader 节点去接受外部请求,转化为内部操作(比如创建,修改,删除节点),需要原子性执行的几个操作就组成了事务,这里用 T 代表。zookeeper 要求有序的处理事务,因此给每个事务一个编号,每进来一个事务编号加 1,假设 Leader 节点中进来 n 个事务,可以表示为 T1,T2,T3…Tn。为了防止单点问题导致事务数据丢失,Leader 节点会把事务数据同步到 Follower节点中
事务队列概念:Leader 和 Follower 都会保存一个事务队列,用 L 表示,L=T1,T2,T3…Tn,Leader 的事务队列是接受请求保存下来的,Follower 的事务队列是 Leader 同步过来的,因此 Leader 的事务队列是最新,最全的
任期概念:在 zookeeper 的工作过程中,Leader 节点奔溃,重新选举出新的 Leader 是很正常的事情,所以 zookeeper 的运行历史中会产生多个 Leader,就好比一个国家的历史中会相继出现多为领导人。为了区分各个 Leader,ZAB 协议用一个整数来表示任期,我们假设用 E 表示任务。zookeeper 刚运行时选举出的第一个 Leader 的任期为 E=1;第一个 Leader 奔溃后,下面选举出来的 Leader,任期会加 1,E=2 依次类推。加入任期概念的事务应该表示为 T(E,n)
ZAB 协议是为 zookeeper 专门设计的一种支持奔溃恢复的原子广播协议。虽然它不像 Paxos 算法那样通用,但是它却比 Paxos 算法易于理解。
ZAB 协议主要的作用在于三个方面
- 选举出
Leader
第一:每个Follower广播自己事务队列中最大事务编号maxId
第二:获取集群中其他Follower发出来的maxId,选出最大的maxId所属的Follower,投票给Follower,选它为Leader
第三:统计所有投票,获取投票数超过一半的Follower被推选为Leader - 同步节点之间的状态达到数据一致
第一:各个Follower向Leader发送自己保存的任期E
第二:Leader比较所有的任期,选取最大的E,加1后作为当前的任期E = E + 1
第三:将任期E广播给所有Follower
第四:Follower将任期改为Leader发过来的值,并且返回给Leader事务队列L
第五:Leader从队列集合中选取任期最大的队列,如果有多个队列任期都是最大,则选取事务编号n最大的队列Lmax。将Lmax设置为Leader队列,并且广播给各个Follower
第六:Follower接收队列替换自己的事务队列,并且执行提交队列中的事务
至此各个节点的数据达成一致,zookeeper恢复正常服务 - 数据的广播
第一:Leader节点接收到请求,将事务加入事务队列,并且将事务广播给各个Follower
第二:Follower接收事务并加入到事务队列,然后给Leader发送准备提交请求
第三:Leader接收到半数以上的准备提交请求后,提交事务同时向Follower发送提交事务请求
第四:Follower提交事务


zookeeper 与 CAP 理论
CAP 理论概述
著名的 CAP 理论告诉我们,一个分布式系统不可能同时满足以下三种
- 一致性(
C:Consistency) - 可用性(
A:Available) - 分区容错性(
P:Partition Tolerance)
这三个基本需求,最多只能同时满足其中的两项,因为 P 是必须的,因此往往选择就在 CP或者 AP 中
一致性(C:Consistency)
在分布式环境中,一致性是指数据在多个副本之间是否能够保持数据一致的特性。在一致性的需求下,当一个系统在数据一致的状态下执行更新操作后,应该保证系统的数据仍然处于一致的状态。例如一个将数据副本分布在不同分布式节点上的系统来说,如果对第一个节点的数据进行了更新操作并且更新成功后,其他节点上的数据也应该得到更新,并且所有用户都可以读取到其最新的值,那么这样的系统就被认为具有强一致性(或严格的一致性,最终一致性)
可用性(A:Available)
可用性是指系统提供的服务必须一直处于可用的状态,对于用户的每一个操作请求总是能够在有限的时间内返回结果。“有效的时间内”是指,对于用户的一个操作请求,系统必须能够在指定的时间(即响应时间)内返回对应的处理结果,如果超过了这个时间范围,那么系统就被认为是不可用的
分区容错性(P:Partition Tolerance)
分区容错性约束了一个分布式系统需要具有如下特性:分布式系统在遇到任何网络分区故障的时候,仍然需要能够保证对外提供满足一致性和可用性的服务,除非是整个网络环境都发生了故障
ZooKeeper 保证 CP
- 不能保证每次服务请求的可用性。任何时刻对
ZooKeeper的访问请求能得到一致的数据结果,同时系统对网络分割具备容错性;但是它不能保证每次服务请求的可用性(注:也就是在极端环境下,ZooKeeper可能会丢弃一些请求,消费者程序需要重新请求才能获得结果)。所以说,ZooKeeper不能保证服务可用性 - 进行
leader选举时集群都是不可用。在使用ZooKeeper获取服务列表时,当leader节点因为网络故障与其他节点失去联系时,剩余节点会重新进行leader选举。问题在于,选举leader的时间太长(30 ~ 120s), 且选举期间整个ZooKeeper集群都是不可用的,这就导致在选举期间注册服务瘫痪,虽然服务能够最终恢复,但是漫长的选举时间导致的注册长期不可用是不能容忍的。所以说,ZooKeeper不能保证服务可用性
Eureka 保证 AP
Eureka 看明白了这一点,因此在设计时就优先保证可用性。Eureka 中各个节点都是平等的,几个节点挂掉不会影响正常节点的工作。剩余的节点依然可以提供注册和查询的服务。而 Eureka 的客户端在向某个 Eureka 注册时如果发现连接失败,则会自动切换至其他节点。只要有一台 Eureka 还在,就能保证注册服务可用(保证可用性),只不过查到的信息可能不是最新的(不保证强一致性)
除此之外,Eureka 还有一种自我保护机制,如果在 15 分钟内超过 85% 的节点都没有正常的心跳,那么 Eureka 就认为客户端与注册中心出现了网络故障,此时会出现以下几种情况:
Eureka不再从注册列表中移除因为长时间没有心跳而应该过期的服务Eureka仍然能够接受新服务的注册和查询请求,但是不会同步到其他节点上(既保证当前节点依然可用)- 当网络恢复正常时,当前实例新的注册信息会被同步到其他节点上 因此
Eureka可以很好应对因网络故障导致部分节点失去联系的情况,而不会像Zookeeper那样使整个注册服务瘫痪
最后
以上就是粗心长颈鹿最近收集整理的关于ZooKeeper解读简介zookeeper 节点类型zookeeper 会话 sessionzookeeper 特性zookeeper 集群角色Zookeeper WatcherWatch 机制特点Zookeeper Watcher 示例ZooKeeper 选举机制会发生 zookeeper 选举机制的情况ZooKeeper 典型应用场景zookeeper 数据的一致性zookeeper 与 CAP 理论的全部内容,更多相关ZooKeeper解读简介zookeeper内容请搜索靠谱客的其他文章。
发表评论 取消回复