原理很简单,就是根据创建的顺序节点的ID来对访问进行排序,形成一个等待链表,后面的节点等待前面的节点完成(即监听到delete事件)
直接贴代码:
package com.xycode.zkUtils.lock;
import com.xycode.zkUtils.listener.SimpleEventListener;
import com.xycode.zkUtils.listener.ZKListener;
import com.xycode.zkUtils.zkClient.ZKCli;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.testng.annotations.Test;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* ClassName: ZKDistributedLock
* 基于Zookeeper的顺序分布式锁
* @Author: xycode
* @Date: 2019/10/30
* Description:
**/
public class ZKDistributedLock {
private static final Logger logger= LoggerFactory.getLogger("myLogger");
private String lockPath;//顺序锁的父路径
private String curID;//当前ZKCli占有的顺序ID
private String prevID;//前一个顺序ID,当前ZKCLi要等待它
private ZKCli zkCli;
public ZKDistributedLock(String lockPath, ZKCli zkCli){
this.lockPath=lockPath;
this.zkCli=zkCli;
}
/**
* 尝试获得锁的函数,会创建一个临时顺序节点,
* 然后判断这个临时节点是否在等待队列的开头,若在开头,说明获锁成功,返回true
* @return
*/
public boolean tryLock(){
try {
//创建临时顺序节点,理论上不会出现竞争
String[] tmp=zkCli.createEphemeralSeq(lockPath+"/1","").split("/");
curID=tmp[tmp.length-1];//获得当前创建的顺序节点ID
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
//从最小的ID开始选,也就是最先创建顺序节点成功的那个ZKCli
List ids = zkCli.getChildren(lockPath);
String chosenId = Collections.min(ids);
// System.out.println("-->" + ids.size());
if(curID.equals(chosenId)) {//获得锁了
logger.debug("Agent-{} acquire lock",curID);
return true;
}else{
return false;
}
}
/**
* 等待锁的函数,当tryLock()没能获得锁时调用这个函数
* 具体操作就是注册一个监听器,用于监听前一个顺序ID对应的路径,监听其删除事件,在这之前一直会阻塞等待
*/
public void waitLock(){
final CountDownLatch[] countDownLatch={new CountDownLatch(1)};
//创建监听器对象
ZKListener listener=new SimpleEventListener() {
@Override
public void NodeDeletedHandler(WatchedEvent event) {
// System.out.println("--delete--");
if(countDownLatch[0]!=null)
countDownLatch[0].countDown();
}
};
//找到prevID
List ids=zkCli.getChildren(lockPath);
Collections.sort(ids);
for(int i=0;i 2 -> 3(3意外挂掉了) -> curID -> ...,即变成了1 -> 2 -> -> curID -> ...
//这时curID是会监听到删除事件的,但是前面实际上还有ZKCli在等待,
//再次waitLock()就是想变成这样1 -> 2 -> curID -> ...
waitLock();
logger.warn("[Fix chainBroken Exception]: try waitLock again!");
}else{
logger.debug("Agent-{} acquire lock",curID);
}
}
/**
* 加锁操作
*/
public void lock(){
if(!tryLock()){//每个尝试lock的线程,都会先tryLock一下,通过创建的顺序节点来确定是否获得lock
waitLock();//lock失败了就wait,等待前一个节点delete,并且注意ChainBroken的异常情况
}
}
/**
* 解锁操作,就是删除当前占用的顺序节点
*/
public void unlock(){
final String curPath=lockPath+"/"+curID;
try {
if(zkCli.exists(curPath))
zkCli.delete(curPath);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
logger.debug("Agent-{} release lock",curID);
zkCli.close();
}
//test
public static void main(String[] args) {
String ZKC_ADDRESS="127.0.0.1:2181,127.0.0.1:2182,127.0.0.1:2183";
ZKCli zkCli=new ZKCli(ZKC_ADDRESS);
String path="/seqLockPath";
if(!zkCli.exists(path)) {
try {
//先创建一个永久节点
zkCli.createPersistent(path, "", ZooDefs.Ids.OPEN_ACL_UNSAFE);
} catch (KeeperException | InterruptedException e) {
e.printStackTrace();
}
}
zkCli.close();
Thread[] t=new Thread[10];
for(int i=0;i {
ZKCli zkCli1 =new ZKCli(ZKC_ADDRESS);
zkCli1.unregisterDefaultListener();
ZKDistributedLock lock=new ZKDistributedLock(path, zkCli1);
//tip: 用法
try{
lock.lock();
//zkCli此时获得了锁,可以做一些独占的事情
System.out.println("Agent-" + lock.curID + " working...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}finally {
lock.unlock();
}
});
}
for (Thread thread : t) {
thread.start();
}
}
}
输出:
Agent-10000000084 working...
2020-02-23 23:47:23,731 [DEBUG][Thread-1] 10000000090 waiting for 10000000089 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,732 [DEBUG][Thread-2] 10000000089 waiting for 10000000088 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,733 [DEBUG][Thread-4] 10000000087 waiting for 10000000086 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,734 [DEBUG][Thread-6] 10000000086 waiting for 10000000085 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,734 [DEBUG][Thread-10] 10000000091 waiting for 10000000090 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,734 [DEBUG][Thread-7] 10000000085 waiting for 10000000084 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,734 [DEBUG][Thread-8] 10000000092 waiting for 10000000091 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,735 [DEBUG][Thread-9] 10000000088 waiting for 10000000087 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:23,735 [DEBUG][Thread-3] 10000000093 waiting for 10000000092 (com.xycode.zkUtils.lock.ZKDistributedLock:94)
2020-02-23 23:47:24,732 [DEBUG][Thread-5] Agent-10000000084 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:24,737 [DEBUG][Thread-7] Agent-10000000085 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000085 working...
2020-02-23 23:47:24,838 [DEBUG][Thread-5-EventThread] Thread-5-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:25,749 [DEBUG][Thread-7] Agent-10000000085 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:25,751 [DEBUG][Thread-6] Agent-10000000086 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000086 working...
2020-02-23 23:47:25,853 [DEBUG][Thread-7-EventThread] Thread-7-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:26,756 [DEBUG][Thread-6] Agent-10000000086 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:26,756 [DEBUG][Thread-4] Agent-10000000087 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000087 working...
2020-02-23 23:47:26,860 [DEBUG][Thread-6-EventThread] Thread-6-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:27,769 [DEBUG][Thread-4] Agent-10000000087 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:27,772 [DEBUG][Thread-9] Agent-10000000088 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000088 working...
2020-02-23 23:47:27,875 [DEBUG][Thread-4-EventThread] Thread-4-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:28,784 [DEBUG][Thread-9] Agent-10000000088 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:28,785 [DEBUG][Thread-2] Agent-10000000089 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000089 working...
2020-02-23 23:47:28,888 [DEBUG][Thread-9-EventThread] Thread-9-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:29,791 [DEBUG][Thread-2] Agent-10000000089 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:29,793 [DEBUG][Thread-1] Agent-10000000090 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000090 working...
2020-02-23 23:47:29,894 [DEBUG][Thread-2-EventThread] Thread-2-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:30,799 [DEBUG][Thread-1] Agent-10000000090 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:30,799 [DEBUG][Thread-10] Agent-10000000091 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000091 working...
2020-02-23 23:47:30,903 [DEBUG][Thread-1-EventThread] Thread-1-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:31,804 [DEBUG][Thread-10] Agent-10000000091 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:31,806 [DEBUG][Thread-8] Agent-10000000092 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000092 working...
2020-02-23 23:47:31,909 [DEBUG][Thread-10-EventThread] Thread-10-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:32,817 [DEBUG][Thread-8] Agent-10000000092 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:32,818 [DEBUG][Thread-3] Agent-10000000093 acquire lock (com.xycode.zkUtils.lock.ZKDistributedLock:115)
Agent-10000000093 working...
2020-02-23 23:47:32,921 [DEBUG][Thread-8-EventThread] Thread-8-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
2020-02-23 23:47:33,829 [DEBUG][Thread-3] Agent-10000000093 release lock (com.xycode.zkUtils.lock.ZKDistributedLock:139)
2020-02-23 23:47:33,932 [DEBUG][Thread-3-EventThread] Thread-3-EventThread: (eventType: None) (com.xycode.zkUtils.zkClient.internel.ZKClientFactory$1:39)
可见达到了顺序锁的目的,当然这里实在同一个JVM中测试的,当处于不同机器中测试时,效果也会是一样的.
这里是ZKCli是我自己对Zookeeper进行封装的一个类,见我的github: https://github.com/xycodec/zkUtils, 这个分布式锁的实现也集成到了这个库中.