Skip to content

Zookeeper与分布式协调 ​

核心定位:Zookeeper(ZK)是分布式协调服务,提供配置管理、命名服务、分布式锁、集群管理、选主等能力。是 Dubbo、Kafka 的底层依赖。

1. ZK 核心概念 ​

┌─────────────────────────────────────────────┐
│ ZK 数据模型(树形结构,类似文件系统)          │
├─────────────────────────────────────────────┤
│ /                                           │
│ ├── /app1                                   │
│ │   ├── /app1/config                        │
│ │   ├── /app1/leader                        │
│ │   └── /app1/members                       │
│ │       ├── /app1/members/node1             │
│ │       └── /app1/members/node2             │
│ └── /app2                                   │
│                                              │
│ ★ 节点(ZNode):                             │
│   - 每个节点存储数据(<1MB)                  │
│   - 每个节点有版本号(CAS 乐观锁)            │
│   - 节点类型:                                │
│     持久节点(PERSISTENT)                    │
│     持久顺序节点(PERSISTENT_SEQUENTIAL)     │
│     临时节点(EPHEMERAL)                     │
│     临时顺序节点(EPHEMERAL_SEQUENTIAL)      │
│     容器节点(CONTAINER)                     │
│                                              │
│ ★ Watcher(监听器):                         │
│   - 节点创建/删除/数据变更时通知客户端        │
│   - 一次性触发(触发后需重新注册)            │
└─────────────────────────────────────────────┘

2. ZAB 协议 ​

┌─────────────────────────────────────────────┐
│ ZAB 协议(Zookeeper Atomic Broadcast)       │
├─────────────────────────────────────────────┤
│                                              │
│ 阶段1:崩溃恢复(Leader 选举)                │
│   - 所有节点初始状态为 LOOKING                │
│   - 各节点投票(myid + zxid)                │
│   - ★ zxid 大的优先,其次 myid 大的优先       │
│   - 超过半数同意 → 成为 Leader               │
│                                              │
│ 阶段2:消息广播(原子广播)                    │
│   - Leader 收到写请求                         │
│   - 生成 Proposal(zxid 递增)                │
│   - 发送给所有 Follower                       │
│   - Follower 写入本地日志,回复 ACK           │
│   - ★ 超过半数 ACK → Leader 发送 COMMIT       │
│   - Follower 应用事务                         │
│                                              │
│ ★ 写操作必须经过 Leader                       │
│ ★ 读操作可以从任意 Follower 读                │
└─────────────────────────────────────────────┘

3. Curator 客户端 ​

xml
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>5.5.0</version>
</dependency>
java
@Configuration
public class ZkConfig {

    @Bean
    public CuratorFramework curatorFramework() {
        CuratorFramework client = CuratorFrameworkFactory.builder()
            .connectString("localhost:2181")
            .sessionTimeoutMs(60000)
            .connectionTimeoutMs(15000)
            .retryPolicy(new ExponentialBackoffRetry(1000, 3))  // 重试策略
            .namespace("mall")  // 命名空间(自动加前缀)
            .build();
        client.start();
        return client;
    }
}

4. 分布式锁 ​

4.1 可重入锁 ​

java
@Service
public class ZkLockService {

    @Autowired
    private CuratorFramework client;

    /**
     * ★ Curator InterProcessMutex(可重入排他锁)
     */
    public <T> T executeWithLock(String path, long timeout, TimeUnit unit,
                                 Supplier<T> supplier) {
        InterProcessMutex lock = new InterProcessMutex(client, path);
        try {
            if (lock.acquire(timeout, unit)) {
                try {
                    return supplier.get();
                } finally {
                    lock.release();
                }
            } else {
                throw new RuntimeException("获取 ZK 锁超时:" + path);
            }
        } catch (Exception e) {
            throw new RuntimeException("ZK 锁异常", e);
        }
    }
}

4.2 公平锁(临时顺序节点) ​

┌─────────────────────────────────────────────┐
│ ZK 分布式锁实现原理(公平锁)                 │
├─────────────────────────────────────────────┤
│                                              │
│ 1. 客户端在 /lock 下创建临时顺序节点           │
│    /lock/node-00000001                       │
│    /lock/node-00000002                       │
│    /lock/node-00000003                       │
│                                              │
│ 2. 判断自己是否是最小节点                      │
│    - 是最小 → 获得锁                          │
│    - 不是 → 监听前一个节点的删除事件           │
│                                              │
│ 3. 前一个节点释放锁(删除节点)                │
│    - Watcher 通知下一个节点                   │
│    - 下一个节点获得锁                         │
│                                              │
│ ★ 临时节点:客户端断开后自动删除(防死锁)     │
│ ★ 顺序节点:实现公平排队                      │
│ ★ 监听前一个:避免羊群效应                    │
└─────────────────────────────────────────────┘

5. 集群选主 ​

java
@Service
public class LeaderElectionService {

    @Autowired
    private CuratorFramework client;

    private LeaderLatch leaderLatch;
    private LeaderSelector leaderSelector;

