利用zookeeper原生客户端实现分布式锁:
1.全部客户端在zk服务端创建同一个节点,但是只有一节客户端会创建成功,其余节点监听locks节点,如下图:
当节点删除后触发每个客户端向服务端创建节点,但是却只有一个客户端会创建成功,产生羊群效应,可以利用zk的临时有序节点特性,于是就有了第二种做法。
2.利用zk的临时有序节点特性,每个客户端向同一个节点下创建临时有序节点,但是最小的节点会获取锁,每个节点监听对应的上一个节点。如下图:
对应代码:
public class DistributedLock implements Lock, Watcher {
/**
* zk客户端
*/
private ZooKeeper zk;
//当前节点
private String CURRENT_LOCk;
//等待的当前节点前一个节点
private String WAIT_LOCK;
private CountDownLatch countDownLatch;
//根节点
private String ROOT_LOCK = "/lock";
public DistributedLock() {
try {
zk = new ZooKeeper("192.168.93.132:2181", 5000, this);
// Thread.sleep(10000);
//判断根节点是否存在
Stat stat = zk.exists(ROOT_LOCK, false);
if (null == stat) {
//创建根节点为永久
zk.create(ROOT_LOCK, "0".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
@Override
public void lock() {
if (this.tryLock()) {
//表示获取锁成功
System.out.println(Thread.currentThread().getName() + "->" + CURRENT_LOCk + "获取锁成功");
return;
}
//等待获取锁
try {
if (StringUtils.isNotEmpty(WAIT_LOCK)) {
waitForLock(WAIT_LOCK);
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//监听比自己小的上一个节点状态,如果上一个节点删除关闭会话了,则触发监控当前节点获取锁
private void waitForLock(String waitLock) throws KeeperException, InterruptedException {
Stat stat = zk.exists(waitLock, true);
if (stat != null) {
System.out.println(Thread.currentThread().getName() + "->" + "等待" + waitLock + "释放锁");
//当前节点的上一个节点还存在
countDownLatch = new CountDownLatch(1);
//等待
countDownLatch.await();
System.out.println(Thread.currentThread().getName() + "->" + "获取锁成功");
}
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
try {
//创建临时有序节点
CURRENT_LOCk = zk.create(ROOT_LOCK + "/", "0".getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE,
CreateMode.EPHEMERAL_SEQUENTIAL);
//获取所有节点
List<String> childres = zk.getChildren(ROOT_LOCK, false);
//创建一个有序的set
SortedSet<String> sortedSet = new TreeSet<>();
for (String children : childres) {
sortedSet.add(ROOT_LOCK + "/" + children);
}
//获取最小的节点
String firstNode = sortedSet.first();
//如果最小的节点等于当前节点,表示获取到了锁,并且不需要监控上一个节点
if (CURRENT_LOCk.equals(firstNode)) {
return true;
}
//获取比当前节点小的节点的数据,不包含当前节点
SortedSet<String> lessThenMe = ((TreeSet<String>) sortedSet).headSet(CURRENT_LOCk);
//如果有比自己小的节点
if (!lessThenMe.isEmpty()) {
//获取比当前节点更小的节点的最后一个节点
WAIT_LOCK = lessThenMe.last();
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public void unlock() {
System.out.println(Thread.currentThread().getName() + "->释放锁");
try {
zk.delete(CURRENT_LOCk, -1);
CURRENT_LOCk = null;
zk.close();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public Condition newCondition() {
return null;
}
@Override
public void process(WatchedEvent watchedEvent) {
//如果上一个节点删除,触发watch
if (countDownLatch != null) {
countDownLatch.countDown();
}
}
}
测试代码:
public static void main(String[] args) throws IOException {
CountDownLatch countDownLatch = new CountDownLatch(10);
for (int i = 0; i < 10; i++) {
new Thread(() -> {
try {
countDownLatch.await();
DistributedLock distributedLock = new DistributedLock();
distributedLock.lock();
} catch (Exception e) {
e.printStackTrace();
}
},"Thread-"+i).start();
countDownLatch.countDown();
}
System.in.read();
}
最后对应的线程4获取到了锁。
当然使用Curator很方便的就会实现基于zk的分布式锁。