Zookeeper简介
Zookeeper本身是由Yahoo!开发的后来后来贡献给了Apache的一套用于进行分布式协调的框架,原本是ApacheHadoop的子组件,后来独立出来成为了顶级的项目。Zookeeper不仅仅支持Hadoop,还支持绝大部分的分布式集群。
Zookeeper在配置的时候最好是奇数台。
Zookeeper是一个开源、可以提供分布式协调服务。提供了统一配置、统一命名、分布式锁等功能。
- 统一配置:就是当hadoop集群启动的时候,我们在Zookeeper创建一个节点,保存集群的配置信息。hadoop所有节点对他进行监控,如果发现Zookeeper节点的信息发生了变化,可以将变化后最新的信息同步到各个hadoop节点上去。
- 统一命名:如果我们启动hadoop集群,那么我们会在Zookeeper集群创建一个hadoop节点。如果启动kafka集群,那么就会创建一个kafka集群,集群就会有一个统一的名称。
Zookeeper配置文件
在zookeeper目录下有conf文件夹,找到zoo.cfg


Zookeeper分布式锁原理
核心思想:当客户端要获取锁,则创建节点,使用完锁,则删除该节点。
- 客户端获取锁时,在lock节点下创建临时顺序节点。
- 然后获取lock下面的所有子节点,客户端获取到所有的子节点之后,如果发现自己创建的子节点序号最小,那么就认为该客户端获取到了锁。使用完锁后,将该节点删除。
- 如果发现自己创建的节点并非lock所有子节点中最小的,说明自己还没有获取到锁,此时客户端需要找到比自己小的那个节点,同时对其注册事件监听器,监听删除事件。
- 如果发现比自己小的那个节点被删除,则客户端的Watcher会收到相应通知,此时再次判断自己创建的节点是否是lock子节点中序号最小的,如果是则获取到了锁,如果不是则重复以上步骤继续获取到比自己小的一个节点并注册监听。

Zookeeper本质上是一个分布式的小文件存储系统。
Zookeeper 的路径很像Linux的目录,但是他们在Zookeeper中不叫目录,都叫节点。所以他是一个树状结构,每个节点都是一个Znode节点,这个树就叫做Znode树,根节点为/。
每一个节点都必须存储数据,这个数据可以是对节点的描述信息或者是集群的统一配置信息。
Zookeeper主要用来协调服务的,而不是用来存储业务数据的,所以不要放比较大数据在Znode上,Zookeeper给出的上限时每个节点的数据最大1M。
Zookeeper的树状结构是维系在内存中的,即每个节点中的数据是维系在内存中,这样做的目的是方便快速查找,同时Zookeeper的树状结构也会以快照的形式维系在磁盘上,在磁盘上存储位置由dataDir和dataLogDir属性来决定,这两个位置都是在zoo.cfg中配置的。

