IT评测·应用市场-qidao123.com
标题:
【面试】Zookeeper
[打印本页]
作者:
王國慶
时间:
2025-3-10 12:00
标题:
【面试】Zookeeper
1、ZooKeeper 介绍
ZooKeeper 的核心特性
高可用性:ZooKeeper 通过多节点集群实现高可用性,纵然部门节点故障,服务仍然可用。
一致性:ZooKeeper 利用 ZAB(ZooKeeper Atomic Broadcast)协议包管数据的一致性。
顺序性:ZooKeeper 包管客户端的操纵顺序与请求顺序一致。
高性能:ZooKeeper 的设计目标是高吞吐量和低延迟。
ZNode 的类型
ZooKeeper 的数据模型类似于文件系统的树形结构,每个节点称为 ZNode。ZNode 可以存储数据,而且可以有子节点。
长期(PERSISTENT)节点
:一旦创建就一直存在纵然 ZooKeeper 集群宕机,直到将其删除。
暂时(EPHEMERAL)节点
:暂时节点的生命周期是与 客户端会话(session) 绑定的,会话消失则节点消失 。而且,暂时节点只能做叶子节点 ,不能创建子节点。
长期顺序(PERSISTENT_SEQUENTIAL)节点
:除了具有长期(PERSISTENT)节点的特性之外, 子节点的名称还具有顺序性。比如 /node1/app0000000001 、/node1/app0000000002 。
暂时顺序(EPHEMERAL_SEQUENTIAL)节点
:除了具备暂时(EPHEMERAL)节点的特性之外,子节点的名称还具有顺序性
ZooKeeper 的架构
ZooKeeper 集群通常由多个节点组成,其中一个节点是 Leader,其他节点是 Follower。
Leader:负责处理写请求,并将写操纵广播给 Follower。
Follower:处理读请求,并到场 Leader 选举和写操纵的投票。
ZooKeeper 的核心功能
设置管理
ZooKeeper 可以用于存储和管理分布式系统的设置信息。
示例:HBase 利用 ZooKeeper 存储 RegionServer 的设置。
命名服务
ZooKeeper 可以用于实现分布式系统中的命名服务。
示例:Kafka 利用 ZooKeeper 管理 Broker 的命名和元数据。
分布式锁
ZooKeeper 可以用于实现分布式锁,确保多个节点之间的互斥访问。
示例:利用暂时顺序节点实现分布式锁。
领导者选举
ZooKeeper 可以用于实现分布式系统中的领导者选举。
示例:HBase 利用 ZooKeeper 选举 Master 节点。
服务发现
ZooKeeper 可以用于实现服务发现,动态管理服务的注册和发现。
示例:Dubbo 利用 ZooKeeper 实现服务注册和发现。
2、znode 节点里面的存储
stat
:状态信息。
Stat 类中包含了一个数据节点的全部状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等,如下:
data
: znode 存储业务数据信息。
acl
: 记录客户端对 znode 节点访问权限,如 IP 等。
ZooKeeper 采取 ACL(AccessControlLists)策略来进行权限控制,类似于 UNIX 文件系统的权限控制。对于 znode 操纵的权限,ZooKeeper 提供了以下 5 种:
CREATE : 能创建子节点
READ :能获取节点数据和列出其子节点
WRITE : 能设置/更新节点数据
DELETE : 能删除子节点
ADMIN : 能设置节点 ACL 的权限
其中尤其需要注意的是,CREATE 和 DELETE 这两种权限都是针对 子节点 的权限控制。对于身份认证,提供了以下几种方式:
world : 默认方式,全部用户都可无条件访问。
auth :不利用任何 id,代表任何已认证的用户。
digest :用户名:密码认证方式: username:password 。
ip : 对指定 ip 进行限定
child
: 当前节点子节点引用。
3、znode 节点上监听机制
Watcher 为事件监听器,是 zk 非常重要的一个特性,许多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端注册指定的 watcher ,当服务端符合了 watcher 的某些事件或要求则会向客户端发送事件关照 ,客户端收到关照后找到自己界说的 Watcher 然后实验相应的回调方法 。
可以把 Watcher 理解成客户端注册在某个 Znode 上触发器,当这个 Znode 节点发生厘革时(增删改查),就会触发 Znode 对应注册事件,注册客户端就会收到异步关照,然后做出业务改变。
zookeeper 监听原理
zookeeper的监听事件有四种
nodedatachanged 节点数据改变
nodecreate 节点创建事件
nodedelete 节点删除事件
nodechildrenchanged 子节点改变事件
ZooKeeper Watcher 机制重要包括客户端线程、客户端WatcherManager、Zookeeper 服务器三部门。
客户端向 ZooKeeper 服务器注册 Watcher 同时,会将 Watcher 对象存储在客户端 WatchManager 中。
当 zookeeper 服务器触发 watcher 事件后,会向客户端发送关照, 客户端线程从 WatcherManager 中取出对应Watcher 对象来实验回调逻辑。
4、ZooKeeper 集群部署
为了包管高可用,最好是以集群形态来部署 ZooKeeper,这样只要集群中大部门机器是可用的(能够容忍一定的机器故障),那么 ZooKeeper 本身仍然是可用的。通常 3 台服务器就可以构成一个 ZooKeeper 集群了。ZooKeeper 官方提供的架构图就是一个 ZooKeeper 集群团体对外提供服务。
上图中每一个 Server 代表一个安装 ZooKeeper 服务的服务器。组成 ZooKeeper 服务的服务器都会在内存中维护当前的服务器状态,而且每台服务器之间都互相保持着通信。集群间通过 ZAB 协议来保持数据的一致性。
最典型集群模式: Master/Slave 模式(主备模式)。在这种模式中,通常 Master 服务器作为主服务器提供写服务,其他的 Slave 服务器从服务器通过异步复制的方式获取 Master 服务器最新的数据提供读服务。
ZooKeeper 集群角色
ZooKeeper 集群中的全部机器通过一个 Leader 选举过程 来选定一台称为 “Leader” 的机器,Leader 既可以为客户端提供写服务又能提供读服务。除了 Leader 外,Follower 和 Observer 都只能提供读服务。Follower 和 Observer 唯一的区别在于 Observer 机器不到场 Leader 的选举过程,也不到场写操纵的“过半写乐成”策略,因此 Observer 机器可以在不影响写性能的情况下提拔集群的读性能。
5、ZooKeeper 选举机制
ZooKeeper 的选举机制 是包管其高可用性和一致性的核心部门。ZooKeeper 利用 ZAB(ZooKeeper Atomic Broadcast)协议 来实现领导者选举和数据同步。
选举的触发条件
集群启动:当 ZooKeeper 集群启动时,全部节点会到场选举。
Leader 失效:当 Leader 节点失效时,Follower 节点会重新选举 Leader。
① 选举初始化
当 ZooKeeper 集群启动或 Leader 节点失效时,集群中的节点会进入选举状态。每个节点会实验选举自己为 Leader。
② 投票
每个节点会投票给自己,并将投票信息(包括节点的 ID 和事务 ID)广播给其他节点。节点收到其他节点的投票后,会比力投票信息:
如果收到的投票的事务 ID 更大,则更新自己的投票。
如果事务 ID 雷同,则比力节点 ID,选择更大的节点 ID。
③ 确定 Leader
当某个节点收到超过半数的投票时,该节点被选举为 Leader。Leader 会向其他节点发送确认消息,其他节点确认后成为 Follower。
④ 数据同步
Leader 会将自己的数据状态同步给 Follower,确保集群中的数据一致性。同步完成后,集群进入正常工作状态。
6、作甚集群脑裂
对于一个集群,想要提高这个集群的可用性,通常会采取多机房部署,比如现在有一个由6台zkServer所组成的一个集群,部署在了两个机房:
正常情况下,此集群只会有一个Leader,那么如果机房之间的网络断了之后,两个机房内的zkServer照旧可以相互通信的,如果不考虑过半机制,那么就会出现每个机房内部都将选出一个Leader。
这就相当于本来一个集群,被分成了两个集群,出现了两个“大脑”,这就是脑裂。
对于这种情况,我们也可以看出来,本来应该是统一的一个集群对外提供服务的,现在变成了两个集群同时对外提供服务,如果过了一会,断了的网络忽然联通了,那么此时就会出现标题了,两个集群刚刚都对外提供服务了,数据该怎么合并,数据冲突怎么办理等等标题。
刚刚在阐明脑裂场景时,有一个前提条件就是没有考虑过半机制,以是实际上Zookeeper集群中是不会出现脑裂标题的,而不会出现的原因就跟过半机制有关。
过半机制
举个简单的例子: 如果现在集群中有6台zkServer,也就是说至少要4台zkServer才能选出来一个Leader,才会符合过半机制,才能选出来一个Leader。
以是对于机房1来说它不能选出一个Leader,同样机房2也不能选出一个Leader,这种情况下整个集群当机房间的网络断掉后,整个集群将没有Leader。
如果假设我们现在只有5台机器,也部署在两个机房:
也就是至少要3台服务器才能选出一个Leader,此时机房件的网络断开了,对于机房1来说是没有影响的,Leader依然照旧Leader,对于机房2来说是选不出来Leader的,此时整个集群中只有一个Leader。
以是,我们可以总结得出,有了过半机制,对于一个Zookeeper集群,要么没有Leader,要没只有1个Leader,这样就避免了脑裂标题。
过半机制是怎样防止脑裂现象产生的
ZooKeeper 的过半机制导致不可能产生 2 个 leader,由于少于等于一半是不可能产生 leader 的,这就使得岂论机房的机器怎样分配都不可能发生脑裂。
ZooKeeper 集群为啥最好奇数台
ZooKeeper 集群在宕掉几个 ZooKeeper 服务器之后,如果剩下的 ZooKeeper 服务器个数大于宕掉的个数的话整个 ZooKeeper 才依然可用。假如我们的集群中有 n 台 ZooKeeper 服务器,那么也就是剩下的服务数必须大于 n/2。先说一下结论,2n 和 2n-1 的容忍度是一样的,都是 n-1,大家可以先自己仔细想一想,这应该是一个很简单的数学标题了。
比如假如我们有 3 台,那么最大允许宕掉 1 台 ZooKeeper 服务器,如果我们有 4 台的的时间也同样只允许宕掉 1 台。 假如我们有 5 台,那么最大允许宕掉 2 台 ZooKeeper 服务器,如果我们有 6 台的的时间也同样只允许宕掉 2 台。
综上,何须增长那一个不必要的 ZooKeeper 呢?
7、怎样包管数据一致性
在 ZooKeeper 集群中,全部客户端的请求都是写入到 Leader 进程中的,然后,由 Leader 同步到其他节点,称为 Follower。在集群数据同步的过程中,如果出现 Follower 节点崩溃大概 Leader 进程崩溃时,都会通过 Zab 协议来包管数据一致性。
ZAB 协议的核心头脑
ZAB 协议是一种原子广播协议,用于在分布式系统中实现数据的一致性和可靠性。它的核心头脑包括:
原子广播:确保全部写操纵以雷同的顺序被全部节点吸收和实验。
领导者选举:在集群启动或 Leader 失效时,选举新的 Leader 来和谐写操纵。
数据同步:确保 Leader 和 Follower 之间的数据一致性。
ZAB 协议的工作流程
ZAB 协议的工作流程分为两个阶段:
选举阶段:
当集群启动或 Leader 失效时,ZooKeeper 会进入选举阶段。
通过投票机制选举新的 Leader,确保集群中只有一个 Leader。
广播阶段:
Leader 负责吸收客户端的写请求,并将写操纵广播给全部 Follower。
Follower 吸收到写操纵后,将其应用到本地状态,并向 Leader 发送确认。
当 Leader 收到多数 Follower 的确认后,提交写操纵并关照客户端。
消息广播阶段,ZooKeeper中的一个节点被选为leader节点,它吸收来自客户端的事务提交请求,并将这些请求作为proposal广播给其他follower节点。每个follower节点收到proposal后会进行反馈,leader节点根据收集到的反馈决定是否实验commit操纵。为了包管数据一致性,ZooKeeper利用了quorum选举机制来决定大多数节点上的commit效果。
client端发起请求,读请求由follower和observer直接返回,写请求由它们转发给leader。Leader 首先为这个事务分配一个全局单调递增的唯一事务ID (即 ZXID )。然后发起proposal给follower,Leader 会为每一个 Follower 都各自分配一个单独的队列,然后将需要广播的事务 Proposal 依次放入这些队列中去,而且根据 FIFO策略进行消息发送。每一个 Follower 在吸收到这个事务 Proposal 之后,都会首先将其以事务日志的形式写入到本地磁盘中去,而且在乐成写入后反馈给 Leader 服务器一个 Ack 相应。当 Leader 服务器吸收到超过半数 Follower 的 Ack 相应后,就会广播一个Commit 消息给全部的 Follower 服务器以关照其进行事务提交,同时Leader 自身也会完成对事务的提交。
8、讲一下 zk 分布式锁实现原理吧
实现分布式锁要借助暂时顺序节点和watch,首先我们要有一个长期节点,客户端获取锁就是在长期节点下创建暂时顺序节点。客户端创建的暂时顺序节点创建乐成后会判断节点是不是最小节点,如果是最小节点那么获取锁乐成,否则回去锁失败。如果获取锁失败,则阐明有其他客户端已乐成获得锁,这时间也不需要循环实验去加锁,而是给前一个节点注册一个事件监听器,这个监听器作用就是当前一个节点开释后,也就是节点删除后关照自己让自己获得锁,这样的利益是不会关照到全部的节点去争取锁(避免无效自旋)。以是利用Zookeeper实现的分布式锁是公平锁。
为什么要用暂时顺序节点
暂时节点相比长期节点,最重要的是对会话失效的情况处理不一样,暂时节点会话消失则对应的节点消失。这样的话,如果客户端发生异常导致没来得及开释锁也没关系,会话失效节点自动被删除,不会发存亡锁的标题。
利用 Redis 实现分布式锁的时间,我们是通过逾期时间来避免锁无法被开释导致死锁标题的,而 ZooKeeper 直接利用暂时节点的特性即可。
假设不适用顺序节点的话,全部实验获取锁的客户端都会对持有锁的子节点加监听器。当该锁被开释之后,势必会造成全部实验获取锁的客户端来争取锁,这样对性能不友爱。利用顺序节点之后,只需要监听前一个节点就好了,对性能更友爱。
为什么要设置对前一个节点的监听
同一时间段内,可能会有许多客户端同时获取锁,但只有一个可以获取乐成。如果获取锁失败,则阐明有其他的客户端已经乐成获取锁。获取锁失败的客户端并不会不停地循环去实验加锁,而是在前一个节点注册一个事件监听器。
这个事件监听器的作用是: 当前一个节点对应的客户端开释锁之后(也就是前一个节点被删除之后,监听的是删除事件),关照获取锁失败的客户端(叫醒等待的线程,Java 中的 wait/notifyAll ),让它实验去获取锁,然后就乐成获取锁了。
原生API 分布式锁
package com.example.test.other.zk;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class ZookepeerLock1 {
private final String connectionString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
private final int sessionTimeout = 2000;
private final ZooKeeper zk;
private CountDownLatch countDownLatch = new CountDownLatch(1);
private CountDownLatch waitLatch = new CountDownLatch(1);
private String waitPath;
private String currentMode;
public ZookepeerLock1() throws IOException, InterruptedException, KeeperException {
// 获取连接
zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
@Override
public void process(WatchedEvent watchedEvent) {
// connectLatch 如果连接上zk 可以释放
if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
countDownLatch.countDown();
}
// waitLatch 需要释放
if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
waitLatch.countDown();
}
}
});
// 等待zk正常连接后,往下走程序
countDownLatch.await();
// 判断根节点/locks是否存在
Stat stat = zk.exists("/lockZookeeper", false);
if (stat == null) {
// 创建根节点,这是⼀个完全开放的ACL,持久节点
zk.create("/lockZookeeper", "lockZookeeper".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
}
// 对zk加锁
public void zkLock() {
try {
// 创建对应的临时顺序节点
currentMode =
zk.create("/lockZookeeper/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
// 判断创建的节点是否是序号最小的节点,如果是获取到锁,如果不是,监听他序号前一个节点
List<String> children = zk.getChildren("/lockZookeeper", false);
if (children.size() == 1) {
return;
} else {
//[seq-0000000016, seq-0000000017]
Collections.sort(children);
// 获取节点名称 /locks/seq-0000000017 -> seq-0000000017
String thisNode = currentMode.substring("/lockZookeeper/".length());
// 通过seq-0000000017获取该节点在children集合的位置
int index = children.indexOf(thisNode);
// 判断
if (index == -1) {
System.out.println("数据异常");
} else if (index == 0) {
// 就一个节点,可以获取锁了
return;
} else {
// 需要监听 他前一个结点的变化
waitPath = "/lockZookeeper/" + children.get(index - 1);
zk.getData(waitPath, true, null);
// 等待监听
waitLatch.await();
}
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 解锁
public void unZkLock() {
// 删除节点
try {
zk.delete(currentMode, -1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
复制代码
Curator 分布式锁
package com.example.test.other.zk;
import org.apache.curator.RetryPolicy;
import org.apache.curator.framework.CuratorFramework;
import org.apache.curator.framework.CuratorFrameworkFactory;
import org.apache.curator.framework.recipes.locks.InterProcessLock;
import org.apache.curator.framework.recipes.locks.InterProcessMutex;
import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
import org.apache.curator.retry.ExponentialBackoffRetry;
import org.apache.zookeeper.KeeperException;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
/**
* Description: 1、@Bean单独注解方法时,每次调用方法都是执行方法内的逻辑并返回新创建的对象bean,而且SpringIOC并没有该bean的存在。
* 2、@Bean + @Configuration ,在调用@Bean注解的方法时返回的实例bean是从IOC容器获取的,已经注入的,且是单例的,而不是新创建的。
* 3、@Bean + @Component,虽然@Bean注解的方法返回的实例已经注入到SpringIOC容器中,但是每次调用@Bean注解的方法时,都会创建新的对象实例bean返回,并不会从IOC容器中获取。
* Author: yangjj_tc
* Date: 2023/5/18 13:25
*/
@Configuration
public class ZookeperLock1Test {
private final String ZOOKEEPER_ADDRESS = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
/**
* Description: Author:
* yangjj_tc
* 1、会话连接是异步的,需要自己去处理。比如使用CountDownLatch
* 2、Watch需要重复注册,不然就不能生效
* 3、开发的复杂性还是比较高的
* 4、不支持多节点删除和创建。需要自己去递归
* Date: 2023/5/18 12:48
*/
@Bean
public CuratorFramework getCuratorFramework(){
// 重试策略,重试间隔时间为1秒,重试次数为3次。curator管理了zookeeper的连接,在操作zookeeper的过程中出现连接问题会自动重试。
RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
// 初始化客户端,通过工厂创建连接
// zk地址 会话超时时间,默认60秒 连接超时时间,默认15秒 重试策略
CuratorFramework zkClient = CuratorFrameworkFactory.newClient(ZOOKEEPER_ADDRESS, 5000, 15000, retryPolicy);
// 开始连接
zkClient.start();
return zkClient;
}
public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
// 获得两个客户端
CuratorFramework client1 = new ZookeperLock1Test().getCuratorFramework();
CuratorFramework client2 = new ZookeperLock1Test().getCuratorFramework();
// 可重入锁, 意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。如想重入,则需要使用同一个InterProcessMutex对象。
final InterProcessLock lock1 = new InterProcessMutex(client1, "/lockCurator");
final InterProcessLock lock2 = new InterProcessMutex(client2, "/lockCurator");
// 不可重入锁,区别在于该锁是不可重入的,在同一个线程中不可重入
final InterProcessSemaphoreMutex lock3 = new InterProcessSemaphoreMutex(client1, "/lockCurator");
final InterProcessSemaphoreMutex lock4 = new InterProcessSemaphoreMutex(client2, "/lockCurator");
// 模拟两个线程
new Thread(() -> {
try {
// 线程加锁
lock3.acquire();
lock3.acquire();
System.out.println("线程1获取锁");
// 线程沉睡
Thread.sleep(5 * 1000);
// 线程解锁
lock1.release();
System.out.println("线程1释放了锁");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
// 线程2
new Thread(() -> {
// 线程加锁
try {
lock2.acquire();
System.out.println("线程2获取到锁");
// 线程沉睡
Thread.sleep(5 * 1000);
lock2.release();
System.out.println("线程2释放锁");
} catch (Exception e) {
e.printStackTrace();
}
}).start();
}
}
复制代码
9、Eureka 与 Zk 有什么区别
Eureka采取 AP 模型,优先包管可用性,允许短暂的数据不一致。通过心跳机制和客户端缓存实现最终一致性。ZooKeeper采取 CP 模型,在分区故障时可能牺牲可用性。利用 ZAB 协议包管强一致性。
Eureka是对等架构:Eureka 节点之间是对等的,每个节点都可以吸收注册和查询请求。客户端缓存,客户端会缓存服务注册表,纵然 Eureka 服务器不可用,客户端仍然可以获取服务信息。ZooKeeper主从架构:ZooKeeper 集群中有 Leader 和 Follower 节点,写操纵由 Leader 处理。强一致性:全部节点数据保持一致,客户端总是读取到最新数据。
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/)
Powered by Discuz! X3.4