    /**
     * ★ LeaderLatch:选主后不可放弃
     */
    public void startLeaderLatch() throws Exception {
        leaderLatch = new LeaderLatch(client, "/leader/latch", "instance-" + getInstanceId());
        leaderLatch.start();

        // 等待选主完成
        leaderLatch.await(10, TimeUnit.SECONDS);

        if (leaderLatch.hasLeadership()) {
            log.info("★ 当前实例被选为 Leader");
            // 执行 Leader 独有的任务(如定时任务调度)
            startLeaderOnlyTasks();
        } else {
            log.info("当前实例为 Follower");
        }
    }

    /**
     * ★ LeaderSelector:选主后可主动放弃(轮换)
     */
    public void startLeaderSelector() {
        leaderSelector = new LeaderSelector(client, "/leader/selector",
            new LeaderSelectorListenerAdapter() {
                @Override
                public void takeLeadership(CuratorFramework client) throws Exception {
                    log.info("★ 获得领导权");
                    try {
                        // 执行 Leader 任务
                        while (true) {
                            Thread.sleep(1000);
                            // 定时调度...
                        }
                    } finally {
                        log.info("放弃领导权");
                    }
                }
            });
        leaderSelector.autoRequeue();  // ★ 放弃后自动重新排队
        leaderSelector.start();
    }

    /**
     * Leader 断线自动切换
     */
    @PostConstruct
    public void watchLeaderChange() {
        try {
            NodeCache nodeCache = new NodeCache(client, "/leader/latch");
            nodeCache.getListenable().addListener(() -> {
                ChildData data = nodeCache.getCurrentData();
                if (data == null) {
                    log.warn("Leader 节点消失,等待重新选主");
                } else {
                    log.info("Leader 节点:{}", new String(data.getData()));
                }
            });
            nodeCache.start();
        } catch (Exception e) {
            log.error("监听 Leader 变化异常", e);
        }
    }
}

6. 注册中心(Dubbo 场景) ​

┌─────────────────────────────────────────────┐
│ ZK 作为注册中心的工作流程                     │
├─────────────────────────────────────────────┤
│                                              │
│ 服务提供者注册:                               │
│   创建临时节点                                │
│   /dubbo/com.example.UserService/providers   │
│       /provider-192.168.1.1:20880            │
│       /provider-192.168.1.2:20880            │
│                                              │
│ 服务消费者订阅:                               │
│   获取 /providers 下所有子节点                 │
│   注册 Watcher 监听变化                        │
│                                              │
│ 服务提供者宕机:                               │
│   ★ 临时节点自动删除                          │
│   Watcher 通知消费者更新列表                   │
│                                              │
│ vs Nacos:                                    │
│   ZK:CP 模型(强一致),注册慢               │
│   Nacos:AP 模型(最终一致),注册快 ★ 推荐   │
└─────────────────────────────────────────────┘

7. 常见问题 ​

┌─────────────────────────────────────────────┐
│ ZK 常见问题                                   │
├─────────────────────────────────────────────┤
│ 1. Watcher 一次性触发                        │
│    - 触发后需重新注册                        │
│    - ★ 用 Curator 的 Cache(自动重新注册)   │
│                                              │
│ 2. 羊群效应                                  │
│    - 所有客户端监听同一节点                   │
│    - 节点变化时通知所有客户端                 │
│    - ★ 监听前一个节点(顺序节点)             │
│                                              │
│ 3. Session 过期                              │
│    - 网络分区导致 Session 超时                │
│    - 临时节点被删除                          │
│    - ★ 合理设置 sessionTimeout               │
│                                              │
│ 4. 集群最少3台                               │
│    - 2N+1 台(容忍 N 台宕机)                 │
│    - 3台容忍1台,5台容忍2台                   │
│                                              │
│ 5. 不适合存储大数据                          │
│    - 每个节点 < 1MB                          │
│    - 只存储元数据和协调信息                   │
└─────────────────────────────────────────────┘

关联笔记 ​

  • [[后端/中间件/Redis在Java项目中的整合]]:Redis 分布式锁对比
  • [[后端/微服务/Nacos注册中心与配置中心]]:Nacos vs ZK
  • [[后端/微服务/SpringCloud技术体系与版本选型]]:ZK 在微服务中的角色

练习:把「Zookeeper与分布式协调」变成可验证能力 ​

  1. 从本文任选一个示例,在可丢弃环境重跑;记录运行时/服务版本、输入、关键输出和清理动作。
  2. 保持其他条件不变,只制造一个边界:空值、重复请求、并发、超时、断连、权限不足或资源耗尽。
  3. 用四句话复盘:现象是什么、原理在哪一层、证据是什么、生产中如何止损。

验收清单

  • [ ] 示例能从空环境重复执行,必要依赖和版本已写明。
  • [ ] 同时保留成功路径与至少一个失败路径,不用“应该没问题”冒充证据。
  • [ ] 结论写明适用边界,并能链接回对应学习路线的能力域。
  • [ ] 练习结束后已清理临时数据、凭据、端口或进程。

对照 中间件练习答案与复盘尺。先交自己的证据再翻答案;答案是镜子,不是传送门。

用心记录每一步成长