zk实现分布式锁

一、为什么需要分布式锁

    如果服务运行在多台服务器上,或者运行在多个JVM上,对于一些公共资源,就需要有锁保证资源的独享性。比如,网上银行存钱的时候,客户端发出存钱的指令,被分发到多台服务器上,同时执行了存钱的服务,就可能会导致数据库中有两条相同的记录。所以需要保证同一时刻,只有一个客户端在执行这条指令。

二、分布式锁方案

1、每一次获取锁的时候,都在根节点下创建临时序列化节点,释放锁的时候删除该节点;

2、获取锁时,调用根节点的getChildren()方法,查看所有的子节点,如果发现自己创建的节点是子节点中序列号最小,就定义客户端获取到了锁;否则,找到比自己小的前一个节点,注册监听事件;如果比自己小的节点不存在,再次判断自己所创建的节点是否最小。

zk分布式锁的原理不做多述,直接上代码:

I. 分布式锁的接口

public interface DistributedLock {

    /**
     * 获取分布式锁
     * @return
     */
    boolean dLock();

    /**
     * 在一定时间内获取分布式锁
     * @param time
     * @return
     */
    boolean dLock(long time);

    /**
     * 释放分布式锁
     */
    void unDLock();
}

II. 分布式锁的实现

public class DefaultDistributedLock implements DistributedLock, Watcher {

    private ZooKeeper zkClient;

    // 分布式锁持久化节点名称
    private static String LOCK_PERSIST= "/DIS_LOCK";

    // 临时节点前缀
    private static String LOCK_ELEPHANT_PREFIX = LOCK_PERSIST+"/dis_";

    // zk连接的ip
    private String ips;

    // session过期时间
    private static int sessionTimeout = 300000;

    // 主线程等待连接建立好后才启动
    private CountDownLatch connectedSemaphore = new CountDownLatch(1);

    // 当前线程创建临时节点后返回的路径
    private String selfPath;
    // 等锁路径
    private String waitPath;

    private String lockName;

    private CountDownLatch latch;

    public DefaultDistributedLock(String ips, String lockName) {
        this(ips,sessionTimeout, lockName);
    }

    public DefaultDistributedLock(String ips, int sessionTimeout, String lockName) {
        this.ips = ips;
        this.sessionTimeout = sessionTimeout;
        this.lockName = lockName;
        createRootNode(LOCK_PERSIST,"根节点");
    }



    public boolean dLock() {
        try {
            selfPath = zkClient.create(LOCK_ELEPHANT_PREFIX, null, ZooDefs.Ids.OPEN_ACL_UNSAFE,
                    CreateMode.EPHEMERAL_SEQUENTIAL);
            System.out.println(lockName+" 创建临时节点路径"+selfPath);
            return checkMinPath(selfPath);
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }
    }

    public boolean dLock(long time){
        try {
            if(dLock()){
                return true;
            }

            return waitForLock(time);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }

    public void unDLock()  {
        System.out.println(lockName+"删除本节点:" + selfPath);
        try {
            zkClient.delete(selfPath, -1);
            selfPath = null;
            releaseConnection();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (KeeperException e) {
            e.printStackTrace();
        }
    }

    public void releaseConnection() {
        if (this.zkClient != null) {
            try {
                this.zkClient.close();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(lockName+"释放连接");
    }

    // 判断比自己小的一个节点是否存在,如果不存在,直接返回,无需等待
    private boolean waitForLock(long t) throws KeeperException, InterruptedException {
        Stat  stat = zkClient.exists(waitPath, true);
        if(stat != null){
            this.latch = new CountDownLatch(1);
            // 如果超过等待时间会抛出异常
            latch.await(t, TimeUnit.MILLISECONDS);
            this.latch = null;
        }
        return true;
    }


    // 校验本线程创建的节点是否是所有节点中的最小节点
    private boolean checkMinPath(String selfPath) throws KeeperException, InterruptedException {
        List<String> subNodes = zkClient.getChildren(LOCK_PERSIST,false);
        Collections.sort(subNodes);
        String str = selfPath.substring(LOCK_PERSIST.length()+1);
        int index = subNodes.indexOf(str);

        switch (index){
            case -1:{
                System.out.println(lockName+"--本节点已不在了..." + selfPath);
                return false;
            }
            case 0:{
                System.out.println(lockName+"--本节点是最小节点..." + selfPath);
                return true;
            }
            default:{
                waitPath = LOCK_PERSIST+"/"+subNodes.get(index-1);
                System.out.println(lockName+"--获取子节点中,排在我前面的"+ waitPath);
                // 对前一个节点注册监听事件
                try {
                    zkClient.getData(waitPath,true,new Stat());
                    return false;
                } catch (Exception e) {
                    // 跑出异常的时候,判断节点是否存在
                   if(zkClient.exists(waitPath, false) == null){
                       System.out.println(lockName+ "--子节点中,排在我前面的" + waitPath + "已失踪,重新检查");
                       return checkMinPath(selfPath);
                   }else{
                       throw new RuntimeException(waitPath+"node disappered");
                   }
                }
            }
        }
    }




    // 创建根节点,根节点不需要进行watch
    private boolean createRootNode(String path, String data) {
        try {
            createConnection();
            if(zkClient.exists(path, false) == null){
                String retPath = zkClient.create(path, data.getBytes(), ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT);
                System.out.println("创建根节点:path" + retPath + "content" + data);
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
        }
        return false;
    }


    private void createConnection() throws IOException, InterruptedException {
        if(zkClient == null){
            zkClient = new ZooKeeper(ips, sessionTimeout, this);
            connectedSemaphore.await();
        }
    }


    public void process(WatchedEvent watchedEvent) {
        if(watchedEvent == null){
            return;
        }

        Event.EventType eventType = watchedEvent.getType();
        Event.KeeperState state = watchedEvent.getState();

        if(Event.KeeperState.SyncConnected == state){
            if(Event.EventType.None == eventType){
                System.out.println("正在启动连接服务器");
                connectedSemaphore.countDown();
            }else if (Event.KeeperState.Disconnected == state) {
                System.out.println("与ZK服务器断开连接");
            } else if (Event.KeeperState.Expired == state) {
                System.out.println("会话失效");
            }
        }
    }
}

3、测试用例

public class DisMainTest {

    private static final int THREAD_NUM = 100;
    private static final CountDownLatch threadSemaphore = new CountDownLatch(THREAD_NUM);

    public static void main(String[] args) {
        final String ips = "localhost:2181,localhost:2182";
        for(int i=0; i< THREAD_NUM;i++){
            final int threadId = i + 1;
            new Thread(){
                @Override
                public void run() {
                    try {
                        DistributedLock dc = new DefaultDistributedLock(ips, threadId + "");
                        if (dc.dLock()) {
                            System.out.println(threadId+"获取到锁,并执行了任务");
                        }
                        dc.unDLock();
                        threadSemaphore.countDown();
                    } catch (Exception e) {
                        System.out.println("第" + threadId + "个线程抛出的异常:");
                        e.printStackTrace();
                    }
                }
            }.start();

        }

        try {
            threadSemaphore.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

### 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 实现分布式锁的主要思路及其具体步骤描述。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值