ZK分布式锁
共享锁:也称作读锁,当⼀⽅获得共享锁之后,其它⽅也可以获得共享锁。但其只允许读 取。在共享锁全部释放之前,其它⽅不能获得写锁。
排它锁:也称作写锁,获得排它锁后,可以进⾏数据的读写。在其释放之前,其它⽅不能获 得任何锁。
获取读锁:
1、基于资源ID创建临时序号读锁节点 /lock/888.R0000000002 Read
2、获取 /lock 下所有⼦节点,判断其最⼩的节点是否为读锁,如果是则获锁成功
3、最⼩节点不是读锁,则阻塞等待。添加lock/ ⼦节点变更监听。
4、当节点变更监听触发,执⾏第2步
获取写锁:
1、基于资源ID创建临时序号写锁节点/lock/888.R0000000002 Write
2、获取 /lock 下所有⼦节点,判断其最⼩的节点是否为⾃⼰,如果是则获锁成功
3、最⼩节点不是⾃⼰,则阻塞等待。添加当前节点的上一节点的监听
4、当节点变更监听触发,执⾏第2步
释放锁:读取完毕后,⼿动删除临时节点,如果获锁期间宕机,则会在会话失效后⾃动删除。
import org.I0Itec.zkclient.IZkDataListener;
import org.I0Itec.zkclient.ZkClient;
import java.util.List;
import java.util.stream.Collectors;
public class ZookeeperLock {
private String server = "192.168.0.149:2181";
private ZkClient zkClient;
private static final String rootPath = "/my-lock";
public ZookeeperLock() {
zkClient = new ZkClient(server, 5000, 20000);
buildRoot();
}
// 构建根节点
public void buildRoot() {
if (!zkClient.exists(rootPath)) {
zkClient.createPersistent(rootPath);
}
}
public Lock lock(String lockId, long timeout) {
Lock lockNode = createLockNode(lockId);
lockNode = tryActiveLock(lockNode);// 尝试激活锁
if (!lockNode.isActive()) {
try {
synchronized (lockNode) {
lockNode.wait(timeout);
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
if (!lockNode.isActive()) {
throw new RuntimeException(" lock timeout");
}
return lockNode;
}
public void unlock(Lock lock) {
if (lock.isActive()) {
zkClient.delete(lock.getPath());
}
}
// 尝试激活锁
private Lock tryActiveLock(Lock lockNode) {
// 判断当前是否为最小节点
List<String> list = zkClient.getChildren(rootPath)
.stream()
.sorted()
.map(p -> rootPath + "/" + p)
.collect(Collectors.toList());
String firstNodePath = list.get(0);
if (firstNodePath.equals(lockNode.getPath())) {
lockNode.setActive(true);
} else {
String upNodePath = list.get(list.indexOf(lockNode.getPath()) - 1);
zkClient.subscribeDataChanges(upNodePath, new IZkDataListener() {
@Override
public void handleDataChange(String dataPath, Object data) throws Exception {
}
@Override
public void handleDataDeleted(String dataPath) throws Exception {
// 事件处理 与心跳 在同一个线程,如果Debug时占用太多时间,将导致本节点被删除,从而影响锁逻辑。
System.out.println("节点删除:" + dataPath);
Lock lock = tryActiveLock(lockNode);
synchronized (lockNode) {
if (lock.isActive()) {
lockNode.notify();
}
}
zkClient.unsubscribeDataChanges(upNodePath, this);
}
});
}
return lockNode;
}
public Lock createLockNode(String lockId) {
String nodePath = zkClient.createEphemeralSequential(rootPath + "/" + lockId, "w");
return new Lock(lockId, nodePath);
}
}