Java开源工具库使用之ZooKeeper

前言

分布式系统是由多个节点协同工作的系统,它们可以提供高可用性高性能高扩展性的服务。然而,分布式系统也面临着很多挑战,如网络延迟、节点故障、数据一致性等。为了解决这些问题,分布式系统需要一个协调服务来管理节点之间的通信和协作。

ZooKeeper 就是一个高性能分布式应用协调服务。它提供了一些常用的服务, 如命名、配置管理、同步和组服务。在一个简单的接口中,让你不用从头开始编写,可以直接使用它来实现一致性、组管理、领导选举和存在协议等功能。当然,你也可以在它的基础上构建自己的特定需求。

ZooKeeper 是一个用 java 编写的开源工具库,它原生就提供了 Java API 来访问和操作 ZooKeeper 集群中的数据。你可以使用 ZooKeeper 作为 Java 应用程序或框架的依赖库,来实现分布式功能或集成其他分布式组件。

ZooKeeper 名字的由来:《从 Paxos 到 ZooKeeper 》第四章第一节

ZooKeeper 最早起源于雅虎研究院的一个研究小组。在当时,研究人员发现,在雅虎内部很多大型系统基本都需要依赖一个类似的系统来进行分布式协调,但是这些系统往往都存在分布式单点问题。所以,雅虎的开发人员就试图开发一个通用的无单点问题的分布式协调框架,以便让开发人员将精力集中在处理业务逻辑上。

关于“ZooKeeper”这个项目的名字,其实也有一段趣闻。在立项初期,考虑到之前内部很多项目都是使用动物的名字来命名的(例如著名的 Pig 项目),雅虎的工程师希望给这个项目也取一个动物的名字。时任研究院的首席科学家 RaghuRamakrishnan 开玩笑地说:“在这样下去,我们这儿就变成动物园了!”此话一出,大家纷纷表示就叫动物园管理员吧一一一因为各个以动物命名的分布式组件放在一起,雅虎的整个分布式系统看上去就像一个大型的动物园了,而 ZooKeeper 正好要用来进行分布式环境的协调一一于是,ZooKeeper 的名字也就由此诞生了。


本文将介绍 ZooKeeper 作为 Java 开源工具库的架构和工作原理,ZooKeeper 提供的 Java API 以及 ZooKeeper 在一些分布式系统或应用中的使用案例。

官网:https://ZooKeeper.apache.org/

github:https://github.com/apache/ZooKeeper

扫描二维码关注公众号,回复: 15924532 查看本文章

pom 依赖:

<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.7.0</version>
</dependency>

一、架构和工作原理

1.1 集群结构

Zookeeper 是由多个服务器(server)组成的集群(ensemble),每个服务器都存储着相同的数据,并通过 ZAB (ZooKeeper Atomic Broadcast Protocol) 协议保证数据一致性。客户端(client)可以连接到任意一个服务器,并通过发送请求(request)来读取或修改数据。服务器会根据请求类型进行处理,并返回响应(response)。如果请求涉及到修改数据,则服务器会将请求转发给领导者(leader),领导者会将请求广播给其他跟随者(follower),并等待他们达成一致后再执行请求并返回响应。

Zookeeper 集群是由多个服务器节点组成的,每个节点都可以提供协调服务。为了保证数据的一致性,Zookeeper 集群中的节点分为三种角色:leader、follower 和 observer。

  • Leader 节点是集群中的主节点,负责处理客户端的写请求,以及协调 follower 节点的状态。Leader 节点通过投票选举产生,一个集群中只能有一个 leader 节点。
  • Follower 节点是集群中的从节点,负责处理客户端的读请求,以及同步 leader 节点的数据。Follower 节点可以参与投票选举 leader 节点。
  • Observer 节点是集群中的观察者节点,负责处理客户端的读请求,以及同步 leader 节点的数据。Observer 节点不参与投票选举 leader 节点,也不影响集群的可用性和一致性。

Zookeeper 集群通常需要奇数个节点(至少三个),这样可以保证在出现故障时仍然能够正常工作。例如,在一个五个节点的集群中,即使有两个节点宕机了,仍然有三个节点可以继续提供服务。

