基于zookeeper临时有序节点可以实现的分布式锁。
大致思想即为:每个客户端对某个方法加锁时,在zookeeper上的与该方法对应的指定节点的目录下,生成一个唯一的瞬时有序节点。 判断是否获取锁的方式很简单,只需要判断有序节点中序号最小的一个。 当释放锁的时候,只需将这个瞬时节点删除即可。同时,其可以避免服务宕机导致的锁无法释放,而产生的死锁问题。
代码实现
- 分布式锁锁服务LockServer
import com.tc.zooker.config.ConnectWatcher;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.KeeperException;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.data.Stat;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
public class LockServer extends ConnectWatcher {
private String rootPath = "/lock";
private CountDownLatch countDownLatch;
private String currentLock;
public LockServer() {
this.connect();
}
@Test
public void createRoot() {
try {
this.zooKeeper.create(rootPath, null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Override
public void process(WatchedEvent watchedEvent) {
super.process(watchedEvent);
if(watchedEvent.getType()== Event.EventType.NodeDeleted){
if(countDownLatch!=null){
countDownLatch.countDown();
}
}
}
public void lock(CallBack callBack) {
if(tryLock()){
doAction(callBack);
}else{
waitLock();
doAction(callBack);
}
}
//获取锁的进程执行业务操作,来调用回调函数
private void doAction(CallBack callBack) {
callBack.lockEnd();
}
//元素等待获取锁,使用countDownLatch来阻塞当前线程的运行
private void waitLock() {
countDownLatch=new CountDownLatch(1);
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public boolean tryLock() {
Long id = this.zooKeeper.getSessionId();
String path = "lock-" + id;
try {
if (!isExist(path)) {
currentLock=this.zooKeeper.create(rootPath+"/"+path+"_", null, ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
}
List<String> locks=this.zooKeeper.getChildren(rootPath,false);
Collections.sort(locks);
//判断当前znode是不是最小的一个
if(locks.get(0).equals(currentLock.substring(currentLock.lastIndexOf("/")+1))){
return true;
}else{
List<String> lockNames=new ArrayList<>();
locks.forEach(lock->{
lockNames.add(lock.split("_")[0]);
});
int index=Collections.binarySearch(lockNames,path);
//监听比当前元素小的元素的变化,来通知等待的元素来获取锁
Stat stat = this.zooKeeper.exists(rootPath + "/" + locks.get(index - 1), this);
if(stat!=null){
return false;
}
return true;
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
//解决连接失败重试,造成死锁的问题
public boolean isExist(String path) {
List<String> lockNames = new ArrayList<String>();
List<String> locks = null;
try {
locks = this.zooKeeper.getChildren(rootPath, false);
locks.forEach((lock) -> {
lockNames.add(lock.split("-")[0]);
});
int index = Collections.binarySearch(lockNames, path);
if (index >-1) {
currentLock=rootPath+"/"+locks.get(index);
return true;
}
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
//解锁
public void unLock() {
try {
this.zooKeeper.delete(currentLock,-1);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (KeeperException e) {
e.printStackTrace();
}
}
}
- 利用接口来实现回调函数CallBack
public interface CallBack {
void lockEnd();
}
- 我们需要加锁的方法 LockClient
public class LockClient {
public void testLock(int id){
LockServer lockServer=new LockServer();
lockServer.lock(new CallBack() {
@Override
public void lockEnd() {
//处理自己的业务逻辑
try {
Thread.sleep(800);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("编号"+id+"获取到分布式锁");
lockServer.unLock();
}
});
}
- 并发线程测试工具类LockThread
import java.util.concurrent.CountDownLatch;
public class LockThread implements Runnable{
CountDownLatch countDownLatch;
int id;
public LockThread(CountDownLatch countDownLatch,int id){
this.countDownLatch=countDownLatch;
this.id=id;
}
public void test() {
Thread thread=new Thread(this);
thread.start();
}
@Override
public void run() {
try {
countDownLatch.await();
LockClient lockClient=new LockClient();
lockClient.testLock(id);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 测试类
import java.util.concurrent.CountDownLatch;
public class Main {
public static void main(String[] args) {
CountDownLatch countDownLatch=new CountDownLatch(20);
for (int i=0;i<20;i++){
new LockThread(countDownLatch,i+1).test();
countDownLatch.countDown();
}
}
}