ZK分布式锁的实现
1. 类图
2. 实现
2.1 简单实现
通过实现抽象实现AbstractLock,实现共有逻辑
简单实现的想法是每次都监听一个节点,当节点失效的时候通过Zk的监听模式通知客户端来重新争取锁。
AbstractLock
public abstract class AbstractLock {
public final static String PATH = "/MYLOCK3";
public static final String ADDRESS = "127.0.0.1:2181";
public static final int SESSION_TIMEOUT = 1000;
public final ZkClient zkClient = new ZkClient(ADDRESS, SESSION_TIMEOUT);
// public final static ZkClient zkClient = MyZkClient.getInstance();
public void getLock() {
String threadName = Thread.currentThread().getName();
if (tryLock()) {
System.out.println(threadName + "获取锁");
} else {
System.out.println(threadName + "获取锁失败,等待锁");
waitLock();
getLock();
}
}
public abstract Boolean tryLock();
public abstract void waitLock();
public abstract void releaseLock();
}
SimpleLock
public class SimpleZkLock extends AbstractLock {
private CountDownLatch countDownLatch;
@Override
public Boolean tryLock() {
System.out.println(Thread.currentThread().getName() + "正在获取锁");
try {
boolean exists = zkClient.exists(PATH);
System.out.println(exists);
// System.out.println(zkClient.getChildren(PATH));
if (exists) {
System.out.println("锁占用中。。。");
return false;
} else {
System.out.println("获取锁。。。");
zkClient.createEphemeral(PATH);
return true;
}
} catch (RuntimeException e) {
//如果存在会报错NodeExist,这时候返回false即可
// System.out.println(e);
// e.printStackTrace();
return false;
}
}
@Override
public void waitLock() {
//监听器
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
};
//监听
zkClient.subscribeDataChanges(PATH, iZkDataListener);
if (zkClient.exists(PATH)) {
countDownLatch = new CountDownLatch(1);
try {
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "等待锁...");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//释放监听
zkClient.unsubscribeDataChanges(PATH, iZkDataListener);
}
@Override
public void releaseLock() {
System.out.println(Thread.currentThread().getName() + "释放锁成功");
zkClient.delete(PATH);
zkClient.close();
}
}
2.2 更加好的实现
复用AbstractLock,上一份代码存在的问题是每一次当锁被释放的时候就需要通知所有的客户端,但是只有一个是能获取锁的。浪费了很多CPU的性能,其实只要通知一个节点就行。ZK这边有一个顺序临时节点,就能完成这种想法。
代码:
HighQualityLock
public class HighQualityLock extends AbstractLock {
private String currentPath;
private String beforePath;
private CountDownLatch countDownLatch;
public HighQualityLock() {
try {
//初始化根节点
boolean exists = zkClient.exists(PATH);
if (!exists) {
zkClient.createPersistent(PATH);
}
} catch (RuntimeException e) {
e.printStackTrace();
}
}
@Override
public Boolean tryLock() {
//创建本次节点
if (null == currentPath) {
currentPath = zkClient.createEphemeralSequential(PATH + "/", "lock");
}
//在之前创建了一个直接断currentPath,所以永远不为空
List<String> children = zkClient.getChildren(PATH);
Collections.sort(children);
// System.out.println(children);
if (currentPath.equals(PATH + "/" + children.get(0))) {
return true;
} else {
//获取当前节点的前面一个节点,需要寻找当前节点的前面一个(列表中尾部已经有写入,因为是多线程触发)
int wz = Collections.binarySearch(children, currentPath.substring(PATH.length() + 1));
beforePath = PATH + "/" + children.get(wz - 1);
System.out.println(Thread.currentThread().getName() + " beforePath:" + beforePath);
}
return false;
}
@Override
public void waitLock() {
IZkDataListener iZkDataListener = new IZkDataListener() {
@Override
public void handleDataChange(String s, Object o) throws Exception {
}
@Override
public void handleDataDeleted(String s) throws Exception {
System.out.println("检测到了节点失效");
if(countDownLatch != null) {
countDownLatch.countDown();
}
}
};
System.out.println(Thread.currentThread().getName() + "监听前一个结点" + beforePath);
zkClient.subscribeDataChanges(beforePath, iZkDataListener);
if (zkClient.exists(beforePath)) {
countDownLatch = new CountDownLatch(1);
try {
System.out.println(Thread.currentThread().getName() + "等待释放锁");
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + "运行到这");
zkClient.unsubscribeDataChanges(beforePath, iZkDataListener);
}
@Override
public void releaseLock() {
System.out.println(Thread.currentThread().getName() + "释放锁:" + currentPath);
zkClient.delete(currentPath);
zkClient.close();
}
}
2.3 现有框架实现
在ZK上面实现分布式锁的良好框架:Curator
有如下的优点:
- 对于一个节点的CRUD监控
- 实现分布式锁
- 实现barrier、原子计数器
- 实现队列
有一篇好的ZK关于curator这边的文章:https://blog.youkuaiyun.com/haoyuyang/article/details/53469269,实现分布式锁,可重入锁,栅栏等等。