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

    • 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 最经典的应用场景。掌握 Ephemeral Sequential 节点的锁竞争、Watch 前驱和释放流程。

定义与作用

分布式锁是在分布式系统中协调多进程/多线程对共享资源的互斥访问机制。ZooKeeper 通过 Ephemeral Sequential 节点天然支持分布式锁。

核心需求:

需求ZooKeeper 如何满足
互斥性同一时刻只有一个客户端持有锁(序号最小者)
无死锁Ephemeral 节点自动释放,Session 超时后锁自动归还
可重入通过客户端本地 ThreadLocal 计数实现(应用层)
公平性Sequential 节点天然保证 FIFO 顺序
阻塞/非阻塞Watch 前驱节点实现阻塞等待

核心原理

公平锁竞争流程

三个客户端竞争:各创建自己的临时顺序节点 → 最小序号者获锁 → 其他 Watch 前驱 → 前驱释放后自动唤醒。避免了惊群效应。

非公平锁 vs 公平锁

特性非公平锁公平锁
Watch 目标锁根节点(所有竞争者 Watch 同一个节点)前驱节点(每个竞争者 Watch 自己的前驱)
惊群效应有(释放时所有等待者同时唤醒)无(只唤醒前驱的下一个)
公平性不保证先到先得严格 FIFO
实现复杂度低中

完整示例

示例一:基于 zkCli.sh 的分布式锁演示

场景说明:电商秒杀服务,两个实例竞争同一把锁。

# Instance A —— 竞争锁
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] create -e -s /seckill-lock/req- ""
Created /seckill-lock/req-0000000001
[zkshell: 1] ls /seckill-lock
[req-0000000001]           # 只有我一个 → 获取锁

# Instance B —— 竞争锁
zkCli.sh -server 127.0.0.1:2181
[zkshell: 0] create -e -s /seckill-lock/req- ""
Created /seckill-lock/req-0000000002
[zkshell: 1] ls /seckill-lock
[req-0000000001, req-0000000002]
# 不是最小 → 等待 req-0000000001 释放
# Instance A 释放锁
[zkshell: 2] delete /seckill-lock/req-0000000001

# Instance B 立即检查
[zkshell: 2] ls /seckill-lock
[req-0000000002]           # 现在我是最小 → 获取锁!

操作前后对比:

时刻Instance AInstance B锁持有者
T1req-001—A
T2req-001req-002(等待)A
T3释放req-002B
T4—释放无

示例二:Java 原生 API 公平锁实现

场景说明:完整的公平分布式锁 Java 实现。

public class ZkDistributedLock {
    private final ZooKeeper zk;
    private final String lockPath;
    private final String lockPrefix = "/lock-";
    private String currentLockPath;
    private String waitLockPath;

    public ZkDistributedLock(ZooKeeper zk, String lockPath) {
        this.zk = zk;
        this.lockPath = lockPath;
    }

    public void lock() throws Exception {
        // 1. 创建临时顺序节点
        currentLockPath = zk.create(
                lockPath + lockPrefix, null,
                ZooDefs.Ids.OPEN_ACL_UNSAFE,
                CreateMode.EPHEMERAL_SEQUENTIAL);

        // 2. 获取所有子节点并排序
        List<String> children =
                zk.getChildren(lockPath, false);
        Collections.sort(children);

        String currentNode =
                currentLockPath.substring(lockPath.length() + 1);

        if (children.get(0).equals(currentNode)) {
            System.out.println("Acquired lock: " + currentLockPath);
            return; // 序号最小,获取锁
        }

        // 3. 找到前驱节点并 Watch
        int idx = children.indexOf(currentNode);
        waitLockPath = lockPath + "/" + children.get(idx - 1);

        CountDownLatch latch = new CountDownLatch(1);
        Stat stat = zk.exists(waitLockPath, event -> {
            if (event.getType() ==
                    Watcher.Event.EventType.NodeDeleted) {
                latch.countDown();
            }
        });

        if (stat == null) {
            // 前驱节点已释放(并发场景)
            return;
        }

        // 4. 阻塞等待
        latch.await();
        System.out.println("Acquired lock: " + currentLockPath);
    }

    public void unlock() throws Exception {
        zk.delete(currentLockPath, -1);
        System.out.println("Released lock: " + currentLockPath);
    }
}

使用示例:

