实现原理
Zookeeper观察器可以监测zookeeper里面某个节点的变化,比如节点创建删除、数据变化等。如果产生变化,可以立即通知到客户端。
Zookeeper观察器包含三个方法:getData()
获取数据。getChildren()
获取子节点。exist()
判断当前是否存在。我们在调用这三个方法的时候都可以去添加观察器。
另外,观察器只能监控一次,再次监控需要重新设置。
好,现在聊回正题。如何使用zk去实现分布式锁呢?主要是利用了zookeeper的瞬时有序节点的特性。
- 在多线程并发创建瞬时节点的时候,会创建多个节点,并得到节点的有序序列。序号最小的线程获得这把锁,其他的线程则设置观察器监听自己序号的前一个序号,等前一个序号的节点消失了(即上一个线程完成了任务),那么本线程就可以开始执行了,以此类推,这样就可以完成分布式锁的情况。
Zookeeper分布式锁的实现
同样,引入包:
<dependency>
<groupId>org.apache.zookeeper</groupId>
<artifactId>zookeeper</artifactId>
<version>3.4.14</version>
</dependency>
ZkLock.java
代码如下,该类需要继承Watcher接口,用来监听回调。
- 我们在初始化的时候直接new zookeeper(1,2,3)的第3个参数中注入了Watcher,所以在之后只需要在调用
getData()
,getChildren()
,exist()
三个方法的时候设置true还是false(是否设置watcher)。 - 另外,使用了对象的块锁
synchronized(this)
来让线程等待,并用watcher接口实现的方法process()
来唤醒线程。 - 其他代码都很好理解,不做赘述。
@Slf4j
public class ZkLock implements AutoCloseable, Watcher {
private ZooKeeper zooKeeper;
private String address;
private String znode;
public ZkLock() throws IOException {
address="ip:port";
this.zooKeeper = new ZooKeeper(address, 10000, this);
}
public boolean getLock(String bussinessCode) {
try {
//获取业务根节点
Stat stat = zooKeeper.exists("/" + bussinessCode, false);
if (stat == null) {
//业务根节点不存在就创建一个节点
zooKeeper.create("/" + bussinessCode, bussinessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
}
//创建顺时有序节点,会在`_`自动加序号,比如/order/order_0001
znode = zooKeeper.create("/" + bussinessCode + "/" + bussinessCode + "_", bussinessCode.getBytes(),
ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL);
//判断创建创建的znode是不是最小序号的节点
//获取业务节点下所有的子节点
List<String> nodes=zooKeeper.getChildren("/"+bussinessCode,false);
Collections.sort(nodes);
//如果创建的节点是第一个子节点
String firstNode=nodes.get(0);
if (znode.endsWith(firstNode)){
return true;
}
//不是第一个子节点,则监听前一个子节点
String preNode=firstNode;
for (String node:nodes){
if (znode.endsWith(node)){
zooKeeper.exists("/"+bussinessCode+"/"+preNode,true);//设置监听
break;
}else{
preNode=node;
}
}
//线程等待,等待监听唤醒
synchronized (this){
wait();
}
return true;
} catch (KeeperException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
return false;
}
@Override
public void close() throws Exception {
zooKeeper.delete(znode,-1);
zooKeeper.close();
log.info("已经释放锁");
}
@Override
public void process(WatchedEvent watchedEvent) {
if (watchedEvent.getType()==Event.EventType.NodeDeleted){
//节点被删除
synchronized (this){
notify();
}
}
}
}
好了,我们测试下这个类是否可用:
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class ZkLockTest {
@Test
public void testZkLock() throws Exception {
ZkLock zkLock=new ZkLock();
boolean lock=zkLock.getLock("order");
log.info("获得锁的结果:"+lock);
zkLock.close();
}
}
运行结果如下:
我们再写一个controller,运行两个JVM以证明分布式锁的实现。
@RestController
@Slf4j
public class ZkLockController {
@RequestMapping("zkLock")
public String zkLock() throws IOException {
log.info("我进入了方法");
try (ZkLock zkLock=new ZkLock()){
boolean lock=zkLock.getLock("biz_code");
if (lock){
log.info("我进入了锁");
Thread.sleep(15000);
}
} catch (Exception e) {
e.printStackTrace();
}
log.info("方法执行完成");
return "方法执行完成";
}
}
好的,开启两个端口,运行如下:
从时间上可以看出,一个jvm线程释放锁的同时,另一个jvm中的线程才获取到锁。
使用curator客户端实现分布式锁
curator客户端已经实现了分布式锁,直接使用即可,具体使用方法参考:Using Curator🔗。
# Distributed Lock
InterProcessMutex lock = new InterProcessMutex(client, lockPath);
if ( lock.acquire(maxWait, waitUnit) )
{
try
{
// do some work inside of the critical section here
}
finally
{
lock.release();
}
}
不多做赘述。