The principle of Zookeeper distributed lock

The last article wrote about the use of zk distributed locks. Now let's take a look at how Curator implements distributed locks.

In simple terms:

(1) Each thread generates sequential nodes under the current path;

(2) The node with serial number 0 successfully obtains the lock;

(3) The node that does not get the lock will add a Watch to the previous node and block it;

(4) When the first node is deleted, the next node is awakened and the lock is taken again. (Or delete its own node after blocking for a certain period of time, and return the failure to acquire the lock)

 

 

First look at the code to acquire the lock:

 

lock.acquire();//It will block until the lock is acquired successfully

or

boolean locked= lock.acquire(2000, TimeUnit.MILLISECONDS);

1. Click in and you can see that the method called is InterProcessMutex.internalLock()

 

 

private boolean internalLock(long time, TimeUnit unit) throws Exception
    {
        Thread currentThread = Thread.currentThread();
        //First see if the current thread has acquired the lock
        LockData lockData = threadData.get(currentThread);
        if ( lockData != null )
        {
            // if there is, re-entry directly
            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;
    }

 2. Look again at internals.attemptLock(time, unit, getLockNodeBytes())

 

 

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
            {
                //The key is to look at these two sentences
                //First become the current path;
                ourPath = driver.createsTheLock(client, path, localLockNodeBytes);
                // Then determine whether the lock is acquired
                hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);
            }
            catch ( KeeperException.NoNodeException e )
            {
                // gets thrown by StandardLockInternalsDriver when it can't find the lock node
                // this can happen when the session expires, etc. So, if the retry allows, just try it all again
                if ( client.getZookeeperClient().getRetryPolicy().allowRetry(retryCount++, System.currentTimeMillis() - startMillis, RetryLoop.getDefaultRetrySleeper()) )
                {
                    isDone = false;
                }
                else
                {
                    throw e;
                }
            }
        }

        if ( hasTheLock )
        {
            return ourPath;
        }

        return null;
    }

 3.再看 driver.createsTheLock(client, path, localLockNodeBytes)

 

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

 As you can see, only temporary sequential nodes are generated under the path.

 

 

4. Determine whether the lock is obtained: hasTheLock = internalLockLoop(startMillis, millisToWait, ourPath);

 

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 )
            {
                //Get the sorted child nodes
                List<String>        children = getSortedChildren();
                //Intercept the name of the current sequence node
                String              sequenceNodeName = ourPath.substring(basePath.length() + 1); // +1 to include the slash
                //Generate the result of the judgment
                PredicateResults    predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);
                if ( predicateResults.getsTheLock() )
                {
                    // successfully acquired the lock
                    haveTheLock = true;
                }
                else
                {
                    //When the acquisition fails, Watch the previous node
                    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
                            //Register 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;
                                }
                                //Block, wait for the notification after the watch event is triggered
                                wait(millisToWait);
                            }
                            else
                            {
                                wait();
                            }
                        }
                        catch ( KeeperException.NoNodeException e )
                        {
                            // it has been deleted (i.e. lock released). Try to acquire again
                        }
                    }
                }
            }
        }
        catch ( Exception e )
        {
            doDelete = true;
            throw e;
        }
        finally
        {
            if (doDelete)
            {
                deleteOurPath(ourPath);
            }
        }
        return haveTheLock;
    }

 5. PredicateResults predicateResults = driver.getsTheLock(client, children, sequenceNodeName, maxLeases);

  

@Override
    public PredicateResults getsTheLock(CuratorFramework client, List<String> children, String sequenceNodeName, int maxLeases) throws Exception
    {
        int             ourIndex = children.indexOf(sequenceNodeName);
        validateOurIndex(sequenceNodeName, ourIndex);

        boolean getsTheLock = ourIndex < maxLeases;//You can see from the front that the value of maxLeases is 1, so only when the current node is the first sequential node can the lock be successfully obtained, and others will fail.
        String pathToWatch = getsTheLock ? null : children.get(ourIndex - maxLeases); //If the lock acquisition fails, to continue waiting, you must watch the previous node,

        return new PredicateResults(pathToWatch, getsTheLock);
    }

 6. When the node being watched changes, it will wake up the blocked thread to compete for the lock again;

private final Watcher watcher = new Watcher()
    {
        @Override
        public void process(WatchedEvent event)
        {
            notifyFromWatcher();
        }
    };

  private synchronized void notifyFromWatcher()
    {
        notifyAll();
    }

 

 

 

 

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=326422759&siteId=291194637