bigywater.tech
ZooKeeper · 93

Curator 分布式锁实现

Curator 的 InterProcessMutex 是 Java 世界里最常用的分布式锁。这篇用真实的 Curator 5.7.1 做五件事:互斥是否成立、可重入怎么实现、获取超时是否留垃圾、读写锁的排队规则,以及持有者 JVM 被冻结(长 GC)后会发生什么。

200 / 200
10 线程累加的最终值
1 个节点
同线程重入两次时 ZooKeeper 里的锁节点数
3.2 s
持有者被冻结后另一个进程拿到锁的时间(会话超时 3 s)

互斥

实测 · 本机执行输出
  最终 200 / 200,用时 1.8 s

底层配方和kazoo 的锁一样:临时顺序节点,每个排队者只监听前一个。

可重入

实测 · 本机执行输出
  InterProcessMutex 同线程第二次 acquire 成功;/re 下的锁节点:[_c_a6d12c5d-8272-4a0b-ba51-4c2f213d89df-lock-0000000000](只有 1 个节点,本地计数)
  release 一次后 /re 下还有 1 个节点(还没释放完)
  release 第二次后 /re 下有 0 个节点
  InterProcessSemaphoreMutex 同线程第二次 acquire(1 s) → false,等了 1.0 s(自己把自己锁住了)
  多 release 一次 → IllegalMonitorStateException

获取超时

实测 · 本机执行输出
  B.acquire(1 s) → false,1.02 s 后返回;/to 下的节点数 = 1(B 超时后自己的排队节点已清理)
  锁节点名:[_c_49f6f480-6e0e-453b-b5f3-424034a23248-lock-0000000000]

读写锁

实测 · 本机执行输出
  0.0s 5 个读者都已持有读锁(同时在内的最大数 5)
  1.5s 写者拿到写锁(此时读者数 0)
  2.1s 写者之后到达的读者拿到读锁(等了 1.5 s)

读写锁在同一个路径下用两种前缀的顺序节点来排队,所以先后顺序由序号决定。

JVM 冻结

实测 · 本机执行输出
  持有者被冻结 3.2 s 后,另一个进程 Waiter 拿到了同一把锁(会话超时 3 s)
  持有者解冻后的日志:
    ACQUIRED
    STATE LOST
    STATE RECONNECTED
    AFTER_SLEEP isOwnedByCurrentThread=true isAcquiredInThisProcess=true

只有持有者的 JVM 被冻结,ZooKeeper 服务端正常运行,所以会话超时后别人合法地拿到了锁。冻结的 JVM 恢复后先发现 LOST,再 RECONNECTED,但业务线程不会被打断。

分步演示

Curator 分布式锁
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 / ZooKeeper 3.9.6(brew 安装的 jar,用 java -cp 直接启动自己的实例,OpenJDK 17 Temurin,JVM 堆 128 MiB)/ 本机回环端口 7300+,数据在临时目录;默认 tickTime=2000;计时用 time.perf_counter,数字每次运行会有小幅抖动。Curator 5.7.1 + OpenJDK 17;客户端与服务端同一台机器;会话超时 3 s;冻结用 kill -STOP
    • 没有验证:InterProcessMultiLock、InterProcessSemaphoreV2(信号量)、revoke(撤销锁)、锁在服务端重启期间的行为、Curator 与 kazoo 的锁互操作(两者节点命名不同:kazoo 是 uuid__lock__序号,Curator 是 _c_uuid-lock-序号;是否互相排斥没有测)
    京ICP备2026067502号-1京公网安备11010502063487号