Skip to content

ZooKeeper 典型应用:Kafka 控制器、Dubbo 注册中心

你有没有想过这个问题:

Kafka 的 Controller 选举、Hadoop 的 NameNode 高可用……这些分布式系统的「大脑」是怎么选出来的?

答案都指向同一个组件——ZooKeeper。

但为什么是 ZooKeeper?它凭什么能成为这些系统的「定海神针」?

今天,我们来深入看看 ZooKeeper 在 Kafka 和 Dubbo 中的实际应用。

Kafka 控制器:Broker Leader 的选举

Kafka 是一个分布式消息系统,它的核心是 Broker(代理服务器)和 Partition(分区)。

每个 Topic 有多个 Partition,每个 Partition 有多个副本(Replica),分布在不同的 Broker 上。

问题来了:谁决定哪个副本是 Leader?

Controller 的职责

Kafka 集群中,有一个特殊的角色——Controller

Controller 本质上是一个 Broker,但它比其他 Broker 多了一份职责:

Controller 负责:
1. 管理整个集群的 Broker 元数据
2. 选举 Partition Leader
3. 处理 Broker 上下线
4. 维护分区状态
5. 处理分区副本分配

Controller 是集群的「大脑」,所有分区状态变更都需要 Controller 决策。

ZooKeeper 在 Kafka Controller 中的角色

Controller 的选举,依赖 ZooKeeper 的临时节点机制:

java
// Kafka Broker 启动时,尝试创建 Controller 节点
// 路径:/controller
// 内容:{"brokerId": 1, "timestamp": 1234567890}

String controllerPath = "/controller";

try {
    zkClient.createEphemeral(controllerPath, brokerInfo);
} catch (NodeExistsException e) {
    // 已经被其他 Broker 创建,监听其变化
    zkClient.watchForChanges(controllerPath);
}

选举规则

  1. 第一个抢到 /controller 节点的 Broker 成为 Controller
  2. 后续 Broker 只能 Watch 这个节点
  3. Controller 宕机 → 节点消失 → 所有 Broker 收到通知 → 重新选举

Controller 故障转移

1. Broker 1 成为 Controller(创建 /controller 节点)
2. Broker 1 宕机 → /controller 节点消失
3. 所有 Broker 收到 Watch 通知
4. Broker 2、3、4 同时尝试创建 /controller
5. Broker 2 第一个创建成功,成为新的 Controller
6. Broker 2 重新初始化 Controller 上下文,选举新的 Partition Leader

整个故障转移过程在秒级完成,这就是 ZooKeeper 带来的「自愈」能力。

分区 Leader 选举

有了 Controller,Partition Leader 的选举就简单了:

java
// Controller 需要选举某个 Partition 的 Leader
// 向 ZooKeeper 写入新的 Leader 信息

String partitionPath = "/brokers/topics/topic-1/partitions/0/state";
String state = "{\"leader\": 2, \"leader_epoch\": 5, \"version\": 1}";

zkClient.setData(partitionPath, state);
// 所有 Watch 该路径的 Broker 收到通知,同步更新本地元数据

Dubbo 注册中心:服务提供者和消费者的发现

Dubbo 是阿里巴巴开源的 RPC 框架,2.6 之前的版本使用 ZooKeeper 作为注册中心。

服务注册模型

Dubbo 将服务信息写入 ZooKeeper,形成树形结构:

text
/dubbo
├── /dubbo/com.example.UserService
│   ├── /providers
│   │   ├── /dubbo/com.example.UserService/providers/192.168.1.1:20880
│   │   ├── /dubbo/com.example.UserService/providers/192.168.1.2:20880
│   │   └── /dubbo/com.example.UserService/providers/192.168.1.3:20880
│   ├── /consumers
│   │   ├── /dubbo/com.example.UserService/consumers/192.168.1.10:xxxx
│   │   └── /dubbo/com.example.UserService/consumers/192.168.1.11:xxxx
│   ├── /routers (路由规则)
│   └── /configurators (动态配置)

