"From Initial to Abandoned - ZooKeeper" ZooKeeper Practical - Distributed Lock

Keywords: Programming Zookeeper IE Java

Preface

In ZooKeeper Actual-Distributed Queue, we wrote about how to implement a distributed queue through ZooKeeper's persistent sequential nodes.

In this article, we will write a distributed lock implemented by ZooKeeper.

Design

Refer to the concurrent programming of JUC-locks-ReentrantLock, which was learned before, to implement the java.util.concurrent.locks.Lock interface.

We implement a reentrant lock by rewriting the method in the interface.

  • Lock: Request a lock, return directly if successful, and block until the lock is acquired if unsuccessful.
  • Lock Interruptibly: Request a lock, and if it fails, wait until the lock is acquired or the thread interrupts
  • tryLock: 1. Attempt to acquire the lock and return to false if the acquisition fails. No more waiting. 2. Attempt to retrieve the lock and get it back to true successfully. Otherwise, keep requesting until the time-out returns false.
  • unlock: release lock

We use ZooKeeper's EPHEMERAL temporary node mechanism. If we can create it successfully, we can get the lock successfully. After releasing the lock or disconnecting the client, the temporary node will be deleted automatically, which can avoid the case of deletion by mistake or omission.

After the lock acquisition fails, we use polling to keep trying to create it. In fact, Watcher mechanism should be used to achieve this, which can avoid a large number of useless requests. We'll use it in the next section for a more elegant distributed lock implementation mechanism.

DistributedLock