在Zookeeper中,会将每一次的写操作,比如创建节点、删除节点、修改节点,看作是一个事务,会为每一个事务分配一个全局递增的编号,这个编号被称为事务id,简写为Zxid。
节点类型
- 持久节点(PERSISTENT)
- 客户端断开节点还在
- create /test
- 临时节点(EPHEMERAL)
- 客户端断开节点消失
- create -e /test
- 持久顺序节点(PERSISTENT_SEQUENTIAL)
- 创建带有事务编号的节点,永久存在
- create -s /test
- 临时顺序节点(EPHEMERAL_SEQUENTIAL)
- 创建带有事务编号的节点,客户端断开节点消失
- create -s -e /test
每个持久节点都可以挂载子节点,但是临时节点不能挂载子节点。
每个节点的路径都是唯一的,所以基于次特点,可以做集群的统一命名。
常用命令
Zookeeper 服务端常用命令
启动服务
./zkServer.sh start
停止服务
./zkServer.sh stop
查看服务状态
./zkServer.sh status
重启服务
./zkServer.sh restart
Zookeeper 客户端常用命令
连接客户端
./zkCli.sh –server ip:port
quit
断开连接
help
查看命令帮助
ls
显示指定目录下节点
ls /test
-s 查看节点详细信息,具体信息如下:
- czxid:节点被创建的事务ID
- ctime: 创建时间
- mzxid: 最后一次被更新的事务ID
- mtime: 修改时间
- pzxid:子节点列表最后一次被更新的事务ID
- cversion:子节点的版本号,子节点个数的变化次数
- dataversion:数据版本号, 节点数据的变化次数
- aclversion:权限版本号
- ephemeralOwner:用于临时节点,代表临时节点的事务ID,等于sessionid,如果为持久节点则为0
- dataLength:节点存储的数据的长度
- numChildren:当前节点的子节点个数
ls /demo04 watch 监听demo04下子节点的变化,如果子节点发生变化就会触发监听机制。触发监听后就可以get获取相关信息,进行同步,就可以实现统一配置。
create
创建节点,create /节点path value
-e 创建临时节点(ephemeral)
-s 创建顺序节点(sequential)
get(ls2)
获取节点值,get /节点path
get /demo04 watch 监听demo04数据的变化
-s 获取节点详细信息
set
设置节点值,set /节点path value
delete
删除单个节点,delete /节点path
如果有子节点,无法使用delete删除
deleteall(rmr)
删除带有子节点的节点,deleteall /节点path
Zookeeper Watch机制
zookeeper作为一款成熟的分布式协调框架,订阅-发布功能是很重要的一个。所谓订阅功能,其实说白了就是观察者模式。观察者会订阅一些感兴趣的主题,然后这些主题一旦变化了,就会自动通知到这些观察者。
zookeeper的订阅发布也就是watch机制,是一个轻量级的设计。因为它采用了一种推拉结合的模式。一旦服务端感知主题变了,那么只会发送一个事件类型和节点信息给关注的客户端,而不会包括具体的变更内容,所以事件本身是轻量级的,这就是所谓的“推”部分。然后,收到变更通知的客户端需要自己去拉变更的数据,这就是“拉”部分。watch机制分为添加数据和监听节点。
Zookeeper中所有读操作(getData(),getChildren(),exists())都可以设置Watch选项。Watch事件具有one-time trigger(一次性触发)的特性,如果Watch监视的Znode有变化,那么就会通知设置该Watch的客户端。
客户端在Znode设置了Watch时,如果Znode内容发生改变,那么客户端就会获得Watch事件。例如:客户端设置getData(“/znode1”, true)后,如果/znode1发生改变或者删除,那么客户端就会得到一个/znode1的Watch事件,但是/znode1再次发生变化,那客户端是无法收到Watch事件的,除非客户端设置了新的Watch。
事件类型(znode节点相关的)
针对的是你所观察的一个节点而言的
- EventType.NodeCreated 【节点创建】
- EventType.NodeDataChanged 【节点数据发生变化】
- EventType.NodeChildrenChanged 【这个节点的子节点发生变化】
- EventType.NodeDeleted 【删除当前节点】
状态类型(是跟客户端实例相关的)
ZooKeeper集群跟应用服务之间的状态的变更
- KeeperState.Disconnected 【没有连接上】
- KeeperState.SyncConnected 【连接上】
- KeeperState.AuthFailed 【认证失败】
- KeeperState.Expired 【过期】
watcher的特性
- 一次性:对于ZooKeeper的watcher,你只需要记住一点,ZooKeeper有watch事件,是一次性触发的,当watch监视的数据发生变化时,通知设置该watch的client,即watcher,由于ZooKeeper的监控都是一次性的,所以每次必须设置监控;
- 客户端串行执行:客户端watcher回调的过程是一个串行同步的过程,这为我们保证了顺序,同时需要开发人员注意一点,千万不要因为一个watcher的处理逻辑影响了整个客户端的watcher回调;
- 轻量:WatchedEvent是ZooKeeper整个Watcher通知机制的最小通知单元,整个结构只包含三个部分:通知状态、事件类型和节点路径。也就是说Watcher通知非常的简单,只会告知客户端发生了事件而不会告知其具体内容,需要客户端自己去进行获取,比如NodeDataChanged事件,ZooKeeper只会通知客户端指定节点的数据发生了变更,而不会直接提供具体的数据内容。
Zookeeper选举
Leader选举
- Serverid:服务器ID
- 比如有三台服务器,编号分别是1,2,3。
- 编号越大在选择算法中的权重越大。
- Zxid:数据ID
- 服务器中存放的最大数据ID,值越大说明数据越新,在选举算法中数据越新权重越大。
- 在Leader选举的过程中,如果某台ZooKeeper获得了超过半数的选票,则此ZooKeeper就可以成为Leader了。
Leader选举机制
- 在选举刚开始的时候,每一个节点都会进入选举状态,并且都会推荐自己成为leader,然后将自己的选举信息发送给其他的节点
- 节点之间进行两两比较,经过多轮比较之后,最终胜出的节点成为leader
- 选举信息
- 自己所拥有的最大事务id – Zxid
- 自己的选举id – myid
- 比较原则
- 先比较两个节点的最大事务id,谁大谁赢
- 如果最大事务id一致,则比较myid,谁大谁赢
- 过半性,经过多轮比较之后,一个节点如果胜过了一半及以上的节点,则这个节点就会成为leader 。
- 在Zookeeper集群中,一旦选举出来leader,那么新添的节点的事务id或者myid是多大,都只能成为follower
- 在Zookeeper集群中,当leader宕机之后,会自动的重新选举出一个新的leader,所以不存在单点故障
- 如果在Zookeeper集群中出现了2个及以上的leader,则这种现象称之为脑裂
- 脑裂产生的原因
- 集群产生了分裂
- 分裂之后进行了选举
- 脑裂产生的原因
- 如果一个Zookeeper集群中,存活(能够相互通信)的节点个数不足一半,则剩余的存活节点则停止服务(对外停止服务,对内停止投票)
- 在Zookeeper集群中,会对每次选举出来的leader分配一个唯一的全局递增的编号,称之为epochid。如果Zookeeper集群中存在了多个leader,那么会自动的将epochid较低的节点切换为follower状态
集群选举特性
- 3个节点的集群,从服务器挂掉,集群正常
- 3个节点的集群,2个从服务器都挂掉,主服务器也无法运行。因为可运行的机器没有超过集群总数量的半数。
- 当集群中的主服务器挂了,集群中的其他服务器会自动进行选举状态,然后产生新得leader
- 当领导者产生后,再次有新服务器加入集群,不会影响到现任领导者。
节点状态
- Looking – 选举状态
- follower – 追随者
- leader – 领导者
- observer – 观察者
Zookeeper 集群角色
在ZooKeeper集群服中务中有三个角色
Leader 领导者
- 处理事务请求
- 集群内部各服务器的调度者
Follower 跟随者
- 处理客户端非事务请求,转发事务请求给Leader服务器
- 参与Leader选举投票
Observer 观察者
不参与投票也不参与选举的follower,会监听投票的结果,可以提高选举和操作效率。
- 处理客户端非事务请求,转发事务请求给Leader服务器
观察者的数量的多少不影响投票的性能。因此观察者不是Zookeeper集群整体的主要组件。 因此如果观察者产生故障或者从集群断开连接都不会影响Zookeeper服务的可用性
对于当前的Zookeeper架构而言,如果添加了更多的投票成员,则会导致写入性能下降:一个写入操作要求共识协议至少是整体的一半,因此投票的成本随着投票者越多会显著增加
在实际开发过程中,如果集群规模比较大,受网络波动影响的考虑,一般会把集群的90%-97%的节点设置为observer。
观察者不参与投票,它只监听投票的结果,但是观察者可以和追随者一样运行 ,即客户端可能链接他们并发送读取和写入请求。 观察者像追随者一样转发这些请求到领导者,而他们只是简单的等待监听投票的结果
在实际使用中,观察者可以连接到比追随者更不可靠的网络。事实上,观察者可以用于从其他数据中心和Zookeeper服务通信。观察者的客户端会看到快速的读取,因为所有的读取都在本地,并且写入导致最小的网络开销,因为投票协议所需的消息数量更小
配置方法:
找到zookeeper目录中conf下的zoo.cfg添加以下内容就可以了,只改observer对应的服务器就可以。

