Class ReentrantDistributedZookeeperLock
- java.lang.Object
 - 
- com.broadleafcommerce.search.provider.solr.util.zk.ReentrantDistributedZookeeperLock
 
 
- 
- All Implemented Interfaces:
 com.broadleafcommerce.search.api.DistributedLock,Lock
public class ReentrantDistributedZookeeperLock extends Object implements com.broadleafcommerce.search.api.DistributedLock
This is similar to aReentrantLock, except that it uses Zookeeper to share the lock state across multiple nodes, allowing for a distributed lock. Since we exclusively use Apache SolrCloud which relies on Apache ZooKeeper we can assume that if this library is on the classpath that ZooKeeper will be available as well. This is a general purpose, distributed locking mechanism that implementsLock. The owning thread may acquire the lock multiple times, but must unlock for each time the lock is acquired. It's important to get in the habit of unlocking immediately in a finally block to prevent orphaned locks:Lock lock = new ReentrantDistributedZookeeperLock(zk, "/solr-update/locks", "solrUpdate_commandLock"); lock.lockInterruptibly(); //This will block until a lock is acquired or until the Thread is interrupted. try { // Arbitrarily lock again for demonstration purposes. lock.lock() try { //Do something in a globally locked state } finally { // Be sure to unlock each time a lock is acquired. lock.unlock(); } } finally { // Be sure to unlock each time a lock is acquired. lock.unlock(); }or
Lock lock = new ReentrantDistributedZookeeperLock(zk, "/solr-update/locks", "solrUpdate_commandLock"); if (lock.tryLock()) { try { //Do something in a globally locked state } finally { lock.unlock(); } }In terms of performance, the average lock time, on a single quad-core laptop running a single Zookeeper instance, takes about 7-8 milliseconds. And the average unlock time also takes around 7-8 milliseconds during a simple test on a quad core laptop that was also running a single Zookeeper instance. This is with a single thread, and is assuming no reentrant locks and no contention or waiting for locks to become available. In other words, disregarding blocking and contention, it will likely take, on average, 14-16 milliseconds to create and release a lock (approximately 55-65 locks and releases / second for any given lock). These metrics will depend on a number of things, including contention for locks, Zookeeper cluster size and configuration, hardware, etc. Note that this locking mechanism attempts to be fair, but does not explicitly guarantee fairness.- Author:
 - Kelly Tisdell (ktisdell)
 
 
- 
- 
Field Summary
Fields Modifier and Type Field Description static StringDEFAULT_BASE_FOLDERThis is the base folder that all locks will be written to in Zookeeper.static StringLOCK_PREFIXThis is the prefix of any lock entry. 
- 
Constructor Summary
Constructors Constructor Description ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName)ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), and the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_').ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), and the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_').ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, org.springframework.core.env.Environment env, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'), and anEnvironmentobject, which can be null.ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, org.springframework.core.env.Environment env, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'), and anEnvironmentobject, which can be null. 
- 
Method Summary
All Methods Instance Methods Concrete Methods Modifier and Type Method Description booleancanParticipate()Allows one to disable this locking mechanism via the 'com.broadleafcommerce.search.api.DistributedLock.canParticipate' (globally) or the 'com.broadleafcommerce.search.api.DistributedLock.${lockName}.canParticipate' property.booleancurrentThreadHoldsLock()protected Optional<String>findFirstLockFileName(String currentLockPath)Reads all of the lock files under the lock folder and orders them by file name.protected StringgenerateLockFile()Create a lock reference in Zookeeper.protected List<org.apache.zookeeper.data.ACL>getAcls()Returns a list of Zookeeper ACLs to be used for this lock.intgetCurrentThreadLockPermits()protected org.springframework.core.env.EnvironmentgetEnvironment()Returns the SpringEnvironmentobject associated with this environment.protected intgetFailureRetries()This will retry any interactions with Zookeeper this many times.protected StringgetFullLockName()Returns LOCK_PREFIX + getLockName() E.g.protected StringgetLockAccessPropertyName()The name of the property associated with this lock.protected StringgetLockFolderPath()protected StringgetLockName()The lock name provided in the constructor.protected longgetRetryWaitTime()If an interaction with Zookeeper fails, it will retry and this will be the pause time, in millis, between retries.protected org.apache.zookeeper.ZooKeepergetZookeeperClient()protected Comparator<String>getZooKeeperSequentialComparator(String lockFileName)This sorts the ZooKeeper files (znodes) according to name, based on the appended sequence, and depending on what the current sequence is.protected voidinitialize()Creates the appropriate folder(s) in Zookeeper if they don't already exist.protected booleanisAdditiveWaitTimes()If this is true, the wait time in millis will be incremented by itself each time a retry occurs, up to the retry count.booleanisLocked()Indicates whether, at the time of calling, a distributed lock is in place.voidlock()protected booleanlockInternally(long waitTime)Negative number means wait indefinitely (typically until anInterruptedExceptionis thrown.voidlockInterruptibly()ConditionnewCondition()protected intparseIntegerSequenceFromFilename(String lockFileName)This parses the 32-bit signed integer from the end of the lock file name (or znode).booleantryLock()booleantryLock(long time, TimeUnit unit)voidunlock() 
 - 
 
- 
- 
Field Detail
- 
DEFAULT_BASE_FOLDER
public static final String DEFAULT_BASE_FOLDER
This is the base folder that all locks will be written to in Zookeeper. The constructors require a lock path, which will be appended to this path.- See Also:
 - Constant Field Values
 
 
- 
LOCK_PREFIX
public static final String LOCK_PREFIX
This is the prefix of any lock entry. It is dzlck-. A lock file (node) will be dzlck-mylock, for example.- See Also:
 - Constant Field Values
 
 
 - 
 
- 
Constructor Detail
- 
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName) 
- 
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), and the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'). ThisLockwill, by default, participate in or be allowed to acquire a lock. The lockPath will be prepended with '/broadleaf/app/distributed-locks'.- Parameters:
 zk- TheZooKeeperclientlockPath- The path to the lock file (similar to a file system path)lockName- The name of the lock, itself (e.g. "CATALOG")acls- The ZooKeeper security ACLs
 
- 
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), and the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'). ThisLockwill, by default, participate in or be allowed to acquire a lock. If use defaultBasePath is true, then the lockPath will be prepended with '/broadleaf/app/distributed-locks'.- Parameters:
 zk- TheZooKeeperclientlockPath- The path to the lock file (similar to a file system path)lockName- The name of the lock, itself (e.g. "CATALOG")acls- The ZooKeeper security ACLs
 
- 
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, org.springframework.core.env.Environment env, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'), and anEnvironmentobject, which can be null. ThisLockwill, by default, participate in or be allowed to acquire a lock if theEnvironmentargument is null or if the 'com.broadleafcommerce.search.api.DistributedLock.${lockName}.canParticipate' property is not set or is set to false. The lockPath will be prepended with '/broadleaf/app/distributed-locks'.- Parameters:
 zk- TheZooKeeperclientlockPath- The path to the lock file (similar to a file system path)lockName- The name of the lock, itself (e.g. "CATALOG")env- The (optional) SpringEnvironmentinstanceacls- The ZooKeeper security ACLs
 