URL 格式

dubbo://192.168.1.1:20880/com.example.UserService?version=1.0.0&group=prod&methods=getUser,createUser&timestamp=1234567890

服务提供者注册

java
// Dubbo 服务提供者启动时,向 ZooKeeper 注册
// 使用临时节点,服务下线自动清理

String providerPath = "/dubbo/com.example.UserService/providers/" +
    URLEncoder.encode(providerUrl, "UTF-8");

zkClient.createEphemeral(providerPath, metadata);

// ZooKeeper 路径示例:
// /dubbo/com.example.UserService/providers/dubbo%3A%2F%2F192.168.1.1%3A20880...

服务消费者订阅

java
// Dubbo 服务消费者订阅服务提供者列表
// Watch providers 节点的孩子变化

List<String> providers = zkClient.getChildren(
    "/dubbo/com.example.UserService/providers",
    watcher // 监听孩子变化
);

// 消费者收到通知后,重新获取最新的提供者列表
// 本地缓存 + ZooKeeper Watch = 准实时的服务发现

监控中心订阅

Dubbo 的监控中心(Monitors)也会订阅服务数据,用于统计和展示:

java
// 监控中心订阅所有服务
// 每当服务上下线,Monitors 都会收到通知

zkClient.getChildren(
    "/dubbo/com.example.UserService/providers",
    notification -> {
        // 统计当前在线服务数量
        int count = notification.getChildren().size();
        metricsCollector.recordServiceCount("UserService", count);
    }
);

Dubbo 2.7 的变革:为什么改用 Nacos?

Dubbo 2.6 之前,ZooKeeper 是唯一的注册中心选择。

但 Dubbo 2.7 发布后,引入了独立的注册中心抽象,支持 Zookeeper、Nacos、Redis、Consul 等。

为什么 Dubbo 2.7 之后推荐 Nacos?

ZooKeeper 不适合服务注册

服务注册场景有个特点:大量小数据 + 高频变更

Kafka Controller:1 个节点,变更稀少 ✅
Dubbo 服务注册:可能有 10000 个服务,每个服务有 N 个实例,实例频繁上下线 ❌

ZooKeeper 的问题是:

  1. 每个服务实例都要创建一个 ZNode
  2. 每次变更都要通知所有 Watcher
  3. 大量 Watcher 会导致「通知风暴」

Nacos 的优势

Nacos 是专门为「服务发现 + 配置管理」设计的:

java
// Nacos 服务注册
namingService.registerInstance("UserService", "192.168.1.1", 8080);
// Nacos 自动处理心跳、健康检查、负载均衡
  • 心跳保活:客户端定期发送心跳,服务不健康自动剔除
  • 批量通知:Nacos 内部优化了通知机制
  • 更友好的 API:HTTP API,不需要 ZooKeeper 客户端
  • 配置中心集成:一个组件解决两个问题

迁移路径

Dubbo 2.7+ 可以无缝迁移到 Nacos:

xml
<!-- Dubbo 配置 -->
<dubbo:registry address="nacos://localhost:8848" />

总结

ZooKeeper 在 Kafka 和 Dubbo 中的应用,展示了它在分布式协调领域的核心价值:

  • Kafka Controller:临时节点 + Watch = Broker Leader 自动选举
  • Dubbo 注册中心:树形结构 + 临时节点 = 服务发现
  • ZooKeeper 的局限:大数据量、高并发变更场景有瓶颈
  • 演进方向:Nacos、Consul 等专用注册中心

理解这些经典应用,才能真正理解 ZooKeeper 的定位。

面试追问方向:

  • Kafka Controller 选举失败会怎样?
  • ZooKeeper 在 Dubbo 中的作用和 Nacos 有什么区别?
  • 为什么 Kafka 用 ZooKeeper 而不用其他组件?
  • 如果 ZooKeeper 集群不可用,Kafka 还能工作吗?

基于 VitePress 构建