ZAB协议(Zookeeper Atomic Broadcast)
ZAB(Zookeeper Atomic Broadcast)协议是为分布式协调服务ZooKeeper专门设计的一种支持崩溃恢复的原子广播协议
ZAB协议是一种特别为ZooKeeper设计的崩溃可恢复的原子消息广播算法。这个算法是一种类2PC算法,在2PC基础上做的改进
2PC
2PC算法就是两阶段提交算法,核心思想:一票否决。因为如果有一台因为网络问题没有回复,那么就不能通过提案,所以2PC算法效率比较低。

由于2PC算法效率不高,所以引入Paxos算法,核心思想:过半性
Paxos
Paxos算法引入了一个leader角色,所有的follower不操作数据,将操作同步到leader上,在leader上进行操作。
Paxos算法没有解决单点故障问题,和follower宕机数据恢复问题。ZAB算法解决了这些问题。
ZAB
ZAB协议包括两种基本模式,分别是:
- 消息原子广播(保证数据一致性)
- 崩溃恢复
消息原子广播
在ZooKeeper中,主要依赖ZAB协议来实现分布式数据一致性,基于该协议,ZooKeeper实现了一种主备模式的系统架构来保持集群中各副本之间数据的一致性,实现分布式数据一致性的这一过程称为消息广播(原子广播)。
ZAB协议的消息广播过程使用的是原子广播协议,类似于一个二阶段提交过程。但是相较于2PC算法,不同的是ZAB协议引入了过半性思想。

