Class ReentrantDistributedZookeeperLock
- All Implemented Interfaces:
com.broadleafcommerce.search.api.DistributedLock
,AutoCloseable
,Lock
ReentrantLock
, 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 implements Lock
.
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
FieldsModifier and TypeFieldDescriptionstatic final String
This is the base folder that all locks will be written to in Zookeeper.static final String
This is the prefix of any lock entry.Fields inherited from interface com.broadleafcommerce.search.api.DistributedLock
GLOBAL_ENV_CAN_OBTAIN_LOCK_PROPERTY_NAME
-
Constructor Summary
ConstructorsConstructorDescriptionReentrantDistributedZookeeperLock
(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName) ReentrantDistributedZookeeperLock
(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, org.springframework.context.ApplicationContext applicationContext, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 anEnvironment
object, which can be null.ReentrantDistributedZookeeperLock
(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, org.springframework.context.ApplicationContext applicationContext, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 anEnvironment
object, which can be null. -
Method Summary
Modifier and TypeMethodDescriptionboolean
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.final void
close()
Permanently closes the lock and frees resources.final boolean
final void
Permanently closes the lock and frees resources.protected List<org.apache.zookeeper.data.ACL>
getAcls()
Returns a list of Zookeeper ACLs to be used for this lock.protected org.springframework.context.ApplicationContext
final int
protected org.springframework.core.env.Environment
Returns the SpringEnvironment
object associated with this environment.protected int
This will retry any interactions with Zookeeper this many times.Returns LOCK_PREFIX + getLockName() E.g.protected String
The name of the property associated with this lock.protected String
The lock name provided in the constructor.protected long
If an interaction with Zookeeper fails, it will retry and this will be the pause time, in millis, between retries.protected org.apache.solr.common.cloud.SolrZkClient
protected boolean
If this is true, the wait time in millis will be incremented by itself each time a retry occurs, up to the retry count.final boolean
isLocked()
Indicates whether, at the time of calling, a distributed lock is in place.protected boolean
Indicates if this lock should have a lock monitor thread enabled.final void
lock()
final void
final boolean
tryLock()
final boolean
final void
unlock()
-
Field Details
-
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:
-
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:
-
-
Constructor Details
-
ReentrantDistributedZookeeperLock
-
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 '_'). ThisLock
will, by default, participate in or be allowed to acquire a lock. The lockPath will be prepended with '/broadleaf/app/distributed-locks'.- Parameters:
zk
- TheSolrZkClient
clientlockPath
- 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.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 '_'). ThisLock
will, 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
- TheSolrZkClient
clientlockPath
- 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.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, org.springframework.context.ApplicationContext applicationContext, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 anEnvironment
object, which can be null. ThisLock
will, by default, participate in or be allowed to acquire a lock if theEnvironment
argument 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
- TheSolrZkClient
clientlockPath
- The path to the lock file (similar to a file system path)lockName
- The name of the lock, itself (e.g. "CATALOG")applicationContext
- The (optional) SpringApplicationContext
instanceacls
- The ZooKeeper security ACLs
-
ReentrantDistributedZookeeperLock
public ReentrantDistributedZookeeperLock(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName, org.springframework.context.ApplicationContext applicationContext, boolean useDefaultBasePath, List<org.apache.zookeeper.data.ACL> acls) This constructor takes in theSolrZkClient
(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 anEnvironment
object, which can be null. ThisLock
will, by default, participate in or be allowed to acquire a lock if theEnvironment
argument 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
- TheSolrZkClient
clientlockPath
- The path to the lock file (similar to a file system path)lockName
- The name of the lock, itself (e.g. "CATALOG")applicationContext
- The (optional) SpringApplicationContext
instanceuseDefaultBasePath
- Indicates if a default path should be used (e.g.DEFAULT_BASE_FOLDER
.acls
- The ZooKeeper security ACLs
-
-
Method Details
-
lock
public final void lock() -
unlock
public final void unlock() -
lockInterruptibly
- Specified by:
lockInterruptibly
in interfaceLock
- Throws:
InterruptedException
-
tryLock
public final boolean tryLock() -
tryLock
- Specified by:
tryLock
in interfaceLock
- Throws:
InterruptedException
-
newCondition
- Specified by:
newCondition
in interfaceLock
-
currentThreadHoldsLock
public final boolean currentThreadHoldsLock()- Specified by:
currentThreadHoldsLock
in interfacecom.broadleafcommerce.search.api.DistributedLock
-
getCurrentThreadLockPermits
public final int getCurrentThreadLockPermits() -
isLocked
public final 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 theEnvironment
is 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 theEnvironment
is 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:
canParticipate
in interfacecom.broadleafcommerce.search.api.DistributedLock
- Returns:
- whether this environment can participate in this lock, as specified by name.
-
isLockFailureMonitorEnabled
protected boolean isLockFailureMonitorEnabled()Indicates if this lock should have a lock monitor thread enabled. By default, if theApplicationContext
is not null and this lockcanParticipate()
, then this returns true. You can disable this by setting the property:com.broadleafcommerce.search.api.DistributedLock.${lockName}.isLockMonitorEnabled=false
- Returns:
-
getZookeeperClient
protected org.apache.solr.common.cloud.SolrZkClient getZookeeperClient() -
getLockName
The lock name provided in the constructor.- Returns:
-
getFullLockName
Returns LOCK_PREFIX + getLockName() E.g. dzlck-myLock- Returns:
-
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
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 SpringEnvironment
object associated with this environment. This method may return null.- Returns:
- the current Spring
Environment
, or null.
-
getLockAccessPropertyName
The name of the property associated with this lock. Used by canParticipate method to determine if this thisEnvironment
can obtain access to (i.e. lock) thisLock
.- Returns:
-
getLockFailureMonitorEnabledPropertyName
-
close
public final void close()Permanently closes the lock and frees resources.- Specified by:
close
in interfaceAutoCloseable
- Throws:
IllegalStateException
- if the lock is held in this runtime env.
-
forceClose
public final void forceClose()Permanently closes the lock and frees resources. This lock can be re-opened by instantiating a new lock. If the SpringApplicationContext
is not null and this lock is still held by a thread in this runtime environment, then anZookeeperLockFailedEvent
will be raised before closing the lock and removing the resources. -
getApplicationContext
protected org.springframework.context.ApplicationContext getApplicationContext()
-