ZooKeeper zk = new ZooKeeper("127.0.0.1:2181", 3000, e -> {});
zk.create("/my-lock", null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
        CreateMode.PERSISTENT);

ZkDistributedLock lock = new ZkDistributedLock(zk, "/my-lock");
lock.lock();
try {
    // 临界区代码
    System.out.println("Processing critical section...");
} finally {
    lock.unlock();
}

操作前后对比:

阶段/my-lock 下的节点锁状态
无竞争者lock-0000000001Client 1 持有
Client 2 竞争lock-0000000001, lock-0000000002Client 1 持有,Client 2 等待
Client 1 释放lock-0000000002Client 2 持有

示例三:tryLock 非阻塞模式

场景说明:超时获取锁,避免线程无限等待。

public boolean tryLock(long timeout, TimeUnit unit)
        throws Exception {
    currentLockPath = zk.create(
            lockPath + lockPrefix, null,
            ZooDefs.Ids.OPEN_ACL_UNSAFE,
            CreateMode.EPHEMERAL_SEQUENTIAL);

    List<String> children =
            zk.getChildren(lockPath, false);
    Collections.sort(children);

    String currentNode =
            currentLockPath.substring(lockPath.length() + 1);

    if (children.get(0).equals(currentNode)) {
        return true;
    }

    int idx = children.indexOf(currentNode);
    waitLockPath = lockPath + "/" + children.get(idx - 1);

    CountDownLatch latch = new CountDownLatch(1);
    Stat stat = zk.exists(waitLockPath, event ->
            latch.countDown());

    if (stat == null) return true;

    // 有限等待
    return latch.await(timeout, unit);
}

操作前后对比:

场景tryLock 结果说明
无竞争者立即返回 true直接获锁
有竞争者,5s 内释放等待后返回 true阻塞获锁
有竞争者,5s 未释放返回 false超时放弃

易错场景与面试考点

易错场景

1. 未处理 Session 过期导致锁丢失

客户端持有锁期间 Session 过期 → Ephemeral 节点被服务端删除 → 锁"静默"释放。应用层需感知 Expired 事件并停止临界区操作。

// Watcher 中检测
if (event.getState() ==
        Watcher.Event.KeeperState.Expired) {
    // 锁已丢失,停止临界区操作
}

2. 未创建锁根节点直接使用

create -e -s /lock/req- ""
# KeeperErrorCode = NoNode for /lock

必须先创建持久根节点:

create /lock ""

3. 释放不属于自己的锁

Ephemeral Sequential 节点中,只有序号最小的持有锁。不能跨节点删除——删除不属于自己的节点会导致排队的客户端误判。

4. 锁超时与业务超时不一致

ZooKeeper 锁的释放依赖 Session 超时(通常 10s~30s),如果业务操作耗时超过 Session Timeout,锁会被服务端自动释放。设置合理的 Session Timeout 至关重要。

面试高频题

Q:ZooKeeper 分布式锁相比 Redis 分布式锁的优势?

A:

  • ZooKeeper:通过 Ephemeral 节点保证死锁安全,通过 Watch 机制实现公平排队。成本是部署 ZooKeeper 集群。
  • Redis:通过 SET NX + 过期时间实现,性能更高但公平性弱。RedLock 算法可提升可靠性但实现复杂。
  • 选型:ZooKeeper 适合要求强一致性和公平性的场景;Redis 适合高吞吐、对可靠性容忍度更高的场景。

Q:如何避免惊群效应?

A:使用公平锁策略(Watch 前驱节点而非根节点)。当锁释放时,只有前驱节点的下一个竞争者被唤醒,而非所有等待者同时竞争。

Q:Curator 的 InterProcessMutex 与原生实现的区别?

A:Curator 封装了连接管理、重试、锁续期等细节。核心机制仍是 Ephemeral Sequential 节点 + Watch 前驱。优势在于生产级健壮性(自动处理连接断开、Session 过期等边界情况)。

小结

要点说明
核心机制Ephemeral Sequential + Watch 前驱
死锁安全Ephemeral 自动释放
公平锁按序号排队,Watch 前驱节点
非公平锁Watch 根节点,存在惊群效应
可重入应用层 ThreadLocal 实现
超时控制tryLock + CountDownLatch.await(timeout)

分布式锁解决了多实例互斥问题。下一章进入配置中心,理解 ZooKeeper 如何成为分布式系统的配置中枢。