Class ReentrantDistributedZookeeperLock

java.lang.Object
com.broadleafcommerce.search.provider.solr.util.zk.ReentrantDistributedZookeeperLock
All Implemented Interfaces:
com.broadleafcommerce.search.api.DistributedLock, AutoCloseable, Lock

public class ReentrantDistributedZookeeperLock extends Object implements com.broadleafcommerce.search.api.DistributedLock, AutoCloseable
This is similar to a 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

    Fields
    Modifier and Type
    Field
    Description
    static 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

    Constructors
    Constructor
    Description
    ReentrantDistributedZookeeperLock(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 the SolrZkClient (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 the SolrZkClient (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 the SolrZkClient (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 an Environment 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 the SolrZkClient (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 an Environment object, which can be null.
  • Method Summary

    Modifier and Type
    Method
    Description
    boolean
    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
    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>
    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 Spring Environment 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
    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
     
    final void
     
     
    final boolean
     
    final boolean
    tryLock(long time, TimeUnit unit)
     
    final void
     

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Field Details

    • 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:
    • 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:
  • Constructor Details

    • ReentrantDistributedZookeeperLock

      public ReentrantDistributedZookeeperLock(org.apache.solr.common.cloud.SolrZkClient zk, String lockPath, String lockName)
    • 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 the SolrZkClient (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 '_'). This Lock will, by default, participate in or be allowed to acquire a lock. The lockPath will be prepended with '/broadleaf/app/distributed-locks'.
      Parameters:
      zk - The SolrZkClient client
      lockPath - 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 the SolrZkClient (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 '_'). This Lock 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 - The SolrZkClient client
      lockPath - 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 the SolrZkClient (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 an Environment object, which can be null. This Lock will, by default, participate in or be allowed to acquire a lock if the Environment 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 - The SolrZkClient client
      lockPath - 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) Spring ApplicationContext instance
      acls - 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 the SolrZkClient (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 an Environment object, which can be null. This Lock will, by default, participate in or be allowed to acquire a lock if the Environment 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 - The SolrZkClient client
      lockPath - 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) Spring ApplicationContext instance
      useDefaultBasePath - 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()
      Specified by:
      lock in interface Lock
    • unlock

      public final void unlock()
      Specified by:
      unlock in interface Lock
    • lockInterruptibly

      public final void lockInterruptibly() throws InterruptedException
      Specified by:
      lockInterruptibly in interface Lock
      Throws:
      InterruptedException
    • tryLock

      public final boolean tryLock()
      Specified by:
      tryLock in interface Lock
    • tryLock

      public final boolean tryLock(long time, TimeUnit unit) throws InterruptedException
      Specified by:
      tryLock in interface Lock
      Throws:
      InterruptedException
    • newCondition

      public Condition newCondition()
      Specified by:
      newCondition in interface Lock
    • currentThreadHoldsLock

      public final boolean currentThreadHoldsLock()
      Specified by:
      currentThreadHoldsLock in interface com.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 the Environment 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 the Environment 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 interface com.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 the ApplicationContext is not null and this lock canParticipate(), 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

      public String getLockName()
      The lock name provided in the constructor.
      Returns:
    • getFullLockName

      public String getFullLockName()
      Returns LOCK_PREFIX + getLockName() E.g. dzlck-myLock
      Returns:
    • getLockFolderPath

      public 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 Spring Environment object 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 this Environment can obtain access to (i.e. lock) this Lock.
      Returns:
    • getLockFailureMonitorEnabledPropertyName

      protected String getLockFailureMonitorEnabledPropertyName()
    • close

      public final void close()
      Permanently closes the lock and frees resources.
      Specified by:
      close in interface AutoCloseable
      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 Spring ApplicationContext is not null and this lock is still held by a thread in this runtime environment, then an ZookeeperLockFailedEvent will be raised before closing the lock and removing the resources.
    • getApplicationContext

      protected org.springframework.context.ApplicationContext getApplicationContext()