ZooKeeper 需要奇数个节点是因为它需要一个 多数派 来选举领导者节点和保证数据一致性。如果有偶数个节点,那么当一半的节点失效时,就无法形成多数派,也就无法继续提供服务。而且,增加偶数个节点并不会提高可用性,反而会增加通信开销。所以,ZooKeeper 通常使用 3 或 5 个节点来构建集群。

1.2 数据模型

ZooKeeper存储的数据是以树形结构组织的节点(node),每个节点都有一个唯一路径(path)标识,并包含一些元数据(metadata)和可选值(value)。每个节点还可以有子节点(child node),形成层次关系。客户端可以通过路径来访问或操作节点。

Zookeeper 的数据模型是一个层次化的命名空间(类似于文件系统),其中每个节点称为 ZNode。每个 ZNode 可以存储一些数据,并且可能有子节点。ZNode 的路径由斜杠分隔,并且必须是绝对路径(不能使用相对路径)。ZNode 的名称可以包含 Unicode 字符。

例如:/app/config/db/url 是一个合法的 ZNode 路径。

)

每个 ZNode 都有一个状态结构(stat),用于记录该 ZNode 的元数据信息,例如创建时间、修改时间、版本号、子节点数量等。ZooKeeper 客户端可以通过这些信息来判断 ZNode 的变化,并做出相应的操作。

ZooKeeper 支持两种类型的 ZNode:持久化(persistent)和临时(ephemeral)。

  • 持久化 ZNode 是指在创建后一直存在于 ZooKeeper 中,直到被删除或重命名。
  • 临时 ZNode 是指在创建后只存在于 ZooKeeper 中一段时间,在创建它们的会话结束后自动删除。

1.3 会话管理

ZooKeeper与客户端之间维持着会话(session),客户端需要定期向服务器发送心跳包(ping)来保持会话有效。如果客户端与服务器失去联系超过指定时间,则会话失效,并且该客户端创建或持有的临时节点(ephemeral node)也会被删除。临时节点是一种特殊类型的节点,它只存在于创建它或持有它 的客户端有效期内,在客户端断开连接后自动消失。临时节点不能有子节点。

ZooKeeper 会话是指客户端和服务器之间的一种连接状态,用于保证客户端的请求和响应的一致性。每个 ZooKeeper 会话都有一个唯一的 64 位标识符(session id),以及一个超时时间(session timeout)。

  • Session id 是由服务器在客户端第一次连接时分配给客户端的,用于标识该客户端的身份。如果客户端断开连接后重新连接到另一个服务器,它会发送自己的 session id 给新的服务器,以便继续之前的会话。
  • Session timeout 是由客户端在创建 ZooKeeper 实例时指定的,用于告诉服务器该客户端多久没有发送心跳包就认为是失效了。如果服务器在 session timeout 时间内没有收到客户端的任何消息,它会认为该客户端已经断开连接,并且删除其相关的临时 ZNode 和监听事件。

ZooKeeper 客户端需要定期向服务器发送心跳包(ping),以维持自己的会话状态。如果客户端因为网络故障或其他原因无法与当前服务器通信,它会尝试连接到其他可用的服务器,并且恢复自己的会话状态。

ZooKeeper 客户端可以通过注册 Watcher 来监听 ZNode 的变化事件,并且在事件发生时得到通知。Watcher 只能触发一次,如果客户端想要持续监听 ZNode 的变化,需要重新注册 Watcher。Watcher 的注册和触发都是在 ZooKeeper 会话中进行的,如果 ZooKeeper 会话失效了,Watcher 的注册和触发也就无效了。

1.4 一致性保证

ZooKeeper 是一个分布式协调服务,它需要在多个服务器节点之间同步数据,并且保证客户端能够访问到最新的数据。为了实现这一目标,ZooKeeper 提供了以下几种一致性保证:

  • 原子性(Atomicity):每个更新操作(创建、删除、修改 ZNode)都是原子的,要么成功要么失败。
  • 顺序性(Ordering):每个更新操作都有一个全局唯一的事务编号(zxid),用于表示该操作在整个集群中的顺序。客户端可以通过 zxid 来判断 ZNode 的变化情况。
  • 单一系统映像(Single System Image):无论客户端连接到哪个服务器节点,它都能看到相同的数据视图。
  • 可靠性(Reliability):如果一个更新操作成功完成了,那么它会被持久化到所有服务器节点上,并且不会被撤销。
  • 实时性(Timeliness):客户端能够在一个有限的时间内访问到最新的数据。

