Class 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 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 String DEFAULT_BASE_FOLDER
      This is the base folder that all locks will be written to in Zookeeper.
      static String LOCK_PREFIX
      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.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.
    • 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 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 - The ZooKeeper 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.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 - The ZooKeeper 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.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 '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 ZooKeeper client
        lockPath - 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) Spring Environment instance
        acls - 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 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 '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 ZooKeeper client
        lockPath - 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) Spring Environment instance
        useDefaultBasePath - Indicates if a default path should be used (e.g. DEFAULT_BASE_FOLDER.
        acls - The ZooKeeper security ACLs
    • Method Detail

      • lock

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

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

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

        protected boolean lockInternally​(long waitTime)
                                  throws InterruptedException
        Negative number means wait indefinitely (typically until an InterruptedException is 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:
        currentThreadHoldsLock in interface com.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 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.
      • 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 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:
      • 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 to Integer.MIN_VALUE after it reaches Integer.MAX_VALUE. We need to sort positive numbers first after we roll over to Integer.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.