Zookeeper分布式锁源码分析

使用Zookeeper分布式锁时,一般会使用Curator中的实现。本文主要分析一下Curator中的可重入锁 InterProcessMutex 的源码实现

  • 从锁的获取acquire()方法开始
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
    {
        Thread currentThread = Thread.currentThread();
        // threadData是一个ConcurrentHashMap,用于存放每个线程对应的锁数据
        LockData lockData = threadData.get(currentThread);
        // 如果存在锁数据,说明当前线程获取过锁;因为是可重入锁,返回true获取锁即可
        if ( lockData != null )
        {
            lockData.lockCount.incrementAndGet();
            return true;
        }
        
        // 否则需要去为该线程获取锁
        String lockPath = internals.attemptLock(time, unit, getLockNodeBytes());

        // 把信息存入lockData中
        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
    {
        ....
        String          ourPath = null;
        boolean         hasTheLock = false;
        boolean         isDone = false;
        while ( !isDone )
        {
            isDone = true;

            try
            {
                // 1.在Zookeeper下创建锁节点
                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
                // 2.尝试获取锁
                hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
            }
            catch ( KeeperException.NoNodeException e )
            {               
               ....
        }
        
        if ( hasTheLock ){
            return ourPath;
        }

        return null;
    }
  • 第一步:在Zookeeper下创建锁节点,从下面源码可以看到可重入锁实际就是在Zookeeper下创建临时有序节点
@Override
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;
}
  • 第二步:尝试获取锁
private boolean internalLockLoop(long startMillis, Long millisToWait, String ourPath) throws Exception {
    boolean     haveTheLock = false;
    boolean     doDelete = false;
    try {
       .....

       // 如果客户端状态正常且还没有获取到锁
       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);
                // 如果获取到了,haveTheLock设置为true,不会进入下一次循环
                if ( predicateResults.getsTheLock() ) {
                    haveTheLock = true;
                }
                else {
                    // 否则获取次小节点
                    String  previousSequencePath = basePath + "/" + predicateResults.getPathToWatch();
                    
                    synchronized(this){
                        try {
                            // 给次小节点添加Watch监听
                            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 )
        {
            ...
        }
        
        return haveTheLock;
    }
  • 来看getsTheLock()是怎么判断能否获取锁的
 @Override
public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception{
        // 获取当前节点的排序
        int  ourIndex = children.indexOf(sequenceNodeName);
        validateOurIndex(sequenceNodeName, ourIndex);

        // 对于可重入锁,maxLeases为1
        // 说明只有是序号最小的节点才能成功获取锁
        boolean   getsTheLock = ourIndex < maxLeases;
        // 并找出次小的节点添加监听
        String    pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases);

        return new PredicateResults(pathToWatch, getsTheLock);
    }

这么一来,锁的获取就可以一个一个传递下去,并且一次只唤醒一个,不会导致羊群效应

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值