- 
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.zookeeper.ZooKeeper zk, String lockPath, String lockName, org.springframework.core.env.Environment env, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls)This constructor takes in theZooKeeper(non-nullable), the lock path (non-nullable and in the format of '/path/to/this/lock/folder`), the lock name (non-nullable and non-empty string with no whitespaces, leading or trailing slashes, or special characters except '-' or '_'), and anEnvironmentobject, which can be null. ThisLockwill, by default, participate in or be allowed to acquire a lock if theEnvironmentargument is null or if the 'com.broadleafcommerce.search.api.${lockName}.canParticipate' property is not set or is set to false. If use defaultBasePath is true, then the lockPath will be prepended with '/broadleaf/app/distributed-locks'.- Parameters:
 zk- TheZooKeeperclientlockPath- The path to the lock file (similar to a file system path)lockName- The name of the lock, itself (e.g. "CATALOG")env- The (optional) SpringEnvironmentinstanceuseDefaultBasePath- Indicates if a default path should be used (e.g.DEFAULT_BASE_FOLDER.acls- The ZooKeeper security ACLs
 
 - 
 
- 
Method Detail
- 
lockInterruptibly
public void lockInterruptibly() throws InterruptedException- Specified by:
 lockInterruptiblyin interfaceLock- Throws:
 InterruptedException
 
- 
tryLock
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException- Specified by:
 tryLockin interfaceLock- Throws:
 InterruptedException
 
- 
newCondition
public Condition newCondition()
- Specified by:
 newConditionin interfaceLock
 
- 
lockInternally
protected boolean lockInternally(long waitTime) throws InterruptedExceptionNegative number means wait indefinitely (typically until anInterruptedExceptionis thrown. Zero (0) means don't wait. Positive number means wait for that number of millis.- Parameters:
 waitTime- time to wait for a lock- Throws:
 InterruptedException
 
- 
initialize
protected void initialize()
Creates the appropriate folder(s) in Zookeeper if they don't already exist. 
- 
currentThreadHoldsLock
public boolean currentThreadHoldsLock()
- Specified by:
 currentThreadHoldsLockin interfacecom.broadleafcommerce.search.api.DistributedLock
 
- 
getCurrentThreadLockPermits
public int getCurrentThreadLockPermits()
 
- 
isLocked
public boolean isLocked()
Indicates whether, at the time of calling, a distributed lock is in place. Note that this is more like a hint because as soon as this method exits, another thread may create a lock.- Returns:
 - whether this lock is locked by this thread or another.
 
 
- 
canParticipate
public boolean canParticipate()
Allows one to disable this locking mechanism via the 'com.broadleafcommerce.search.api.DistributedLock.canParticipate' (globally) or the 'com.broadleafcommerce.search.api.DistributedLock.${lockName}.canParticipate' property. Both are true by default. If either of these properties are set to false, then this lock will never be obtained. This allows only certain environments or certain nodes (JVMs) to obtain the lock while preventing others. Assuming theEnvironmentis not null, this first checks the more specific 'com.broadleafcommerce.search.api.DistributedLock.${lockName}.canParticipate' property. If that is true or null, then it checks the more global 'com.broadleafcommerce.search.api.DistributedLock.canParticipate'. If that is true or null then it returns true. By default, this method returns true if theEnvironmentis null or if both properties are null. If this method returns false, then the locking mechanisms and semantics will continue to work. However, the lock will always be locked and will never allow the acquisition of a lock.- Specified by:
 canParticipatein interfacecom.broadleafcommerce.search.api.DistributedLock- Returns:
 - whether this environment can participate in this lock, as specified by name.
 
 
- 
getZookeeperClient
protected org.apache.zookeeper.ZooKeeper getZookeeperClient()
 
- 
getLockName
protected String getLockName()
The lock name provided in the constructor.- Returns:
 
 
- 
getFullLockName
protected String getFullLockName()
Returns LOCK_PREFIX + getLockName() E.g. dzlck-myLock- Returns:
 
 
- 
getLockFolderPath
protected String getLockFolderPath()
 
- 
getFailureRetries
protected int getFailureRetries()
This will retry any interactions with Zookeeper this many times. Must be a positive number. Zero (0) is OK.- Returns:
 
 
- 
getRetryWaitTime
protected long getRetryWaitTime()
If an interaction with Zookeeper fails, it will retry and this will be the pause time, in millis, between retries.- Returns:
 - time between reties in milliseconds
 
 
- 
isAdditiveWaitTimes
protected boolean isAdditiveWaitTimes()
If this is true, the wait time in millis will be incremented by itself each time a retry occurs, up to the retry count. So, if the retries is 5 and the retryWaitTime is 100, then the max amount of time that the system will wait is 1500: 100 + 200 + 300 + 400 + 500, not including any wait time associated with I/O or execution of the Operation.- Returns:
 - whether wait times should be added together between each retry
 
 
- 
getAcls
protected List<org.apache.zookeeper.data.ACL> getAcls()
Returns a list of Zookeeper ACLs to be used for this lock.- Returns:
 - the ZooKeeper ACL list
 
 
- 
getEnvironment
protected org.springframework.core.env.Environment getEnvironment()
Returns the SpringEnvironmentobject associated with this environment. This method may return null.- Returns:
 - the current Spring 
Environment, or null. 
 
- 
getLockAccessPropertyName
protected String getLockAccessPropertyName()
The name of the property associated with this lock. Used by canParticipate method to determine if this thisEnvironmentcan obtain access to (i.e. lock) thisLock.- Returns:
 
 
- 
generateLockFile
protected String generateLockFile() throws Exception
Create a lock reference in Zookeeper. By default, it looks something like /path/to/my/locks/dzlck-myLockxyz, where 'xyz' is a signed 32-bit integer. Creating this file does not guarantee that a lock has been acquired. It is simply a placeholder for this thread competing against others for lock acquisition. The sequence appended to the lock name allows us to determine which file was created first and therefore, which thread (the one who created the file) holds the lock.- Returns:
 - a full file name, including the path, of the lock file handle that this thread is using to compete for a lock
 - Throws:
 Exception
 
- 
findFirstLockFileName
protected Optional<String> findFirstLockFileName(String currentLockPath) throws Exception
Reads all of the lock files under the lock folder and orders them by file name. Then, this selects the first file, which is the one whose thread owns the lock.- Parameters:
 currentLockPath- The path of the lock file created by this thread- Returns:
 - the file name of the lock file whose name is first in a list, ordered by name lexicographically
 - Throws:
 Exception
 
- 
getZooKeeperSequentialComparator
protected Comparator<String> getZooKeeperSequentialComparator(String lockFileName)
This sorts the ZooKeeper files (znodes) according to name, based on the appended sequence, and depending on what the current sequence is. ZooKeeper allows us to use a built-in sequence generator to append a sequence to the lock file (or znode). This helps us sort the znodes by name, with the first name being associated with the znode that has the lock. Threads compete to obtain a lock. The thread that created a file with a sequence that is sorted before another thread that created a file with a different sequence wins. The problem is that it's a 32 bit signed sequence that starts at zero and overflows toInteger.MIN_VALUEafter it reachesInteger.MAX_VALUE. We need to sort positive numbers first after we roll over toInteger.MIN_VALUE, and negative numbers first after we pass zero again.- Parameters:
 lockFileName- The file name created by the current thread.- Returns:
 - A comparator that sorts files by their 32-bit file name appendage.
 
 
- 
parseIntegerSequenceFromFilename
protected final int parseIntegerSequenceFromFilename(String lockFileName)
This parses the 32-bit signed integer from the end of the lock file name (or znode).- Parameters:
 lockFileName- The name of a lock file for which to parse the sequence number- Returns:
 - The signed, 32-bit integer which is a sequence number generated by ZooKeeper.
 
 
 - 
 
 -