07-zk实现分布式锁

本文详细介绍了一种基于ZooKeeper实现的分布式锁方案。通过定义接口和抽象类模板,文章进一步介绍了两种具体实现:基于ZooKeeper异常的实现和基于监听的实现。这两种方法分别利用ZooKeeper的特性来确保在分布式环境中锁的一致性和原子性。

一、定义接口

  • 接口包含获取锁和释放锁
    public interface Lock {

    public void getLock();

    public void unLock();

}

二、流程模板

  • 在抽象类中使用模板方法定义好获取锁和释放锁的流程
    public abstract class ZookeeperAbstractLock implements Lock {

    private static final String ZookeeperConnectString = "192.168.xx.xx:2181";

    //创建zk连接
    protected ZkClient zkClient = new ZkClient(ZookeeperConnectString);
    protected static final String PATH = "/lock";
    protected static final String PATH2 = "/lock2";

    public void getLock() {
        if (tryLock()) {
            System.out.println("获取zk锁资源...");
        } else {
            //等待
            waitLock();
            //重新获取锁资源
            getLock();
        }
    }

    //获取锁资源
    abstract boolean tryLock();

    //等待
    abstract void waitLock();

}

三、实现

  • 子类继承抽象类,实现获取锁,等待锁和释放锁的方法即可。
基于zk异常的实现
    public class ZookeeperDistributeLock extends ZookeeperAbstractLock {

    private CountDownLatch countDownLatch = null;

    @Override
    boolean tryLock() {
        try {
            //创建临时节点,创建成功则返回
            zkClient.createEphemeral(PATH);
            return true;
        } catch (Exception e) {
            //e.printStackTrace();
            //System.out.println("获取锁失败...");
            return false;
        }
    }

    @Override
    void waitLock() {
        IZkDataListener listener = new IZkDataListener() {
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            public void handleDataDeleted(String dataPath) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };

        //注册事件
        zkClient.subscribeDataChanges(PATH, listener);
        if (zkClient.exists(PATH)) {
            countDownLatch = new CountDownLatch(1);
            try {
                //如果起那么的节点一直存在,自己就等着,直到前面的节点被删除,方法再返回,
                // 这里的await就相当于一直阻塞在这里等着,删除的动作在listen里面实现
                countDownLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //删除监听
        zkClient.unsubscribeDataChanges(PATH, listener);

    }

    public void unLock() {
        if (zkClient != null) {
            zkClient.delete(PATH);
            zkClient.close();
            System.out.println("释放锁资源...");
        }
    }
}
基于监听的实现
    public class ZookeeperDistributeListenLock extends ZookeeperAbstractLock {

    private String beforePath;
    private String currentPath;
    private CountDownLatch countDownLatch = null;

    public ZookeeperDistributeListenLock() {
        if (!zkClient.exists(PATH2)) {
            zkClient.createPersistent(PATH2);
        }
    }

    @Override
    boolean tryLock() {
        //如果currentPath为空,则尝试加锁,第一次加锁赋值currentPath,并创建临时有序节点
        if (currentPath == null || currentPath.length() < 1) {
            currentPath = zkClient.createEphemeralSequential(PATH2 + '/', "lock");
        }
        //获取所有临时节点并且排序
        List<String> childrens = zkClient.getChildren(PATH2);
        Collections.sort(childrens);
        //如果自己是第一个节点,那么就认为自己获取到锁了
        if (currentPath.equals(PATH2 + '/' + childrens.get(0))) {
            return true;
        } else {
            //如果当前节点不是排名第一,那么就获取其前面一个节点,并在后面监听前面的节点
            int wz = Collections.binarySearch(childrens, currentPath.substring(7));
            beforePath = PATH2 + '/' + childrens.get(wz - 1);
        }
        return false;
    }

    @Override
    void waitLock() {
        IZkDataListener listener = new IZkDataListener() {
            public void handleDataChange(String dataPath, Object data) throws Exception {

            }

            public void handleDataDeleted(String dataPath) throws Exception {
                if (countDownLatch != null) {
                    countDownLatch.countDown();
                }
            }
        };
        //监听前面一个节点是否被删除,前面节点一旦被删除,自己就有机会获取锁了
        zkClient.subscribeDataChanges(beforePath, listener);
        if (zkClient.exists(beforePath)) {
            countDownLatch = new CountDownLatch(1);
            try {
                //如果起那么的节点一直存在,自己就等着,直到前面的节点被删除,方法再返回,
                // 这里的await就相当于一直阻塞在这里等着,删除的动作在listen里面实现
                countDownLatch.await();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
        //删除监听
        zkClient.unsubscribeDataChanges(beforePath, listener);
    }

    public void unLock() {
        if (zkClient != null) {
            zkClient.delete(currentPath);
            zkClient.close();
            System.out.println("释放锁资源...");
        }
    }
}

四、源码

### Zookeeper 分布式锁实现原理 Zookeeper 是一种高效的协调服务工具,能够帮助开发者在分布式环境中管理配置、同步状态以及提供一致性保障。通过其核心特性和数据模型,Zookeeper 可以被用来实现分布式锁。 #### 1. 核心特性支持 Zookeeper 提供了两种类型的节点:**持久节点**和**临时节点**,同时还支持创建有序节点(sequential nodes)。这些特性共同构成了分布式锁的基础[^3]。 - **临时节点**:当客户端断开连接时,该节点会自动删除。这一特性确保了即使某个进程崩溃或网络中断,锁也能被安全释放。 - **有序节点**:每次创建新节点时都会附加一个递增的序列号,这使得多个竞争者可以通过比较序列号来判断谁拥有锁。 #### 2. 锁的获取流程 为了获得锁,客户端会在指定路径下创建一个带有 `EPHEMERAL_SEQUENTIAL` 属性的子节点。随后,它会读取当前路径下的所有子节点并按序排列。如果发现自己创建的节点是最小的一个,则表示成功获得了锁;否则需要监听前驱节点的变化事件[^4]。 一旦前驱节点消失(即持有锁的客户端主动释放或者因异常退出),当前等待中的客户端会被触发回调函数,并重新尝试获取锁[^1]。 #### 3. 锁的释放流程 释放锁的过程相对简单,只需调用 API 删除之前创建的那个特定子节点即可。由于这是个原子操作,在正常情况下不会引发任何竞态条件问题[^2]。 #### 4. 异常处理机制 考虑到实际运行环境可能存在各种不确定性因素,因此还需要设计合理的错误恢复策略。例如,对于会话超时导致的数据丢失情况,应用程序应该有能力检测到这种状况并对受影响的操作采取补偿措施。 ```java // Java 示例代码展示如何使用 Curator Framework 来简化 zk 客户端编程过程 import org.apache.curator.framework.CuratorFramework; import org.apache.curator.framework.recipes.locks.InterProcessMutex; public class DistributedLockExample { public static void main(String[] args) throws Exception { CuratorFramework client = ...; // 初始化 curator 客户端 InterProcessMutex lock = new InterProcessMutex(client, "/lock_path"); try { lock.acquire(); // 获取锁 System.out.println("Lock acquired."); // 执行业务逻辑 } finally { lock.release(); // 确保最终总是要释放锁 System.out.println("Lock released."); } } } ``` 以上就是基于 zookeeper 实现分布式锁的主要思路及其具体步骤描述。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值