ZooKeeper使用一个单一的主进程(leader服务器)来接收并处理客户端的所有事务请求,并采用ZAB的原子广播协议,将服务器数据的状态变更以事务Proposal的形式广播到所有的副本进程(follower或observer)上去。即:所有事务请求必须由一个全局唯一的服务器来协调处理,这样的服务器被称为leader服务器,而余下的其他服务器则成为follower服务器或observer。
具体流程:
- 客户端的事务请求,leader服务器会先将该事务写到本地的log文件中
- leader服务器会为这次请求生成对应的事务Proposal并且为这个事务Proposal分配一个全局递增的唯一的事务ID,即Zxid
- leader服务器会为每一个follower服务器都各自分配一个单独的队列,将需要广播的事务Proposal依次放入队列中,发送给每一个follower
- 每一个follower在收到队列之后,会从队列中依次取出事务Proposal,写到本地的事务日志中。如果写成功了,则给leader返回一个ACK消息
- 当leader服务器接收到半数的follower的ACK相应之后,就会广播一个Commit消息给所有的follower以通知其进行事务提交,同时leader自身也进行事务提交
- leader在收到Commit消息后完成事务提交
在这种简化了的二阶段提交模型下,是无法处理Leader服务器崩溃退出而带来的数据不一致问题的,因此在ZAB协议中添加了另一个模式,即采用崩溃恢复模式来解决这个问题。
整个消息广播协议是基于具有FIFO特性的TCP协议来进行网络通信的,因此能够很容易地保证消息广播过程中消息接收与发送的顺序性。
崩溃恢复
- 当leader服务器出现崩溃、重启等场景,或者因为网络问题导致过半的follower不能与leader服务器保持正常通信的时候,Zookeeper集群就会进入崩溃恢复模式
- 进入崩溃恢复模式后,只要集群中存在过半的服务器能够彼此正常通信,那么就可以选举产生一个新的leader
- 每次新选举的leader会自动分配一个全局递增的编号,即epochid
- 当选举产生了新的leader服务器,同时集群中已经有过半的机器与该leader服务器完成了状态同步之后,ZAB协议就会退出恢复模式。其中,所谓的状态同步是指数据同步,用来保证集群中存在过半的机器能够和leader服务器的数据保持一致
- 当集群中已经有过半的follower服务器完成了和leader服务器的状态同步,那么整个服务框架就可以进入消息广播模式了
- 当一台同样遵守ZAB协议的服务器启动后加入到集群中时,如果此时集群中已经存在一个Leader服务器在负责进行消息广播,那么新加入的服务器就会自觉地进入数据恢复模式:找到leader所在的服务器,并与其进行数据同步,然后一起参与到消息广播流程中
follower数据恢复的时候,在没有同步成功之前,这个follower是不对外提供服务的,数据完全恢复成功之后,才会对外提供服务。
如果事务还没有commit,leader宕机了。那么新选举的leader会让自己和所有节点将未正常commit的记录删掉。
ZAB协议是基于2PC算法和Paxos算法改进得来,解决了2PC一票否决效率低,Paxos算法leader宕机,follower宕机之后重启数据怎么恢复的问题。
Zookeeper特性
- 数据一致性:客户端不论连接到哪个Zookeeper节点上,展示给它都是同一个视图,即查询的数据都是一样的。这是Zookeeper最重要的性能,主要通过ZAB协议保证。
- 原子性:对于事务决议的更新,只能是成功或者失败两种可能,没有中间状态。 要么都更新成功,要么都不更新。即,要么整个集群中所有机器都成功应用了某一事务,要么都没有应用,一定不会出现集群中部分机器应用了改事务,另外一部分没有应用的情况
- 可靠性:一旦Zookeeper服务端成功的应用了一个事务,并完成对客户端的响应,那么该事务所引起的服务端状态变更将会一直保留下来,除非有另一个事务又对其进行了改变
- 实时性:Zookeeper保证客户端将在非常短的时间间隔范围内获得服务器的更新信息,或者服务器失效的信息,或者指定监听事件的变化信息。(前提条件是:网络状况良好)
- 顺序性:如果在一台服务器上消息a在消息b前发布,则在所有服务器上消息a都将在消息b前被发布。客户端在发起请求时,都会跟一个递增的命令号,根据这个机制,Zookeeper会确保客户端执行的顺序性。底层指的是Zxid
- 过半性:Zookeeper集群必须有半数以上的机器存活才能正常工作。因为只有满足过半性,才能满足选举机制选出leader。因为只有过半,在做事务决议时,事务才能更新。所以一般来说,Zookeeper集群的数量最好是奇数个
- 过半选举
- 过半存活
- 过半操作