Class ReentrantDistributedZookeeperLock

java.lang.Object
org.broadleafcommerce.core.util.lock.ReentrantDistributedZookeeperLock
All Implemented Interfaces:
Lock, DistributedLock

public class ReentrantDistributedZookeeperLock extends Object implements DistributedLock
This is similar to a ReentrantLock, except that it uses Zookeeper to share the lock state across multiple nodes, allowing for a distributed 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 { //Do something in a globally locked state } finally { 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.

Author:
Kelly Tisdell
  • Nested Class Summary

    Nested classes/interfaces inherited from interface org.broadleafcommerce.core.util.lock.DistributedLock

    DistributedLock.DistributedLockException
  • 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 org.broadleafcommerce.core.util.lock.DistributedLock

    GLOBAL_ENV_CAN_OBTAIN_LOCK_PROPERTY_NAME
  • 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 the ZooKeeper (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 the ZooKeeper (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 the ZooKeeper (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.zookeeper.ZooKeeper zk, String lockPath, String lockName, org.springframework.core.env.Environment env, List<org.apache.zookeeper.data.ACL> acls)
    This constructor takes in the Zookeeper (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 'org.broadleafcommerce.core.util.lock.DistributedLock.canParticipate' (globally) or the 'org.broadleafcommerce.core.util.lock.DistributedLock.
    boolean
    Indicates if the current thread holds the lock.
    protected List<org.apache.zookeeper.data.ACL>
    Returns a list of Zookeeper ACLs to be used for this lock.
    int
     
    protected org.springframework.core.env.Environment
     
    protected int
    This will retry any interactions with Zookeeper this many times.
    protected String
    Returns LOCK_PREFIX + getLockName()
    protected String
     
    protected String
     
    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.zookeeper.ZooKeeper
     
    protected void
    Creates the appropriate folder(s) in Zookeeper if they don't already exist.
    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.
    void
     
    protected boolean
    lockInternally(long waitTime)
    Negative number means wait indefinitely (typically until an UnsupportedOperationException is thrown.
    void
     
     
    boolean
     
    boolean
    tryLock(long time, TimeUnit unit)
     
    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.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 the ZooKeeper (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 -
      lockPath -
      lockName -
      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 the ZooKeeper (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 -
      lockPath -
      lockName -
      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 the Zookeeper (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 'org.broadleafcommerce.core.util.lock.DistributedLock.${lockName}.canParticipate' property is not set or is set to false.

      The lockPath will be prepended with '/broadleaf/app/distributed-locks'.

      Parameters:
      zk -
      lockPath -
      lockName -
      env -
      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 the ZooKeeper (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 'org.broadleafcommerce.core.util.lock.DistributedLock.${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 -
      lockPath -
      lockName -
      env -
      useDefaultBasePath -
      acls -
  • Method Details

    • lock

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

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

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

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

      public 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
    • lockInternally

      protected boolean lockInternally(long waitTime) throws InterruptedException
      Negative number means wait indefinitely (typically until an UnsupportedOperationException is thrown. Zero (0) means don't wait. Positive number means wait for that number of millis.
      Parameters:
      waitTime -
      Throws:
      InterruptedException
    • initialize

      protected void initialize()
      Creates the appropriate folder(s) in Zookeeper if they don't already exist.
    • canParticipate

      public boolean canParticipate()
      Allows one to disable this locking mechanism via the 'org.broadleafcommerce.core.util.lock.DistributedLock.canParticipate' (globally) or the 'org.broadleafcommerce.core.util.lock.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 'org.broadleafcommerce.core.util.lock.DistributedLock.${lockName}.canParticipate' property. If that is true or null, then it checks the more global 'org.broadleafcommerce.core.util.lock.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 DistributedLock
      Returns:
    • currentThreadHoldsLock

      public boolean currentThreadHoldsLock()
      Description copied from interface: DistributedLock
      Indicates if the current thread holds the lock.
      Specified by:
      currentThreadHoldsLock in interface DistributedLock
      Returns:
    • getCurrentThreadLockPermits

      public int getCurrentThreadLockPermits()
    • getZookeeperClient

      protected org.apache.zookeeper.ZooKeeper getZookeeperClient()
    • getEnvironment

      protected org.springframework.core.env.Environment getEnvironment()
    • 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:
    • isAdditiveWaitTtimes

      protected boolean isAdditiveWaitTtimes()
      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:
    • getLockAccessPropertyName

      protected String getLockAccessPropertyName()
    • getAcls

      protected List<org.apache.zookeeper.data.ACL> getAcls()
      Returns a list of Zookeeper ACLs to be used for this lock.
      Returns: