乐途乐途
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
主页
  • 计算机基础

    • TCP/IP
    • Linux
    • HTTP
  • 数据库

    • SQL
    • MySQL 5.7
  • 编程语言

    • C
    • C++
    • Java SE
    • Python2
    • Python3
  • 数据格式

    • JSON
    • XML
  • 认证与安全

    • JWT
  • 工具

    • Markdown
  • Git

    • GitFlow
  • Quartz

    • Quartz
  • Java

    • Maven 入门
    • Maven 进阶
    • MyBatis
    • Spring
    • Spring MVC
  • Java

    • Spring Boot
    • Spring Cloud
    • Spring Cloud Alibaba
    • Spring Security
    • Spring AI
    • Spring Batch
    • Kafka
    • Java 设计模式
  • 缓存

    • Redis
  • 搜索引擎

    • Elasticsearch
  • 分布式协调

    • ZooKeeper
联系
阿里云
  • ZooKeeper 学习路径
  • 第1章 分布式协调与ZooKeeper概述

    • ZooKeeper 概述
  • 第2章 单机与集群搭建

    • 配置参数详解
    • 集群搭建
  • 第3章 数据模型与ZNode

    • ZNode 详解
    • 节点类型对比
    • 顺序节点
    • ACL 权限控制
  • 第4章 会话与Watcher机制

    • 会话机制
    • Watcher 机制
  • 第5章 ZAB协议与一致性保证

    • ZAB 协议
    • 一致性保证
    • 数据同步
  • 第6章 Leader选举

    • Leader 选举
  • 第7章 典型应用:分布式锁

    • 分布式锁
  • 第8章 典型应用:配置中心与命名服务

    • 配置中心
    • 命名服务
  • 第9章 客户端编程基础(Java原生API)

    • Java 原生 API 编程
  • 第10章 运维与监控

    • 四字命令
    • 监控体系
  • 第11章 面试考点与设计思想

    • 设计思想
    • 面试考点汇编

本章定位:命名服务是 ZooKeeper 在分布式系统中的另一大典型应用,涵盖全局唯一 ID 生成和服务注册发现两大场景。

定义与作用

命名服务(Naming Service)是分布式系统中将名称映射到资源的机制。ZooKeeper 通过 ZNode 路径和 Sequential 节点提供两种核心能力:

能力机制典型应用
全局唯一 ID 生成Persistent Sequential 节点订单号、流水号、消息 ID
服务注册与发现Ephemeral 节点 + getChildren微服务注册中心

核心原理

服务注册发现流程

服务提供者创建 Ephemeral 节点注册自己,消费者 Watch 父节点获取在线服务列表。提供者宕机后 Ephemeral 节点自动删除,消费者收到通知并更新列表。

ID 生成架构

多个 ID 生成器实例各自创建 Persistent Sequential 节点,Leader 原子分配序号,保证全局唯一。没有单点故障(Snowflake 依赖机器时钟)。

完整示例

示例一:服务注册与发现

场景说明:订单服务 3 个实例注册,网关动态发现。

# 创建命名空间
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] create /services ""
Created /services
[zkshell: 1] create /services/order ""
Created /services/order
# 订单服务实例 1 注册(Terminal 1)
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] create -e /services/order/192.168.1.10:8080 "weight=10"
Created /services/order/192.168.1.10:8080

# 实例 2 注册(Terminal 2)
[zkshell: 0] create -e /services/order/192.168.1.11:8080 "weight=5"

# 实例 3 注册(Terminal 3)
[zkshell: 0] create -e /services/order/192.168.1.12:8080 "weight=10"
# 网关发现服务(Terminal 4)
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] ls -w /services/order
[192.168.1.10:8080, 192.168.1.11:8080, 192.168.1.12:8080]
# 实例 2 宕机(关闭 Terminal 2)
# 等待 Session Timeout 后...

# 网关自动收到通知
[zkshell: 1] ls -w /services/order
[192.168.1.10:8080, 192.168.1.12:8080]
# 192.168.1.11:8080 已自动移除

操作前后对比:

阶段在线实例消费者视图
3 实例全部在线10:8080, 11:8080, 12:80803 个实例
实例 2 宕机10:8080, 12:80802 个实例(自动更新)

示例二:Java 服务注册实现

场景说明:服务启动时注册,关闭时主动注销。

public class ServiceRegistry {
    private ZooKeeper zk;
    private String servicePath;
    private String instancePath;

    public ServiceRegistry(String zkAddr, String serviceName,
            String host, int port) throws Exception {
        this.zk = new ZooKeeper(zkAddr, 5000, event -> {});
        this.servicePath = "/services/" + serviceName;

        // 确保父节点存在
        if (zk.exists(servicePath, false) == null) {
            zk.create(servicePath, null,
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }

        // 注册 Ephemeral 节点
        String address = host + ":" + port;
        this.instancePath = zk.create(
                servicePath + "/" + address,
                ("status=UP").getBytes(),
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL);
        System.out.println("Registered: " + instancePath);
    }

    public void shutdown() throws Exception {
        if (instancePath != null) {
            zk.delete(instancePath, -1);
            System.out.println("Deregistered: " + instancePath);
        }
        zk.close();
    }
}

// 使用
ServiceRegistry registry = new ServiceRegistry(
    "127.0.0.1:2181", "order-service", "192.168.1.10", 8080);

// 应用关闭时
Runtime.getRuntime().addShutdownHook(
    new Thread(() -> registry.shutdown()));

操作前后对比:

事件/services/order-service 节点原因
应用启动新增 192.168.1.10:8080create Ephemeral
应用正常关闭删除 192.168.1.10:8080delete 主动注销
应用异常宕机自动删除(~Session Timeout 后)Ephemeral 自动清理

示例三:全局唯一订单号生成

场景说明:分布式订单系统生成唯一订单号。

public class OrderIdGenerator {
    private ZooKeeper zk;
    private static final String ID_PATH = "/order-ids/ORD-";

    public OrderIdGenerator(String zkAddr) throws Exception {
        this.zk = new ZooKeeper(zkAddr, 5000, event -> {});
        if (zk.exists("/order-ids", false) == null) {
            zk.create("/order-ids", null,
                    ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.PERSISTENT);
        }
    }

    public String nextId() throws Exception {
        String path = zk.create(ID_PATH, null,
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.PERSISTENT_SEQUENTIAL);
        // path = "/order-ids/ORD-0000000001"
        return path.substring(ID_PATH.length());
    }
}

// 使用
OrderIdGenerator gen = new OrderIdGenerator("127.0.0.1:2181");
System.out.println(gen.nextId()); // ORD-0000000001
System.out.println(gen.nextId()); // ORD-0000000002
System.out.println(gen.nextId()); // ORD-0000000003

操作前后对比:

调用返回/order-ids 下节点数
nextId() #1ORD-00000000011
nextId() #2ORD-00000000022
nextId() #3ORD-00000000033

注意:生成的 Sequential 节点应定期清理(可异步删除或 TTL 自动清理),避免节点数量无限增长。

易错场景与面试考点

易错场景

1. 服务消费者本地缓存与实际不一致

消费者 getChildren 后本地缓存服务列表。如果 Watcher 通知丢失(网络抖动),消费者可能一直使用过时的列表。解决方案:

  • 定时轮询(如每 30s 重新 getChildren)
  • 调用失败后立即刷新列表

2. 服务注册信息过于庞大

将服务的完整元数据(如健康检查 URL、版本号、权重、标签)全部写在 ZNode 数据中可能超出 1MB 限制。建议在 ZNode 中只存储关键信息(地址 + 权重),详细元数据通过独立 API 提供。

3. Sequential 节点未清理

ID 生成器创建的 Persistent Sequential 节点永久存在,数量积累后:

  • ls 命令变慢
  • 内存占用增加
  • ID 解析时的排序开销增大

应在 ID 消费后异步删除节点。

面试高频题

Q:ZooKeeper 做服务发现与 Eureka 有何区别?

A:

  • ZooKeeper(CP):保证一致性,网络分区时少数派不可用
  • Eureka(AP):保证可用性,网络分区时各分区独立服务
  • 选型:ZooKeeper 适合要求强一致性的场景(如金融服务);Eureka 适合要求高可用的互联网场景

Q:如何实现带权重的负载均衡?

A:在 ZNode 数据中存储权重信息:

create -e /services/order/192.168.1.10:8080 "weight=10"
create -e /services/order/192.168.1.11:8080 "weight=5"

消费者读取子节点列表 + 各自数据 → 按权重加权随机或轮询。

Q:如何处理服务上下线的短暂不一致?

A:依赖 ZooKeeper 的顺序一致性保证——同一个客户端看到的事件顺序与全局顺序一致。结合客户端重试 + 服务端优雅下线(先标记 status=DOWN,等待一段时间后再断开 Session),可大幅减少不一致窗口。

小结

要点说明
服务注册Ephemeral 节点,宕机自动清除
服务发现getChildren + Watcher 监听子节点变化
ID 生成Persistent Sequential 节点,Leader 原子分配
负载均衡在节点数据中存储 weight,客户端加权选择
缓存策略Watcher + 定时轮询兜底
清理Sequential 节点需定期清理

命名服务解决了"在哪里"和"叫什么"的问题。下一章进入客户端编程,学习如何用 Java 原生 API 与 ZooKeeper 交互。

上一页
配置中心