ZooKeeper 的一致性保证是基于领导者选举和 ZAB 协议实现的。领导者选举是指集群中的服务器节点通过投票机制选择出一个 leader 节点,负责处理所有的写请求和协调其他节点的状态。ZAB 协议是指 ZooKeeper 原子广播协议,用于在 leader 和 follower 节点之间传输和同步数据。

ZooKeeper 的一致性保证并不是强一致性(Strong Consistency),而是顺序一致性(Sequential Consistency)。这意味着不同的客户端可能会看到不同版本的数据,但是他们看到的数据变化顺序是相同的。这样做可以提高读取效率和容错能力,但也可能带来一些问题,例如脑裂现象(Split Brain)。因此,在使用 ZooKeeper 时需要注意避免这些问题,并且合理设置超时时间和重试次数等参数。

脑裂现象是指由于网络分区导致 ZooKeeper 集群中的节点被划分为两个或多个子集,每个子集都可能产生一个领导者节点,从而造成数据不一致的情况。

ZooKeeper 通过使用 Quorum 算法来防止脑裂现象。Quorum 算法的基本思想是要求集群中的大多数节点(超过一半)能够正常通信和工作,才能进行领导者选举和状态变化广播。因此,ZooKeeper 集群的节点数应该是奇数,以避免出现平局。如果某个子集的节点数不满足 Quorum 条件,那么它就不能产生领导者节点,也不能处理客户端的请求. 可以见源码org.apache.zookeeper.server.quorum.flexible.QuorumMaj.java

private int half;
public QuorumMaj(Map<Long, QuorumServer> allMembers) {
    
    
    this.allMembers = allMembers;
    for (QuorumServer qs : allMembers.values()) {
    
    
        if (qs.type == LearnerType.PARTICIPANT) {
    
    
            votingMembers.put(Long.valueOf(qs.id), qs);
        } else {
    
    
            observingMembers.put(Long.valueOf(qs.id), qs);
        }
    }
    half = votingMembers.size() / 2;
}
public boolean containsQuorum(Set<Long> ackSet) {
    
    
    return (ackSet.size() > half);
}

ZooKeeper 保证了以下三种顺序:

  • 全局顺序:所有服务器按照相同的顺序执行相同的请求。
  • 因果顺序:如果一个事件A在另一个事件B之前发生,则任何观察到B的客户端也必然观察到A。
  • 客户端顺序:对于同一个客户端发出的请求,任何服务器都按照该客户端发送请求的顺序来执行。

二、Java API

ZooKeeper 提供的常见 java API,如创建、读取、更新、删除节点,监听节点变化,异步操作等

2.1 基本节点CRUD

ZooKeeper 中的数据是以树形结构存储的,每个节点称为一个 ZNode。ZNode 可以存储一些元数据和用户数据,并且可以有多个子节点。ZNode 也有一些属性,例如版本号、时间戳、访问控制列表等。

ZooKeeper 提供了一些基本的操作(CRUD)来对 ZNode 进行创建、读取、更新和删除。这些操作都是原子的,要么成功要么失败。

  • 创建(Create):创建一个新的 ZNode,并且指定其路径、数据和访问控制列表。可以选择是否创建临时节点或顺序节点。
  • 读取(Read):读取一个已存在的 ZNode 的数据和属性。可以选择是否注册 Watcher 来监听该 ZNode 的变化事件。
  • 更新(Update):更新一个已存在的 ZNode 的数据或访问控制列表。需要指定期望的版本号,如果与当前版本号不匹配,则更新失败。
  • 删除(Delete):删除一个已存在的 ZNode 及其所有子节点。需要指定期望的版本号,如果与当前版本号不匹配,则删除失败。

