Curator分布式锁源码分析

Curator是Apache ZooKeeper的Java / JVM客户端库,官网有个图很形象。

pic

curator对于zookeeper来说就像Guava之余java.我们知道Guava是谷歌开源的java类库,该库经过高度优化,运用得当可极大提高我们的代码效率和质量。

所以,用Curator的前提是了解zookeeper,

在现在分布式应用大行其道的时代,分布式锁一直是热点问题。现在我们一起来看下curator是如果操作zookeeper,以及如何实现分布式锁的。

//1、制定重试策略:初试时间为1s 重试3次  默认值参考官网
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000, 3); 
 //2、通过工厂创建连接
        CuratorFramework client = CuratorFrameworkFactory.newClient(address, retryPolicy);
  //3、开启连接
        client.start();
//4 新建一个分布式锁对象,既然是在zookeeper中,当然要在节点中,这里节点已存在,不存在的自行创建
        final InterProcessMutex mutex = new InterProcessMutex(client, "/testRoot"); 
//这是代码结构
if ( lock.acquire(maxWait, waitUnit) ) 
{
    try 
    {
        // do some work inside of the critical section here
    }
    finally
    {
        lock.release();
    }
}

很明显,我们需要进入acquire方法

 @Override
    public boolean acquire(long time, TimeUnit unit) throws Exception
    {
        return internalLock(time, unit);
    }

这个方法的作用就是获取锁,这方法还有两个参数,分别是等待的时间和单位。该方法会一直阻塞知道锁可用或者时间过期,并且该锁是可重入锁,每一个获取锁操作必须对应一个释放锁操作。

继续进入internalLock方法

private boolean internalLock(long time, TimeUnit unit) throws Exception
	    {
	        
		  	//取得当前线程
	        Thread currentThread = Thread.currentThread();
	        //取出与当前线程绑定的LockData对象
	        LockData lockData = threadData.get(currentThread);
	        //如果对象不为空,说明当前线程已经拥有锁
	        if ( lockData != null )
	        {
	            //每一次重入都原子计数加1
	            lockData.lockCount.incrementAndGet();
	            return true;
	        }
	        //获得锁的关键代码
	        String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());
	        //第一次获得锁后,将当前线程和锁信息放入map中
	        if ( lockPath != null )
	        {
	            LockData newLockData = new LockData(currentThread, lockPath);
	            threadData.put(currentThread, newLockData);
	            return true;
	        }

	        return false;
	    }

继续看获取锁的关键代码attemptLock

 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
	            {	//创建一个节点
	                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
	                //根据创建的节点来判断是否取得锁
	                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;
	    }

这段代码中核心的就是中间加了注释的那两行,一个是创建节点的createsTheLock方法,进入该方法

  public String createsTheLock(CuratorFramework client, String path, byte[] lockNodeBytes) throws Exception
    {
        String ourPath;
        if ( lockNodeBytes != null )
        {
            ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path, lockNodeBytes);
        }
        else
        {
            ourPath = client.create().creatingParentContainersIfNeeded().withProtection().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(path);
        }
        return ourPath;
    }

用过java操作zookeeper的同学就能看出这就是对创建节点方法的封装,这里创建的是临时顺序节点。

再看internalLockLoop方法

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);
            }
            //调用后一直获取知道拿到锁
            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);
                //获得了锁
                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();
                                if ( millisToWait <= 0 )
                                {
                                    doDelete = true;    // timed out - delete our node
                                    break;
                                }

                                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
        {
            if ( doDelete )
            {
                deleteOurPath(ourPath);
            }
        }
        return haveTheLock;
    }

进入getsTheLock方法查看获取具体细节

   public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
	    {
		   //在已排序的节点中查找自己创建节点的下标
	        int             ourIndex = children.indexOf(sequenceNodeName);
	        //验证ourIndex是否小于0 ,小于0表示该接节点不存在
	        validateOurIndex(sequenceNodeName, ourIndex);
	        //如果比maxLeases小, 则说明是最小的节点  maxLeases在创建锁InterProcessMutex示例时被初始化为1
	        boolean         getsTheLock = ourIndex < maxLeases;
	        //如果不是最小的,则需要监听离你最近的节点
	        String          pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);
	        return new PredicateResults(pathToWatch, getsTheLock);
	    }

获取锁的过程基本就是这样,建议大家debug跟踪下。

思路其实和java操作zookeeper一样:
判断是否已经拥有锁,如果拥有技术加1直接返回
如果不是,则尝试创建节点,如果当前线程创建的节点值是最小的,则获取锁
如果不是最小,则监听利你最近的那个节点。

有获取锁就有释放锁,相对来说释放锁要简单些。

public void release() 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 )
        {
            throw new IllegalMonitorStateException("You do not own the lock: " + basePath);
        }
		//技术减一,由于锁是可重入的,所以如果newLockCount>0,则不能删除节点直接返回。
        int newLockCount = lockData.lockCount.decrementAndGet();
        if ( newLockCount > 0 )
        {
            return;
        }//如果为负数则显然不正常,可能是获取一次释放多次的缘故
        if ( newLockCount < 0 )
        {
            throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + basePath);
        }
        try
        {	//释放锁,删除节点,移除监听,
            internals.releaseLock(lockData.lockPath);
        }
        finally
        {	//从map移除当前线程的锁信息
            threadData.remove(currentThread);
        }
    }

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值