一、Zookeeper相关概念
基于Zookeeper实现分布式锁,主要依赖于它的【瞬时有序节点】,当多个客户端并发创建瞬时有序节点时,会自动为我们生成有序的节点,例如定义子节点名为 order_ ,则生成的节点为 order_00000001,下一个节点为 order_00000002,为我们保证有序。
利用Zookeeper节点的递增性,可以规定节点编号最小的那个获得锁。当编号最小的那个释放锁后,通知第二个节点获得锁(节点监听机制),以此类推,首尾相连。所以说Zookeeper天然支持分布式锁。
Zookeeper的内部机制,能保证后面的节点能够正常的监听到删除事件通知。若当前的客户端与Zookeeper集群服务器失去连接时,这个临时节点也将自动删除。排在它后面的那个节点,也能收到删除事件,正常执行。
二、Zookeeper实现分布式锁的步骤
- 创建持久化根节点,所有获取锁创建的瞬时有序节点都在该节点下
- 在根节点下创建瞬时有序节点
- 判断当前节点是否为第一个节点,是则获取到锁
- 若不是第一个节点,则遍历所有子节点,查找到上一个节点并创建监听
- 调用 wait();使得当前线程等待
- 当上一个节点删除后,通知该节点唤醒线程获取到锁
- 方法结束后释放锁,即删除瞬时节点
三、代码实现
package com.example.distributezklock.lock;
import lombok.extern.slf4j.Slf4j;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {
/**
* Zookeeper连接
*/
private ZooKeeper zooKeeper;
/**
* 当前节点
*/
private String znode;
public ZkLock() throws IOException {
// 创建Zookeeper连接
this.zooKeeper = new ZooKeeper("localhost:2181",
10000,this);
}
/**
* 获取锁
* @param lockCode 锁 code
* @return 加锁结果
*/
public boolean getLock(String lockCode) {
try {
// 创建业务 根节点
Stat stat = zooKeeper.exists("/" + lockCode, false);
if (null == stat){
// 根节点不存在则创建,创建开放的,持久化节点
zooKeeper.create("/" + lockCode,lockCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.PERSISTENT);
}
// 创建瞬时有序节点 多个线程同时创建,则为生成有序的节点名 /order/order_00000001
znode = zooKeeper.create("/" + lockCode + "/" + lockCode + "_", lockCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
// 获取业务节点下 所有的子节点
List<String> childrenNodes = zooKeeper.getChildren("/" + lockCode, false);
// 子节点排序
Collections.sort(childrenNodes);
// 获取序号最小的(第一个)子节点
String firstNode = childrenNodes.get(0);
// 如果创建的节点是第一个子节点,则获得锁
if (znode.endsWith(firstNode)){
return true;
}
// 不是第一个子节点,则监听前一个节点
String lastNode = firstNode;
for (String node:childrenNodes){
if (znode.endsWith(node)){
// 监听前一个节点
zooKeeper.exists("/"+lockCode+"/"+lastNode,true);
break;
}else {
// 前节点
lastNode = node;
}
}
// 执行等待,等待唤醒,固定写法
synchronized (this){
wait();
}
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
* 自动关闭方法,实现AutoCloseable接口
* @throws Exception
*/
@Override
public void close() throws Exception {
// 删除当前瞬时节点,释放锁
zooKeeper.delete(znode,-1);
// 关闭Zookeeper连接
zooKeeper.close();
}
/**
* 通知方法,实现Watcher接口
* @param event 事件
*/
@Override
public void process(WatchedEvent event) {
// 监听到前一个节点被删除,则 [唤醒] 当前线程
if (event.getType() == Event.EventType.NodeDeleted){
synchronized (this){
notify();
}
}
}
}
四、ZooKeeper客户端框架curator
比较完善的ZooKeeper客户端框架,通过封装的一套高级API 简化了ZooKeeper的操作。通过查看官方文档,可以发现Curator主要解决了三类问题:
- 封装ZooKeeper client与ZooKeeper server之间的连接处理
- 提供了一套Fluent风格的操作API
- 提供ZooKeeper各种应用场景(recipe, 比如:分布式锁服务、集群领导选举、共享计数器、缓存机制、分布式队列等)的抽象封装
建议生产环境中使用该框架进行实现Zookeeper分布式锁。