IT评测·应用市场-qidao123.com

标题: 【面试】Zookeeper [打印本页]

作者: 王國慶    时间: 2025-3-10 12:00
标题: 【面试】Zookeeper
1、ZooKeeper 介绍





2、znode 节点里面的存储


Stat 类中包含了一个数据节点的全部状态信息的字段,包括事务 ID(cZxid)、节点创建时间(ctime) 和子节点个数(numChildren) 等等,如下:


3、znode 节点上监听机制

Watcher 为事件监听器,是 zk 非常重要的一个特性,许多功能都依赖于它,它有点类似于订阅的方式,即客户端向服务端注册指定的 watcher ,当服务端符合了 watcher 的某些事件或要求则会向客户端发送事件关照 ,客户端收到关照后找到自己界说的 Watcher 然后实验相应的回调方法 。

可以把 Watcher 理解成客户端注册在某个 Znode 上触发器,当这个 Znode 节点发生厘革时(增删改查),就会触发 Znode 对应注册事件,注册客户端就会收到异步关照,然后做出业务改变。
zookeeper 监听原理
zookeeper的监听事件有四种


ZooKeeper Watcher 机制重要包括客户端线程、客户端WatcherManager、Zookeeper 服务器三部门。
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。
① 选举初始化

② 投票

③ 确定 Leader

④ 数据同步

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 协议的工作流程分为两个阶段:

