Zookeeper实现分布式锁(Curator API)

Curator API提供了基于Zookeeper的分布式锁的实现

通过查看InterProcessMutexLockInternals源码,确定分布式锁的锁定和释放流程

互斥锁设计的核心思想:同一时间,仅一个进程/线程可以占有

  1. 临时节点:利用临时节点,会话中断,就会删除的特点,避免死锁
  2. 节点的顺序性:利用同一路径下,不能存在相同节点,节点创建存在顺序,先创建的节点的序号更小,序号最小的节点占有锁
  3. Watch机制:监听当前占用锁的路径,如果锁对应的路径被修改,就唤醒所有等待的节点

获取锁

// InterProcessMutex

	public void acquire() throws Exception
    {
        // 获取锁
        if ( !internalLock(-1, null) )
        {
            throw new IOException("Lost connection while trying to acquire lock: " + basePath);
        }
    }
	
	private boolean internalLock(long time, TimeUnit unit) throws Exception
    {
        /*
           Note on concurrency: a given lockData instance
           can be only acted on by a single thread so locking isn't necessary
        */

        Thread currentThread = Thread.currentThread();

        LockData lockData = threadData.get(currentThread);
        //线程已经占用锁,增加重入次数
        if ( lockData != null )
        {
            // re-entering
            lockData.lockCount.incrementAndGet();
            return true;
        }

        // 尝试获取锁
        String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
        //设置
        if ( lockPath != null )
        {
            LockData newLockData = new LockData(currentThread, lockPath);
            threadData.put(currentThread, newLockData);
            return true;
        }

        return false;
    }
// LockInternals

    // 该Watcher对象,一旦收到通知,就会唤醒所有阻塞的线程
	private final Watcher watcher = new Watcher()
    {
        @Override
        public void process(WatchedEvent event)
        {
            client.postSafeNotify(LockInternals.this);
        }
    };

	String attemptLock(long time, TimeUnit unit, byte[] lockNodeBytes) throws Exception
    {
        final long      startMillis = System.currentTimeMillis();
        final Long      millisToWait = (unit != null) ? unit.toMillis(time) : null;
        final byte[]    localLockNodeBytes = (revocable.get() != null) ? new byte[0] : lockNodeBytes;
        int             retryCount = 0;

        String          ourPath = null;
        boolean         hasTheLock = false;
        boolean         isDone = false;
        while ( !isDone )
        {
            isDone = true;

            try
            {
                // 在path下创建一个路径,作为锁路径
                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
                // 循环获取锁,成功获取锁,返回true;否则,返回false
                hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
            }
            catch ( KeeperException.NoNodeException e )
            {
                // 会话超时导致的锁释放,重试获取锁
                if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
                {
                    isDone = false;
                }
                else
                {
                    // 超过最大重试次数,抛出异常
                    throw e;
                }
            }
        }

        if ( hasTheLock )
        {
            // 返回锁路径
            return ourPath;
        }

        return null;
    }

	private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception
    {
        boolean     haveTheLock = false;
        boolean     doDelete = false;
        try
        {
            if ( revocable.get() != null )
            {
                client.getData().usingWatcher(revocableWatcher).forPath(ourPath);
            }
			
            // 需要在和Zookeeper保持连接,且未获取到锁,就不断循环获取
            while ( (client.getState() == CuratorFrameworkState.STARTED) && !haveTheLock )
            {
                List<String>        children = getSortedChildren();
                String              sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash

                // 获取锁
                PredicateResults    predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
                // 成功获取锁,退出循环,返回true
                if ( predicateResults.getsTheLock() )
                {
                    haveTheLock = true;
                }
                else
                {
                    // 获取需要监听的节点的绝对路径
                    String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();

                    synchronized(this)
                    {
                        try
                        {
                            // use getData() instead of exists() to avoid leaving unneeded watchers which is a type of resource leak
                           //监听对应的路径 
                            client.getData().usingWatcher(watcher).forPath(previousSequencePath);
                            // 设置了超时时间
                            if ( millisToWait != null )
                            {
                                millisToWait -= (System.currentTimeMillis() - startMillis);
                                startMillis = System.currentTimeMillis();
                                // 已经超时,将doDelete标志位设置为true,后续删除节点
                                if ( millisToWait <= 0 )
                                {
                                    doDelete = true;    // timed out - delete our node
                                    break;
                                }
								
                                // 未超时,同步阻塞当前线程millisToWait时间
                                wait(millisToWait);
                            }
                            else
                            {
                                // 未设置超时时间,直接同步阻塞当前线程
                                wait();
                            }
                        }
                        catch ( KeeperException.NoNodeException e )
                        {
                            // it has been deleted (i.e. lock released). Try to acquire again
                        }
                    }
                }
            }
        }
        catch ( Exception e )
        {
            ThreadUtils.checkInterrupted(e);
            doDelete = true;
            throw e;
        }
        finally
        {
            // 设置doDelete=true,删除路径
            if ( doDelete )
            {
                deleteOurPath(ourPath);
            }
        }
        return haveTheLock;
    }

	// 获取锁
    // maxLeases默认为1,表示资源数
	public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
    {
        // 在有序的列表中,找到当前节点的索引,索引从0开始,如果<0,表示没找到当前节点
        int             ourIndex = children.indexOf(sequenceNodeName);
        validateOurIndex(sequenceNodeName, ourIndex);
		
        // 当前路径所属索引为0时,才能成功获取锁
        boolean         getsTheLock = ourIndex < maxLeases;
        // 需要监听当前节点的前一个节点(这里因为maxLeases为1)
        String          pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);

        return new PredicateResults(pathToWatch, getsTheLock);
    }

释放锁

释放锁的过程较为简单。

参考

posted on 2023-03-14 20:25  DaydayupLiu  阅读(92)  评论(0编辑  收藏  举报