思路
加锁
首先,在Zookeeper当中创建一个持久节点lock,当第一个客户端想要获得锁时,需要在lock这个节点下面创建一个临时顺序节点 。
之后,Client1查找lock下面所有的临时顺序节点并排序,判断自己所创建的节点是不是顺序最靠前的一个。如果是第一个节点,则成功获得锁。
如果再有一个客户端 Client2 前来获取锁,则在lock下再创建一个临时顺序节点。Client2查找lock下面所有的临时顺序节点并排序,判断自己所创建的节点是不是顺序最靠前的一个,结果发现节点Lock2并不是最小的。于是,Client2向排序仅比它靠前的节点注册Watcher,用于监听前一个节点是否存在。这意味着Client2抢锁失败,进入了等待状态。
这时候,如果又有一个客户端Client3前来获取锁,则在再创建一个临时顺序节点并重复Client2的操作,监听Client2创建的节点。
这样一来,Client1得到了锁,Client2监听了Lock1,Client3监听了Lock2。形成一个等待队列
释放锁
释放锁分为两种情况:
1.任务完成,客户端显式释放
当任务完成时,Client1会显示调用删除对应节点的指令。
2.任务执行过程中,客户端崩溃
获得锁的Client1在任务执行过程中如果崩溃,则会断开与Zookeeper服务端的链接。根据临时节点的特性,相关联的节点会随之自动删除。
由于Client2一直监听着Client1节点的存在状态,当该节点被删除时,*Client2会立刻收到通知。这时候Client2会再次查询lock下面的所有节点,确认自己创建的节点是不是目前最小的节点。*如果是最小,则Client2顺理成章获得了锁。
同理,如果Client2也因为任务完成或者节点崩溃而删除了自己的节点,那么Client3就会接到通知。
代码实现
DLock
package com.th.zkdistributedlock;
/**
* 锁操作接口
*/
public interface DLock {
/**
* 阻塞获取锁
* @return
*/
boolean lock();
/**
* 释放锁
* @return
*/
boolean unlock();
}
ZkLock
package com.th.zkdistributedlock;
import org.apache.zookeeper.*;
import org.apache.zookeeper.data.Stat;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
/**
* 锁方法实现类
*/
public class ZkLock implements DLock {
// 根节点绝对路径
private String path;
// 根节点名称
private String name;
// 临时有序节点绝对路径
private String lockPath ;
private ZooKeeper zk;
// 锁重入计数
private int state ;
public ZkLock(String path, String name, ZooKeeper zk) {
this.path = path;
this.name = name;
this.zk = zk;
this.state=0;
}
/**
* 加锁操作,锁重入如果成功state+1
* @return
*/
public boolean lock() {
boolean flag= lockInternal();
if(flag){
state++;
}
return flag;
}
/**
* 加锁实现
* @return
*/
private boolean lockInternal(){
try {
// 创建临时有序节点
String result = zk.create(getPath(), "".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
this.lockPath = result;
// 获取主节点下所有子节点并排序
List<String> waits = zk.getChildren(path, false);
Collections.sort(waits);
// 将当前创建的节点路径转为数组
String[] paths=result.split("/");
// 获取当前子节点name
String curNodeName = paths[paths.length-1];
// 如果当前节点是第一个子节点
if (waits.get(0).equalsIgnoreCase(curNodeName)) {
return true;
}
CountDownLatch latch = new CountDownLatch(1);
for (int i = 0; i < waits.size(); i++) {
String cur = waits.get(i);
// 遍历所有子节点,直到找到当前创建的节点
if (!cur.equalsIgnoreCase(curNodeName)) {
continue;
}
// 给当前节点的前一个节点添加watcher,监控该节点
String prePath = path+"/"+waits.get(i - 1);
zk.exists(prePath, new Watcher() {
public void process(WatchedEvent event) {
if (event.getType().equals(Event.EventType.NodeDeleted))
latch.countDown();
}
});
break;
}
latch.await();
return true;
} catch (Exception e) {
System.out.println(e.getMessage());
}
return false;
}
private String getPath() {
return path+"/"+name;
}
public boolean unlock() {
if(state>1){
state--;
return true;
}
try {
Stat stat=zk.exists(lockPath,false);
int version= stat.getVersion();
zk.delete(lockPath,version);
state--;
return true;
} catch (Exception e) {
System.out.println("unlock:"+lockPath+" ,exception,");
}
return false;
}
}
DLockFactory
package com.th.zkdistributedlock;
/**
* 持久节点创建工厂
*/
public interface DLockFactory {
/**
* 拿到对象
* @param key
* @return
*/
DLock getLock(String key);
}
ZkDLockFactory
package com.th.zkdistributedlock;
import lombok.Getter;
import lombok.Setter;
import org.apache.zookeeper.CreateMode;
import org.apache.zookeeper.ZooDefs;
import org.apache.zookeeper.ZooKeeper;
import org.apache.zookeeper.data.Stat;
/**
* 持久节点实现类
*/
@Setter
@Getter
public class ZkDLockFactory implements DLockFactory {
private String DLOCK_ROOT_PATH="/testLock";
private ZooKeeper zooKeeper;
public DLock getLock(String key) {
String path = getPath(key);
try {
zooKeeper.create(path,"".getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
} catch (Exception e) {
}finally {
try {
Stat stat= zooKeeper.exists(path,false);
if(stat == null){
return null;
}
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
DLock lock = new ZkLock(path,key,zooKeeper);
return lock;
}
private String getPath(String key) {
return DLOCK_ROOT_PATH+"/"+key;
}
}
DLockTest
package com.th.zkdistributedlock;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;
import org.apache.zookeeper.ZooKeeper;
import java.io.IOException;
import java.util.Objects;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
/**
* 测试类
*/
public class DLockTest {
public final static Random random = new Random();
public static void main(String[] args) {
ZooKeeper zk = getZkClient();
CountDownLatch latch = new CountDownLatch(10);
ZkDLockFactory factory = new ZkDLockFactory();
factory.setZooKeeper(zk);
for(int i = 0; i<10; i++){
int finalI = i;
Thread t = new Thread(()->{
exec(factory);
System.out.println("Thread_"+ finalI +"释放锁完成");
latch.countDown();
},"Thread_"+i);
t.start();
}
try {
latch.await();
TimeUnit.SECONDS.sleep(5);
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("测试完成");
}
/**
* 加锁释放锁操作
* @param factory
*/
public static void exec(ZkDLockFactory factory){
DLock lock=factory.getLock("lock");
if (Objects.nonNull(lock)) {
System.out.println("Thread:"+Thread.currentThread().getName()+",尝试获取锁");
boolean flag=lock.lock();
System.out.println("Thread:"+Thread.currentThread().getName()+",尝试获取锁,结果:"+flag);
try {
TimeUnit.MILLISECONDS.sleep(random.nextInt(30));
} catch (InterruptedException e) {
e.printStackTrace();
}
lock.unlock();
System.out.println("Thread:"+Thread.currentThread().getName()+",释放锁锁");
} else {
System.out.println("获取持久节点失败");
}
}
/**
* 获取链接
* @return
*/
public static ZooKeeper getZkClient(){
try {
ZooKeeper zooKeeper = new ZooKeeper("192.168.137.142:2181", 200000, new Watcher() {
@Override
public void process(WatchedEvent event) {
if(event.getState() == Event.KeeperState.SyncConnected){
System.out.println("连接成功");
}
}
});
return zooKeeper;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
}
依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.12</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.th</groupId>
<artifactId>zkDistributedLock</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>zkDistributedLock</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.7.0</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.2.11</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
</project>
测试结果
如有不足欢迎指出,如有问题随时提问