Implementation of Curator Distributed Lock

Posted by nikbone on Wed, 31 Jul 2019 09:28:58 +0200

Before learning about the implementation of Curator distributed locks, it is recommended that steps be taken: Implementation of Distributed Lock Based on ZooKeeper To understand the implementation principle of ZooKeeper API distributed lock.

Based on ZooKeeper distributed lock implementation, there are two ways:

Utilize ZooKeeper's uniqueness of the same node path (not recommended)

2. Use ZooKeeper ordered node feature (temporary ordered node) (recommended)

The implementation of Curator distributed lock is based on ZooKeeper ordered node characteristics. The node names Curator stores in ZooKeeper are similar to the following:

1. Re-entrant locks and non-re-entrant locks

Curator implements a set of distributed locks, which can be re-locked and non-re-locked. In Java, we know that synchronized and ReentrantLock are re-entrant locks, so what's the difference between re-entrant locks and non-re-entrant locks?

Re-entrainable lock: Simply understand is a class of A, B two methods, A, B have access to the same lock, when method A calls, get the lock, when method A's lock has not been released, call method B also get the lock. Re-entrant locks prevent deadlock situations.

Non-reentrant lock: Simply understand is a class of A, B two methods, A, B have a unified lock, when method A calls, get a lock, when method A's lock has not been released, call method B, method B can not get the lock, must wait for method A to release the lock.

2. Let's look at some ways to implement Curator distributed locks

In Curator, the implementation of re-entrant locks is implemented by InterProcess Mutex-like, while the implementation of non-re-entrant locks is implemented by InterProcess Semaphore Mutex-like, similar to InterProcess Mutex.

Let's take InterProcess Mutex as an example to understand the implementation of some specific methods in Curator. Its constructor is:

public InterProcessMutex(CuratorFramework client, String path);

By acqui () method, locks can be acquired and timeout mechanism can be provided.

public void acquire(){...}
public boolean acquire(long time,TimeUnit uinit){...}

By the release() method, locks can be released

public void release(){...}

With the makeRevocable() method, locks can be set as revocable locks, and when other processes or threads want you to release locks, the Revocation Lister calls back.

public void makeRevocable(RevocationListener<T> listener){...}

If you want to request that the current lock be revoked, you can call the attemptRevoke() method, noting that the RevocationListener will call back when the lock is released.

public static void attemptRevoke(CuratorFramework client,String path){...}

3. Using Curator to implement distributed locks through scenarios

Scenario reproduction:

Now we often kill or rush to buy some goods. In order to prevent the problem of selling more and selling less, we will use the lock mechanism to solve these problems. Here, we take the snap-up of mobile phones as an example. There are 5 mobile phones and 10 people buy them to use Curator to complete the implementation of distributed locks.

First, let's create a simulated shared resource Phone class (how many phones are available in the mobile warehouse)

public class Phone {
    /**
     * Mobile phone inventory (5 units)
     */
    private int number = 5;

    public int getNumber() {
        return number;
    }

    public void setNumber(int number) {
        this.number = number;
    }
}

1. Realization of Re-entrant Lock

The realization of re-entrant lock can satisfy the success of snap-in and complete the payment operation after obtaining the lock.

/**
 * Distributed Lock Based on Curator (Re-Lockable)
 */
public class CuratorDistrutedLock{

    //Curator client
    private static CuratorFramework client;
    //Name
    private static String clientName;
    //Distributed Lock Node (Re-Lockable)
    private static String lockPath = "/example/lock";
    //Cluster Address
    private static String clientAddr = "192.168.204.202:2181";

    /**
     * Get client connections (create nodes)
     */
    public static CuratorFramework getClientConnection(){
        //Get Curator Connection
        RetryPolicy retryPolicy = new ExponentialBackoffRetry(1000,3);
        client = CuratorFrameworkFactory.newClient(clientAddr, retryPolicy);
        client.start();
        return client;
    }

