ZooKeeper分布式锁实现原理
分布式锁是ZooKeeper最经典的应用场景。
排他锁实现原理
简单排他锁:
Java
/locks/exclusive-lock
多个客户端竞争创建同一临时节点
只有一个成功 → 持有锁
失败者监听节点删除事件 → 等待锁释放
问题:羊群效应:
Java
锁释放后所有等待者同时竞争
大量请求冲击服务器
性能急剧下降
改进:顺序锁:
text
/locks/lock-0000000001
/locks/lock-0000000002
/locks/lock-0000000003
每个客户端创建顺序节点
只监听前一个节点
避免羊群效应
排他锁流程:
text
1. 创建临时顺序节点 /locks/lock-xxx
2. 获取 /locks 下所有子节点
3. 判断自己是否最小序号
是 → 获得锁
否 → 监听前一个节点
4. 前节点删除后收到通知
5. 再次检查是否最小序号
代码示例:
text
// 创建顺序节点
String path = zk.create("/locks/lock-", null,
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 检查是否最小序号
List<String> children = zk.getChildren("/locks", false);
if (isMin(path, children)) {
// 获得锁
} else {
// 监听前一个节点
String prev = getPrevNode(path, children);
zk.exists(prev, watcher);
}
注意:临时节点保证客户端宕机自动释放锁。
共享锁实现原理
读写锁模型:
| 锁类型 | 特性 |
|---|---|
| 读锁 | 可共享,多个读锁同时持有 |
| 写锁 | 互斥,需等待所有锁释放 |
节点命名:
text
/locks/read-0000000001 ← 读锁
/locks/write-0000000002 ← 写锁
获取读锁条件:
text
序号比自己小的节点中没有写锁节点
说明没有写锁阻塞 → 可以获取读锁
获取写锁条件:
text
自己是序号最小的节点
说明没有其他锁 → 可以获取写锁
读写锁流程:
text
读锁:
1. 创建临时顺序节点 read-xxx
2. 检查前节点是否有写锁
无写锁 → 获得读锁
有写锁 → 等待写锁释放
写锁:
1. 创建临时顺序节点 write-xxx
2. 检查自己是否最小序号
是 → 获得写锁
否 → 等待前节点释放
Curator实现:
text
InterProcessReadWriteLock rwLock =
new InterProcessReadWriteLock(client, "/locks");
// 读锁
rwLock.readLock().acquire();
try {
// 读操作
} finally {
rwLock.readLock().release();
}
// 写锁
rwLock.writeLock().acquire();
try {
// 写操作
} finally {
rwLock.writeLock().release();
}
提示:Curator提供现成的读写锁实现,无需手动实现。
顺序锁与公平性
公平锁原理:
text
按节点序号顺序获取锁
序号最小者最先获得
先申请者优先 → 公平性保证
避免羊群效应:
text
每个客户端只监听前一个节点
锁释放时只有一个客户端收到通知
避免大量竞争 → 性能稳定
公平锁优势:
| 优势 | 说明 |
|---|---|
| 公平性 | 先申请先获得 |
- 无饥饿 | 每个请求最终都能获得 | | 性能稳定 | 避免羊群效应 |
非公平锁对比:
text
简单排他锁:
竞争创建节点 → 随机成功
后申请者可能先获得 → 不公平
羊群效应 → 性能不稳定
顺序锁实现要点:
text
1. 使用临时顺序节点
2. 监听前一个节点(而非全部)
3. 前节点删除后检查是否最小
4. 临时节点保证自动释放
注意:公平锁增加等待时间,非公平锁可能更快但可能饥饿。
要点总结
- 排他锁竞争创建临时节点,存在羊群效应
- 顺序锁监听前一个节点,避免羊群效应
- 读写锁用read-/write-前缀区分
- 读锁可共享,写锁互斥
- 临时节点保证宕机自动释放
- Curator提供InterProcessMutex和读写锁实现
- 顺序锁保证公平性:先申请先获得
📝 发现内容有误?点击此处直接编辑