消息广播阶段,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 分布式锁
  1. package com.example.test.other.zk;
  2. import org.apache.zookeeper.*;
  3. import org.apache.zookeeper.data.Stat;
  4. import java.io.IOException;
  5. import java.util.Collections;
  6. import java.util.List;
  7. import java.util.concurrent.CountDownLatch;
  8. public class ZookepeerLock1 {
  9.     private final String connectionString = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
  10.     private final int sessionTimeout = 2000;
  11.     private final ZooKeeper zk;
  12.     private CountDownLatch countDownLatch = new CountDownLatch(1);
  13.     private CountDownLatch waitLatch = new CountDownLatch(1);
  14.     private String waitPath;
  15.     private String currentMode;
  16.     public ZookepeerLock1() throws IOException, InterruptedException, KeeperException {
  17.         // 获取连接
  18.         zk = new ZooKeeper(connectionString, sessionTimeout, new Watcher() {
  19.             @Override
  20.             public void process(WatchedEvent watchedEvent) {
  21.                 // connectLatch 如果连接上zk 可以释放
  22.                 if (watchedEvent.getState() == Event.KeeperState.SyncConnected) {
  23.                     countDownLatch.countDown();
  24.                 }
  25.                 // waitLatch 需要释放
  26.                 if (watchedEvent.getType() == Event.EventType.NodeDeleted && watchedEvent.getPath().equals(waitPath)) {
  27.                     waitLatch.countDown();
  28.                 }
  29.             }
  30.         });
  31.         // 等待zk正常连接后,往下走程序
  32.         countDownLatch.await();
  33.         // 判断根节点/locks是否存在
  34.         Stat stat = zk.exists("/lockZookeeper", false);
  35.         if (stat == null) {
  36.             // 创建根节点,这是⼀个完全开放的ACL,持久节点
  37.             zk.create("/lockZookeeper", "lockZookeeper".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
  38.         }
  39.     }
  40.     // 对zk加锁
  41.     public void zkLock() {
  42.         try {
  43.             // 创建对应的临时顺序节点
  44.             currentMode =
  45.                     zk.create("/lockZookeeper/" + "seq-", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
  46.             // 判断创建的节点是否是序号最小的节点,如果是获取到锁,如果不是,监听他序号前一个节点
  47.             List<String> children = zk.getChildren("/lockZookeeper", false);
  48.             if (children.size() == 1) {
  49.                 return;
  50.             } else {
  51.                 //[seq-0000000016, seq-0000000017]
  52.                 Collections.sort(children);
  53.                 // 获取节点名称 /locks/seq-0000000017 -> seq-0000000017
  54.                 String thisNode = currentMode.substring("/lockZookeeper/".length());
  55.                 // 通过seq-0000000017获取该节点在children集合的位置
  56.                 int index = children.indexOf(thisNode);
  57.                 // 判断
  58.                 if (index == -1) {
  59.                     System.out.println("数据异常");
  60.                 } else if (index == 0) {
  61.                     // 就一个节点,可以获取锁了
  62.                     return;
  63.                 } else {
  64.                     // 需要监听 他前一个结点的变化
  65.                     waitPath = "/lockZookeeper/" + children.get(index - 1);
  66.                     zk.getData(waitPath, true, null);
  67.                     // 等待监听
  68.                     waitLatch.await();
  69.                 }
  70.             }
  71.         } catch (KeeperException e) {
  72.             e.printStackTrace();
  73.         } catch (InterruptedException e) {
  74.             e.printStackTrace();
  75.         }
  76.     }
  77.     // 解锁
  78.     public void unZkLock() {
  79.         // 删除节点
  80.         try {
  81.             zk.delete(currentMode, -1);
  82.         } catch (InterruptedException e) {
  83.             e.printStackTrace();
  84.         } catch (KeeperException e) {
  85.             e.printStackTrace();
  86.         }
  87.     }
  88. }
复制代码
Curator 分布式锁
  1. package com.example.test.other.zk;
  2. import org.apache.curator.RetryPolicy;
  3. import org.apache.curator.framework.CuratorFramework;
  4. import org.apache.curator.framework.CuratorFrameworkFactory;
  5. import org.apache.curator.framework.recipes.locks.InterProcessLock;
  6. import org.apache.curator.framework.recipes.locks.InterProcessMutex;
  7. import org.apache.curator.framework.recipes.locks.InterProcessSemaphoreMutex;
  8. import org.apache.curator.retry.ExponentialBackoffRetry;
  9. import org.apache.zookeeper.KeeperException;
  10. import org.springframework.context.annotation.Bean;
  11. import org.springframework.context.annotation.Configuration;
  12. import java.io.IOException;
  13. /**
  14. * Description: 1、@Bean单独注解方法时,每次调用方法都是执行方法内的逻辑并返回新创建的对象bean,而且SpringIOC并没有该bean的存在。
  15. *              2、@Bean + @Configuration ,在调用@Bean注解的方法时返回的实例bean是从IOC容器获取的,已经注入的,且是单例的,而不是新创建的。
  16. *              3、@Bean + @Component,虽然@Bean注解的方法返回的实例已经注入到SpringIOC容器中,但是每次调用@Bean注解的方法时,都会创建新的对象实例bean返回,并不会从IOC容器中获取。
  17. * Author: yangjj_tc
  18. * Date: 2023/5/18 13:25
  19. */
  20. @Configuration
  21. public class ZookeperLock1Test {
  22.     private final String ZOOKEEPER_ADDRESS = "127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
  23.     /**
  24.      * Description: Author:
  25.      * yangjj_tc
  26.      *  1、会话连接是异步的,需要自己去处理。比如使用CountDownLatch
  27.      *  2、Watch需要重复注册,不然就不能生效
  28.      *  3、开发的复杂性还是比较高的
  29.      *  4、不支持多节点删除和创建。需要自己去递归
  30.      * Date: 2023/5/18 12:48
  31.      */
  32.     @Bean
  33.     public CuratorFramework getCuratorFramework(){
  34.         // 重试策略,重试间隔时间为1秒,重试次数为3次。curator管理了zookeeper的连接,在操作zookeeper的过程中出现连接问题会自动重试。
  35.         RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3);
  36.         // 初始化客户端,通过工厂创建连接
  37.         // zk地址 会话超时时间,默认60秒 连接超时时间,默认15秒 重试策略
  38.         CuratorFramework zkClient = CuratorFrameworkFactory.newClient(ZOOKEEPER_ADDRESS, 5000, 15000, retryPolicy);
  39.         // 开始连接
  40.         zkClient.start();
  41.         return zkClient;
  42.     }
  43.     public static void main(String[] args) throws InterruptedException, IOException, KeeperException {
  44.         // 获得两个客户端
  45.         CuratorFramework client1 = new ZookeperLock1Test().getCuratorFramework();
  46.         CuratorFramework client2 = new ZookeperLock1Test().getCuratorFramework();
  47.         // 可重入锁, 意味着同一个客户端在拥有锁的同时,可以多次获取,不会被阻塞。如想重入,则需要使用同一个InterProcessMutex对象。
  48.         final InterProcessLock lock1 = new InterProcessMutex(client1, "/lockCurator");
  49.         final InterProcessLock lock2 = new InterProcessMutex(client2, "/lockCurator");
  50.         // 不可重入锁,区别在于该锁是不可重入的,在同一个线程中不可重入
  51.         final InterProcessSemaphoreMutex lock3 = new InterProcessSemaphoreMutex(client1, "/lockCurator");
  52.         final InterProcessSemaphoreMutex lock4 = new InterProcessSemaphoreMutex(client2, "/lockCurator");
  53.         // 模拟两个线程
  54.         new Thread(() -> {
  55.             try {
  56.                 // 线程加锁
  57.                 lock3.acquire();
  58.                 lock3.acquire();
  59.                 System.out.println("线程1获取锁");
  60.                 // 线程沉睡
  61.                 Thread.sleep(5 * 1000);
  62.                 // 线程解锁
  63.                 lock1.release();
  64.                 System.out.println("线程1释放了锁");
  65.             } catch (Exception e) {
  66.                 e.printStackTrace();
  67.             }
  68.         }).start();
  69.         // 线程2
  70.         new Thread(() -> {
  71.             // 线程加锁
  72.             try {
  73.                 lock2.acquire();
  74.                 System.out.println("线程2获取到锁");
  75.                 // 线程沉睡
  76.                 Thread.sleep(5 * 1000);
  77.                 lock2.release();
  78.                 System.out.println("线程2释放锁");
  79.             } catch (Exception e) {
  80.                 e.printStackTrace();
  81.             }
  82.         }).start();
  83.     }
  84. }
复制代码
9、Eureka 与 Zk 有什么区别



免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。




欢迎光临 IT评测·应用市场-qidao123.com (https://dis.qidao123.com/) Powered by Discuz! X3.4