    /**
     * Re-entrainable lock
     */
    public static void main(String[] args) throws IOException {
        CuratorFramework client = getClientConnection();
        Phone phone = new Phone();
        //Re-entrainable lock
        InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
        for (int i = 0; i < 10; i++) {

            new Thread(()->{
                try {
                    //Add the first lock
                    mutex.acquire();
                    System.out.println(Thread.currentThread().getName() + "Get the first lock");
                    //Complete the snap-up operation
                    buy(phone);

                    if(phone.getNumber() >= 0){
                        //Add a second lock
                        mutex.acquire();
                        System.out.println(Thread.currentThread().getName() + "Get the second lock");
                        //Complete payment operation
                        pay();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    try {
                        //Re-entrant locks, use a few locks, release a few locks
                        //Release the first lock
                        mutex.release();

                        if(phone.getNumber() >= 0){
                            //Release the second lock
                            mutex.release();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();
        }
    }

    /**
     * Rush to buy
     */
    public static void buy(Phone phone) {
        System.out.println("[" + Thread.currentThread().getName() + "]Start snapping up");
        //Get the number of mobile phones left

        int currentNumber = phone.getNumber();

        if (currentNumber <= 0) {
            System.out.println("The snap-up is over. Come back next time.");
            phone.setNumber(-1);
        } else if(currentNumber > 0){
            System.out.println("Number of remaining mobile phones:" + currentNumber);
            //Reduce the quantity after purchase by 1
            currentNumber--;
            phone.setNumber(currentNumber);
            System.out.println("[" + Thread.currentThread().getName() + "]Acquisition Completion");
        }
        System.out.println("------------------------------------------------------");
    }

    /**
     * payment
     */
    public static void pay(){
        System.out.println("[" + Thread.currentThread().getName() + "]Start Payment");
        System.out.println("[" + Thread.currentThread().getName() + "]Payment completed");
        System.out.println("*****************************************************");
    }
}

Test results:

Thread-6 gets the first lock
 [Thread-6] Start snapping up
 Number of remaining mobile phones: 5
 [Thread-6] Acquisition Completion
------------------------------------------------------
Thread-6 gets the second lock
 [Thread-6] Start Payment
 [Thread-6] Payment completed
*****************************************************
Thread-1 Gets the First Lock
 [Thread-1] Start snapping up
 Number of remaining mobile phones: 4
 [Thread-1] Acquisition Completion
------------------------------------------------------
Thread-1 gets the second lock
 [Thread-1] Start Payment
 [Thread-1] Payment completed
*****************************************************
Thread-9 Gets the First Lock
 [Thread-9] Start snapping up
 Number of remaining mobile phones: 3
 [Thread-9] Acquisition Completed
------------------------------------------------------
Thread-9 Gets the Second Lock
 [Thread-9] Start Payment
 [Thread-9] Payment completed
*****************************************************
Thread-5 Gets the First Lock
 [Thread-5] Start snapping up
 Number of remaining mobile phones: 2
 [Thread-5] Acquisition Completion
------------------------------------------------------
Thread-5 Gets the Second Lock
 [Thread-5] Start Payment
 [Thread-5] Payment completed
*****************************************************
Thread-7 Gets the First Lock
 [Thread-7] Start snapping up
 Number of remaining mobile phones: 1
 [Thread-7] Acquisition Completion
------------------------------------------------------
Thread-7 Gets the Second Lock
 [Thread-7] Start Payment
 [Thread-7] Payment completed
*****************************************************
Thread-4 Gets the First Lock
 [Thread-4] Start snapping up
 The snap-up is over. Come back next time.
------------------------------------------------------
Thread-8 Gets the First Lock
 [Thread-8] Start snapping up
 The snap-up is over. Come back next time.
------------------------------------------------------
Thread-10 Gets the First Lock
 [Thread-10] Start snapping up
 The snap-up is over. Come back next time.
------------------------------------------------------
Thread-3 Gets the First Lock
 [Thread-3] Start snapping up
 The snap-up is over. Come back next time.
------------------------------------------------------
Thread-2 Gets the First Lock
 [Thread-2] Start snapping up
 The snap-up is over. Come back next time.
------------------------------------------------------

2. Implementing non-reentrant locks

The non-reentrant lock is implemented using the InterProcessSemaphoreMutex class. Compared with the above InterProcessMutex, the operation is the same, that is, the function of Reentrant is less, which means that it cannot be reentrated in the same thread. Can not re-enter the lock, can not satisfy the successful purchase after the lock, and pay for the operation. Do not re-lock, easy to cause deadlock phenomenon.

In terms of code, you can modify the re-entrant lock section, and the rest will remain unchanged.

//Re-entrainable lock
InterProcessMutex mutex = new InterProcessMutex(client,lockPath);
//Modify it to the following sentence.
//Non-reentrant locks
InterProcessSemaphoreMutex mutex = new InterProcessSemaphoreMutex(client,lockPath);

Test results:

Thread-9 Gets the First Lock
 [Thread-9] Start snapping up
 Number of remaining mobile phones: 5
 [Thread-9] Acquisition Completed
------------------------------------------------------

With non-reentrant locks, you will find that Thread-9 (Thread 9) acquires locks, completes the snap-up operation, and does not perform payment operations, but stagnates there, resulting in other threads unable to complete the snap-up operation, resulting in deadlock operations.

This is because thread 9 does not release the lock after it has acquired it, which is the difference between non-reentrant locks and reentrant locks. If the second lock is not added (and the release operation of the second lock is deleted at the same time), the process can be executed smoothly.

END

Topics: Mobile Zookeeper less Java