【面试】Zookeeper

打印 上一主题 下一主题

主题 1046|帖子 1046|积分 3142

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 分布式锁
  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 有什么区别


  • Eureka采取 AP 模型,优先包管可用性,允许短暂的数据不一致。通过心跳机制和客户端缓存实现最终一致性。ZooKeeper采取 CP 模型,在分区故障时可能牺牲可用性。利用 ZAB 协议包管强一致性。
  • Eureka是对等架构:Eureka 节点之间是对等的,每个节点都可以吸收注册和查询请求。客户端缓存,客户端会缓存服务注册表,纵然 Eureka 服务器不可用,客户端仍然可以获取服务信息。ZooKeeper主从架构:ZooKeeper 集群中有 Leader 和 Follower 节点,写操纵由 Leader 处理。强一致性:全部节点数据保持一致,客户端总是读取到最新数据。


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

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

论坛元老
这个人很懒什么都没写!
快速回复 返回顶部 返回列表