public class DistributedLock implements Lock { private static Logger logger = LoggerFactory.getLogger(DistributedQueue.class); //ZooKeeper client, ZooKeeper operation private ZooKeeper zooKeeper; //root node name private String dir; //lock node private String node; //ZooKeeper authentication information List <ACL> acls; //lock node private String full Path; //lock identification, for 0, indicates that no lock is obtained, each acquisition of a lock. The second lock is added one, and the release time is reduced one. Disconnect and delete temporary nodes when reduced to 0. Private volatile int state; /*** Constructor. * * @param zoo Keeper the zoo keeper * @param dir the dir * @param node the node * @param ACLS the ACLS */ public Distributed Lock (ZooKeeper zoo Keeper, String dir, String node, List < ACL > acls) {this.zoo Keeper = zoo Keeper; this.dir = dir. E; this. ACLS = acls; this. fullPath = dir. concat ("/"). concat (node); init ();} private void init () {try {Stat stat = zooKeeper. exists (dir, false); if (stat = null) {zooKeeper. create (dir, null, acls, CreateMode. STENT);}} {Exception {logattribute ("[Lociniger\ error #Locinik];"+ E. toString (), e;}}} 
 

lock

public void lock() { //The reentry mechanism is implemented by state. If the lock has been acquired, state++ can be used. If (addLockCount (){return;}// Always try to acquire locks until they succeed for (;){try {// create temporary node zooKeeper. create (full Path, null, acls, CreateMode. EPHEMERAL); / / / First acquire locks, state++, there is no need to use locking mechanism to ensure atomicity, because at the same time, only one temporary node zooKeeper. Three threads can create the node successfully. State+; break;} catch (Interrupted Exception) {// If an interrupt exception is caught, the current thread is set to interrupt status logger. error ("[Distributed Lock#lock] error:"+ie. toString (), (ie)"; Thread. current Thread (). interrupt ();} catch (KeeperException) {// If the exception caught is that the node already exists For other exceptions, set the current thread to interrupt status logger. error ("[Distributed Lock# lock] error:"+ke. toString (), [ke]); if (!! KeeperException. Code. NODEEXISTS. equals (ke. code ()] {Thread. current Thread (). interrupt ();}}}}}}} 
 

lockInterruptibly

public void lockInterruptibly() throws InterruptedException { //The reentry mechanism is implemented by state. If the lock has been acquired, state++ can be used. If (addLockCount (){return;} for (;){interrupted (){throw new Interrupted Exception ();} try {zooKeeper. create (fullPath, null, acls, CreateMode. EPHEMERAL); ++; break;} (Exception) {Interrupt if capture If an interrupt exception is found, the current thread is set to interrupt status logger. error ("[Distributed Lock# lockInterruptibly] error:"+ie. toString (), ie);"Thread. current Thread (). interrupt ();} catch (KeeperException){// If the exception caught is any other exception outside the node, the current thread is set to medium. Break state logger. error ("[Distributed Lock# lockInterruptibly] error:"+ke. toString (), [ke]; if (!! KeeperException. Code. NODEEXISTS. equals (ke. code ())] {Thread. current Thread (). interrupt ();}}}}} 
 

tryLock

public boolean tryLock() { //The reentry mechanism is implemented by state. If the lock has been acquired, state++ can be used. If (addLockCount ()) {return true;} // / if successful, return true, and if unsuccessful, return false try {zooKeeper. create (full Path, null, acls, CreateMode. EPHEMERAL); state +; return true;} (Exception) catch {logger. error ("[Distributed Lock#tryLock] error:"+e. toString ();} return;} pub} return false;} Pub LIC Boolean tryLock (long time, TimeUnit unit) throws InterruptedException {// through state to implement reentry mechanism. If locks have been acquired, state++ can be used. If (addLockCount (){return true;} // If you attempt to obtain a timeout, return false long nanosTimeout = unit. toNanos (time); if (nanosTimeout <= 0L) {return;} final long deadline = System. nanoTime () + nanosTimeout; for (;){// Threa if the current thread is interrupted D.interrupted (){throw new InterruptedException ();} // If you try to get timeout, return false nanosTimeout = deadline - System. nanoTime (); if (nanosTimeout <= 0L) {return;} try {zooKeeper. create (fullPath, null, ls, temode, EPMERAL); catch +; return} (InterruptedExchange) If an interrupt exception is caught, false logger. error ("[Distributed Lock tryLock] error:"+ie. toString (), [ie)]; return false;} catch (KeeperException) {// If the exception caught is any other exception that exists outside the node, return false logger. error ("[Distributed Lock tryLock]) Or: "+ke.toString(), ke); if (! KeeperException. Code. NODEEXISTS. equals (ke. code ()) {return false;}}}}}}}}} 
 

unlock

public void unlock() { //The reentry mechanism is implemented through state. If the lock has been acquired and released, the state--. delLockCount(); // If the state is 0, it means that the lock is no longer held, the connection needs to be closed and the temporary node if (state = 0 & & zooKeeper!= null) {try {zooKeeper. close ();} catch (Interrupted Exception) {logger. error ("[Distributed Lock unlock] error:"+e. String ();}}}}}} 
 

addLockCount

private boolean addLockCount() { //If the state is greater than 0, that is, the lock is held, add the number of States to if (state > 0) {synchronized (this) {if (state > 0) {state ++; return true;}} return false;} 
 

delLockCount

private boolean delLockCount() { //If the state is greater than 0, it also holds a lock. Reduce the number of states by one if (state > 0) {synchronized (this) {if (state > 0) {state--; return true;}} return false;} 
 

summary

Above is a distributed re-entrant lock implemented through ZooKeeper, taking advantage of the temporary node characteristics. The source code is visible: aloofJr

There are several points that can be optimized.

  • Change polling mode to Watcher mechanism
  • Optimizing the Realization of Re-entrant Lock
  • All threads compete for the creation of a node, which is prone to herd effect and an unfair lock competition mode.

In the next section, we use a new way to implement distributed locks to solve the above problems. If you have good optimization suggestions, you are welcome to discuss them together.

More articles

See my blog: https://nc2era.com

Written by Aloof Jr, reprinted please indicate the source

 

Original link

This article is the original content of Yunqi Community, which can not be reproduced without permission.

Posted by washbucket on Mon, 23 Sep 2019 00:08:11 -0700