Java架构直通车——基于Zookeeper实现分布式锁

实现原理

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();
    }
}

不多做赘述。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值