ZooKeeper 客户端可以使用不同的语言和框架来调用这些基本操作,例如 Java、Python、C++ 等,以下是一个使用 java 客户端进行基本节点 CRUD 的示例代码:

Watcher watcher = event -> System.out.println("Watch event: " + event);

// 创建一个 ZooKeeper 实例并连接到服务器,指定 Watcher 和超时时间
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, watcher);

// 检查父节点是否存在,如果不存在就先创建父节点
Stat stat = zk.exists("/test", false);
if (stat == null) {
    
    
    String parent = zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    System.out.println("创建父节点成功:" + parent);
}


// 创建一个新的 ZNode /test/node123,并设置其数据为 hello,
String path = zk.create("/test/node123", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("Created node: " + path);

// 读取 /test/node2 的数据和属性,并注册 Watcher 监听其变化事件
stat = new Stat();
byte[] data = zk.getData("/test/node123", watcher, stat);
System.out.println("Get node data: " + new String(data));
System.out.println("Get node stat: " + stat);

// 更新 /test/node2 的数据为 world,并指定期望的版本号为 -1 (表示忽略版本检查)
stat = zk.setData("/test/node123", "world".getBytes(), -1);
System.out.println("Set node stat: " + stat);

// 删除 /test/node2 及其所有子节点,并指定期望的版本号为 -1 (表示忽略版本检查)
zk.delete("/test/node123", -1);
System.out.println("Delete node success");

// 关闭 ZooKeeper 连接
zk.close();

2.2 监听节点变化

ZooKeeper 提供了一种机制,称为 Watcher,用于让客户端监听 ZNode 的变化事件。当一个 ZNode 的数据或子节点发生变化时,ZooKeeper 会向注册了 Watcher 的客户端发送一个通知。

Watcher 可以在读取 ZNode 数据或子节点列表时注册,也可以在检查 ZNode 是否存在时注册。Watcher 只能触发一次,如果客户端想要持续监听 ZNode 的变化,需要重新注册 Watcher。

Watcher 有以下几种类型:

  • NodeCreated:当一个不存在的 ZNode 被创建时触发。
  • NodeDeleted:当一个存在的 ZNode 被删除时触发。
  • NodeDataChanged:当一个存在的 ZNode 的数据被修改时触发。
  • NodeChildrenChanged:当一个存在的 ZNode 的子节点列表发生变化时触发。

Watcher 也有以下几种特点:

  • 客户端可以设置多个 Watcher 监听同一个 ZNode 或不同的 ZNode。
  • ZooKeeper 保证客户端不会看到它设置了 Watcher 的节点的变化,直到它先看到 Watcher 事件。
  • ZooKeeper 不保证每个客户端都能收到每个 Watcher 事件,因为可能存在网络延迟或其他因素导致事件丢失。
  • ZooKeeper 不保证 Watcher 事件的顺序与实际变化的顺序一致,因为可能存在并发更新或其他因素导致顺序错乱。

以下是一个使用 Java API 进行监听节点变化的示例代码:

// 创建一个 ZooKeeper 实例并连接到服务器,指定超时时间
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 创建一个 Watcher 实例,用于监听 /test/node3 的变化事件
Watcher watcher = new Watcher() {
    
    
    @Override
    public void process(WatchedEvent event) {
    
    
        System.out.println("Watch event: " + event);
        // 如果是 NodeCreated 或 NodeDataChanged 类型,则重新读取数据并注册新的 Watcher
        if (event.getType() == Event.EventType.NodeCreated || event.getType() == Event.EventType.NodeDataChanged) {
    
    
            try {
    
    
                byte[] data = zk.getData("/test/node3", this, null);
                System.out.println("New data: " + new String(data));
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }
};

// 检查父节点是否存在,如果不存在就先创建父节点
Stat stat = zk.exists("/test", false);
if (stat == null) {
    
    
    String parent = zk.create("/test", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    System.out.println("创建父节点成功:" + parent);
}



// 检查 /test/node3 是否存在,并注册 Watcher 监听其创建和修改事件
stat = zk.exists("/test/node3", watcher);
System.out.println("Check node stat: " + stat);

// 创建 /test/node3,并设置其数据为 hello
String path = zk.create("/test/node3", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
System.out.println("Created node: " + path);

// 更新 /test/node3 的数据为 world
stat = zk.setData("/test/node3", "world".getBytes(), -1);
System.out.println("Set node stat: " + stat);

// 关闭 ZooKeeper 连接
zk.close();

2.3 异步操作

ZooKeeper Java API 提供了一些异步方法,用于在不阻塞主线程的情况下执行一些操作,并通过回调函数获取操作结果。异步方法通常以 async 结尾,并需要传入一个 StringCallback 或 DataCallback 参数作为回调函数。例如:

// 创建一个 ZooKeeper 实例并连接到服务器,指定超时时间
ZooKeeper zk = new ZooKeeper("localhost:2181", 3000, null);

// 检查父节点是否存在,如果不存在就先创建父节点
Stat stat = zk.exists("/app", false);
if (stat == null) {
    
    
    String parent = zk.create("/app", "test".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
    System.out.println("创建父节点成功:" + parent);
}

// 异步创建 /app/config 节点,并设置数据为 "hello"
zk.create("/app/config", "hello".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT, (rc, path, ctx, name) -> {
    
    
    // 处理创建结果
    System.out.println("Create result: " + rc);
    System.out.println("Create path: " + path);
    System.out.println("Create name: " + name);
}, null);

// 异步读取 /app/config 节点的数据和状态
zk.getData("/app/config", false, (rc, path, ctx, data, state) -> {
    
    
    // 处理读取结果
    System.out.println("Get result: " + rc);
    System.out.println("Get path: " + path);
    System.out.println("Get data: " + new String(data));
    System.out.println("Get stat: " + state);
}, null);

try {
    
    
    Thread.sleep(200);
} catch (InterruptedException e) {
    
    
    e.printStackTrace();
}

2.4 会话管理

ZooKeeper 的会话管理主要是通过 SessionTracker 来负责,其采用了分桶策略(将类似的会话放在同一区块中进行管理)进行管理,以便 ZooKeeper 对会话进行不同区块的隔离处理以及同一区块的统一处理。

ZooKeeper 对外的服务端口默认是 2181,客户端启动时,首先会与服务器建立一个 TCP 连接,从第一次连接建立开始,客户端会话的生命周期也开始了。通过这个连接,客户端能够通过心跳检测和服务器保持有效的会话,也能够向 ZooKeeper 服务器发送请求并接受响应,同时也能够从服务器获取最新的数据变化通知。

ZooKeeper 的会话有两种类型:短暂(ephemeral)和持久(persistent)。短暂会话在客户端断开连接或超时时自动结束,而持久会话需要客户端显式关闭或删除。短暂会话可以用来实现分布式锁、Master 选举等功能,而持久会话可以用来实现数据发布/订阅、命名服务等功能。

import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;

// 定义一个类实现Watcher接口,用于监听会话状态和节点变化
public class EphemeralSessionExample implements Watcher {
    
    

    // 定义一个ZooKeeper对象,用于连接和操作ZooKeeper服务器
    private ZooKeeper zk;

    // 定义一个构造方法,传入ZooKeeper服务器的地址和端口,并创建连接
    public EphemeralSessionExample(String hostPort) throws Exception {
    
    
        zk = new ZooKeeper(hostPort, 3000, this); // 设置会话超时时间为3秒
    }

    // 定义一个方法,用于创建或更新一个短暂节点,并设置数据和权限
    public void createOrUpdate(String path, byte[] data) throws Exception {
    
    
        Stat stat = zk.exists(path, false); // 检查znode是否存在,不设置监听器
        if (stat == null) {
    
     // 如果不存在,则创建znode
            zk.create(path, data, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); // 创建短暂的znode,并设置数据和权限
        } else {
    
     // 如果存在,则更新znode的数据
            zk.setData(path, data, -1); // 更新znode的数据,并忽略版本号
        }
    }

    // 定义一个方法,用于获取并打印当前会话的ID和状态
    public void printSessionInfo() {
    
    
        long sessionId = zk.getSessionId(); // 获取会话ID
        String sessionState = zk.getState().toString(); // 获取会话状态
        System.out.println("The session ID is: " + sessionId); // 打印会话ID
        System.out.println("The session state is: " + sessionState); // 打印会话状态
    }

    // 实现Watcher接口中的process方法,用于处理事件通知
    @Override
    public void process(WatchedEvent event) {
    
    
        System.out.println("Received a watch event: " + event); // 打印事件信息

        if (event.getType() == Event.EventType.None && event.getState() == Event.KeeperState.SyncConnected) {
    
     
            System.out.println("The client is connected to the server"); // 如果是连接成功的事件,打印连接信息
        } else if (event.getType() == Event.EventType.None && event.getState() == Event.KeeperState.Expired) {
    
     
            System.out.println("The session is expired"); // 如果是会话过期的事件,打印过期信息
            try {
    
    
                zk.close(); // 关闭ZooKeeper对象
            } catch (Exception e) {
    
    
                e.printStackTrace();
            }
        }
    }

    // 定义一个主方法,用于测试短暂会话的创建和更新
    public static void main(String[] args) throws Exception {
    
    
        EphemeralSessionExample ese = new EphemeralSessionExample("localhost:2181"); // 创建一个EphemeralSessionExample对象,并连接到本地的ZooKeeper服务器
        ese.printSessionInfo(); // 打印当前会话的信息
        ese.createOrUpdate("/test", "Hello".getBytes()); // 创建或更新一个短暂节点,并设置数据为"Hello"
        Thread.sleep(5000); // 睡眠5秒,模拟客户端断开连接
        ese.printSessionInfo(); // 打印当前会话的信息,此时应该是过期状态
    }
}

三、应用场景与案例分析

3.1 优缺点

优点

  • 简单的分布式协调过程:ZooKeeper 提供了一个简单的数据模型,基于树形结构的 znode 节点,可以存储数据和元数据,并支持临时节点、顺序节点等类型。ZooKeeper 还提供了一些原语操作,如创建、删除、读取、写入等,以及一些高级功能,如监听器、订阅者等。
  • 高度同步的工作方式:ZooKeeper 保证了所有节点之间的数据一致性,通过 ZAB 协议实现了领导者选举和事务日志复制。ZooKeeper 还支持互斥和协作之间的服务器进程。
  • 多样化的分布式场景应用:ZooKeeper 可以用于实现各种分布式场景下的功能和服务,如配置管理、命名服务、集群管理、负载均衡、锁服务、队列服务等。
  • 成熟的开源社区和生态系统:ZooKeeper 是 Apache 的顶级项目之一,有着广泛的用户群和活跃的开发者社区。ZooKeeper 还有很多基于它开发的框架和库,如 Curator(一个 Java 库),提供了很多分布式 ZooKeeper 模式的实现。

缺点

  • 低效的写入性能:由于 ZooKeeper 需要保证所有节点之间的强一致性,因此每次写入操作都需要经过领导者节点的确认,并复制到所有跟随者节点上。这样就增加了写入操作的延迟和开销,并降低了吞吐量。
  • 有限的存储容量:由于 ZooKeeper 的数据都存储在内存中,因此受到内存大小的限制。如果数据量过大或者节点数量过多,可能会导致内存不足或者 GC 压力过大。
  • 缺乏动态扩容能力:由于 ZooKeeper 需要维护所有节点之间的状态信息,并保证领导者选举和事务日志复制等机制正常运行,因此不能随意地增加或减少节点数量。如果需要扩容或缩容集群规模,需要进行手动操作,并可能会影响集群稳定性。

3.2 适用场景分析

  • 数据发布/订阅:可以作为配置中心,实现配置信息的集中式管理和动态更新。发布者将数据发布到 ZooKeeper 的节点上,订阅者通过监听器获取数据的变化,从而实现数据的同步和共享。
  • 负载均衡:可以作为服务注册中心,实现服务的发现和负载均衡。服务提供者将自己的地址信息注册到 ZooKeeper 的节点上,服务消费者通过 ZooKeeper 获取可用的服务列表,并根据一定的策略选择合适的服务提供者。
  • 命名服务:可以作为命名服务,实现分布式系统中唯一标识符的生成和管理。利用 ZooKeeper 的顺序节点特性,可以保证每个节点都有一个全局唯一且有序的名称。
  • 分布式协调/通知:可以实现分布式协调/通知机制,实现分布式系统中各个组件之间的协作和状态同步。利用 ZooKeeper 的临时节点特性,可以实现故障检测和恢复;利用 ZooKeeper 的监听器特性,可以实现事件驱动和消息通知。
  • 集群管理:可以作为集群管理工具,实现集群中各个节点的监控和管理。利用 ZooKeeper 的元数据存储特性,可以存储集群中各个节点的状态信息;利用 ZooKeeper 的 ZAB 协议特性,可以保证集群中各个节点之间的数据一致性。
  • Master 选举:Master 选举机制,实现分布式系统中主从模式下主节点的选取和切换。利用 ZooKeeper 的临时顺序节点特性,可以保证在多个候选者中只有一个能够成为主节点,并在主节点故障时进行重新选举。
  • 分布式锁:可以作为分布式锁服务,实现分布式系统中对共享资源的互斥访问。利用 ZooKeeper 的临时顺序节点特性,可以保证在多个请求者中只有一个能够获得锁,并在持有锁的请求者释放或断开连接时进行锁释放。
  • 分布式队列:可以作为分布式队列服务,实现分布式系统中对任务或消息的先进先出(FIFO)或优先级处理。利用 ZooKeeper 的顺序节点特性,可以保证队列中元素按照插入顺序或优先级顺序排列,并支持并发地插入或删除操作。

3.3 实际案例

  • 数据发布/订阅Hadoop、HBase、Kafka 等分布式系统都使用 ZooKeeper 作为配置中心,实现配置信息的集中式管理和动态更新。例如,HBase 中的 HMaster 节点会将集群的元数据信息(如 RegionServer 列表、Region 分布情况等)存储在 ZooKeeper 的节点上,客户端和其他 RegionServer 节点可以通过 ZooKeeper 获取这些信息,并在发生变化时及时更新。
  • 负载均衡Dubbo、Spring Cloud 等微服务框架都使用 ZooKeeper 作为服务注册中心,实现服务的发现和负载均衡。例如,Dubbo 中的服务提供者会将自己的地址信息注册到 ZooKeeper 的节点上,服务消费者会通过 ZooKeeper 获取可用的服务列表,并根据一定的策略(如随机、轮询、最少活跃等)选择合适的服务提供者。
  • 命名服务SolrCloud 是一个分布式搜索平台,它使用 ZooKeeper 作为命名服务,实现分布式系统中唯一标识符的生成和管理。例如,SolrCloud 中每个集合(collection)都有一个唯一的名称,每个分片(shard)都有一个唯一的编号,每个副本(replica)都有一个唯一的 ID。这些名称、编号和 ID 都是通过 ZooKeeper 的顺序节点特性生成和存储的。
  • 分布式协调/通知Storm 是一个分布式实时计算框架,它使用 ZooKeeper 作为分布式协调/通知机制,实现分布式系统中各个组件之间的协作和状态同步。例如,Storm 中有一个 Nimbus 节点负责管理整个集群,并将任务分配给多个 Supervisor 节点;Supervisor 节点负责启动和停止 Worker 进程;Worker 进程负责执行具体的计算任务。这些节点之间都需要通过 ZooKeeper 来进行通信和协调 。
  • 集群管理Redis 是一个高性能的内存数据库,它支持主从复制和哨兵模式来实现高可用性。Redis 使用 ZooKeeper 作为集群管理工具,实现集群中各个节点的监控和管理。例如,在哨兵模式下,哨兵节点会将自己注册到 ZooKeeper 的临时节点上,并定期向其发送心跳;当主节点故障时,哨兵节点会通过 ZooKeeper 进行领导者选举,并通知其他哨兵节点切换从节点为主节点 。

参考

  1. ZooKeeper 相关概念总结(入门)
  2. https://stackoverflow.com/questions/58761164/in-kafka-ha-why-minimum-number-of-brokers-required-are-3-and-not-2
  3. why-zookeeper-needs-an-odd-number-of-nodes

猜你喜欢

转载自blog.csdn.net/qq_23091073/article/details/129641930