概述
简介
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
:只提供读服务,有机会通过选举成为Leader
Observer
:只提供读服务
由以上三种角色的介绍可知,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 节点类型zookeeper 会话 sessionzookeeper 特性zookeeper 集群角色Zookeeper WatcherWatch 机制特点Zookeeper Watcher 示例ZooKeeper 选举机制会发生 zookeeper 选举机制的情况ZooKeeper 典型应用场景zookeeper 数据的一致性zookeeper 与 CAP 理论所遇到的程序开发问题。
如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。
发表评论 取消回复