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(); }