Zookeeper Distributed Lock - Diagram - Second Understanding

Hello everyone, meet again, I'm your friend Quanzhanjun.

In a single application development scenario, when concurrent synchronization is involved, people often use synchronized or Lock to solve the synchronization problem between multiple threads. However, in the development scenario of distributed cluster work, a more advanced locking mechanism is required to deal with the problem of data synchronization across JVM processes, which is distributed locks.

See the companion article to this article for the basics of distributed locks: Redis Distributed Lock (Illustration - Second Understanding - The Most Complete in History)

Principles of fair locks and reentrant locks

The most classic distributed lock is the reentrant fair lock. What is a reentrant fair lock? The concepts and principles explained directly will be more abstract and difficult to understand, so let’s start with specific examples! Here is an analogy with a simple story, which is probably much simpler.

The story takes place in an ancient time when there was no running water. There was a well in a village. The water quality was very good. The villagers rushed to get the water from the well. There is only one well, and there are many people in the village. The villagers fought and fought for water, and even broke their heads.

Problems always have to be solved, so the village chief racked his brains and finally came up with a plan to get water by number. A well watcher is arranged by the well to maintain the order of water intake. The order of taking water is simple:

(1) Before taking water, take the number first;

(2) The number in the front, you can get the water first;

(3) Those who arrive first are in front, those who arrive later, one by one, lined up by the well.

The schematic diagram of water intake is shown in Figure 10-3.

Figure 10-3 Schematic diagram of queuing for water

This queuing water model is a lock model. The number in the first row has the right to draw water, which is a typical exclusive lock. In addition, first-come, first-served, the person in front of the line will get the water first, and after the water, the next person will get the water, which is fair, indicating that it is a kind of fair lock.

What is a reentrant lock? It is assumed that the family is the unit when water is drawn. Someone in the family gets the number, and other family members come to fetch water. At this time, there is no need to take the number, as shown in Figure 10-4.

Figure 10-4 People in the same household do not need to queue up repeatedly

In Figure 10-4, for the family ranked No. 1, the husband takes the number, and if his wife comes, he will be ranked first. Look at No. 2 in the picture above. The father is fetching water. Assuming that his son and daughter have also reached the well, they will be ranked second directly. The so-called son depends on the father. In a word, if the unit of family is used for water collection, the same family can directly reuse the row number, and there is no need to re-take the number from the rear row.

In the above story model, the number is taken once, and it can be used to take water multiple times. The principle is the model of reentrant lock. In the reentrant lock model, an exclusive lock can be locked multiple times, which is called a reentrant lock.

ZooKeeper The principle of distributed lock

After understanding the principle of the classic fair reentrant lock, let's look at the principle of the fair reentrant lock in the distributed scenario. Through the previous analysis, it can basically be determined: ZooKeeper Temporary sequential nodes are born with an embryo that implements distributed locks. why?

(1) Each node of ZooKeeper is a natural sequencer.

Create a temporary sequence node (EPHEMERAL_SEQUENTIAL) type under each node. After the new child node, a sequence number will be added, and the generated sequence number is the previous generated sequence number plus one.

For example, if there is a node "/test/lock" for numbering as the parent node, temporary sequential child nodes with the same prefix can be created under this parent node, assuming the same prefix is ​​"/test/lock/seq-". The first child node created should basically be /test/lock/seq-0000000000, the next node should be /test/lock/seq-0000000001, and so on, as shown in 10-5.

Figure 10-5 The natural issuer function of Zookeeper temporary sequence nodes

(2) The incremental ordering of ZooKeeper nodes can ensure the fairness of locks

A ZooKeeper distributed lock needs to first create a parent node, try to be a persistent node (PERSISTENT type), and then each thread that wants to acquire the lock creates a temporary sequential node under this node. Since the ZK nodes are in the order of creation, they are incremented in turn.

In order to ensure fairness, it can be simply stipulated that the node with the smallest number indicates that the lock has been acquired. Therefore, before each thread tries to occupy the lock, it first determines whether its row number is currently the smallest, and if so, acquires the lock.

(3) ZooKeeper's node monitoring mechanism can ensure the orderly and efficient delivery of possession locks

Before each thread preempts the lock, it tries to create its own ZNode. Similarly, when the lock is released, the created Znode needs to be deleted. After the creation is successful, if it is not the node with the smallest row number, it is in a state of waiting for notification. Who are you waiting for? No need for anyone else, just wait for the previous Znode notification on it. When the previous Znode is deleted, the Znode event will be triggered, and the current node can monitor the deletion event, that is, when it is its turn to own the lock. The first informs the second, the second informs the third, and goes backwards in sequence like a drumming of flowers.

ZooKeeper's node monitoring mechanism can perfectly realize this kind of information transmission like drumming and spreading flowers. The specific method is that each Znode node waiting for notification only needs to listen (linsten) or monitor (watch) the node numbered in front of itself, and the node immediately in front of itself, can receive its deletion event. As long as the previous node is deleted, it will judge again to see if it is the node with the smallest serial number. If so, it will acquire the lock.

In addition, the excellent internal mechanism of ZooKeeper can ensure that the lock can be effectively released when the client holding the lock in the cluster loses connection due to network abnormality or other reasons. Once the client holding the Znode lock loses contact with the ZooKeeper cluster server, this temporary Znode will also be deleted automatically. The node behind it can also receive the delete event and acquire the lock. It is for this reason that when creating a fetch node, try to create a temporary znode node,

(4) ZooKeeper's node monitoring mechanism can avoid the herd effect

ZooKeeper's method of connecting end to end and monitoring the front at the back can avoid the herd effect. The so-called herd effect is that when a node hangs, all nodes listen and then respond, which will put huge pressure on the server, so there are temporary sequential nodes, when a node hangs, only the node behind it just react.

Illustration: Preemption process of distributed locks

Next, let's take a look at the entire process and the principle behind the multi-client acquisition and release of zk distributed locks.

First of all, let's take a look at the figure below. If there are two clients competing for a distributed lock on zk, what will be the scenario?

If you don't know much about zk, it is recommended to do a Baidu search on your own first, and briefly understand some basic concepts, such as what types of nodes are there in zk and so on.

See image above. There is a lock in zk, and this lock is a node on zk. Then, both clients have to acquire this lock. How to acquire it?

Let's assume that client A takes the lead and initiates a distributed lock request to zk. This lock request uses a special concept in zk called "temporary sequence node".

Simply put, it is to create a sequence node directly under the lock node "my_lock". This sequence node has a node sequence number maintained by zk itself.

Client A initiates a lock request

For example, the first client to engage in a sequence node, zk will give it a name: xxx-000001. Then the second client comes to make a sequence node, zk may be named: xxx-000002. Please note that the last number is incremented sequentially, starting from 1. zk will maintain this order.

So at this time, if client A initiates a request first, a sequential node will be created. Looking at the figure below, the Curator framework will probably look like this:

You see, when client A initiates a lock request, it will first create a temporary sequence node under the node you want to lock. This long name is generated by the Curator framework itself.

Then, that last number is "1". Please pay attention, because client A is the first to initiate the request, the serial number of the sequence node obtained for him is "1".

Then client A creates a sequence node. Before it's over, he will check all the child nodes under the lock node "my_lock", and these child nodes are sorted by serial number. At this time, he will probably get such a set:

Then client A will make a critical judgment, which is to say: Alas! Brother, in this set, is the sequence node I created the first one?

If so, then I can lock it! Because it is obvious that I am the first person to create a sequential node, so I am the first person to try to add distributed locks!

bingo! Locked successfully! Look at the picture below, and then intuitively feel the whole process.

Client B comes to queue

Next, suppose that client A has finished locking, and client B wants to lock. At this time, he will do the same thing: first create a temporary sequence node under the lock node "my_lock", then The name will become something like:

Take a look at the picture below:

Because client B is the second to create a sequence node, zk will maintain the sequence number as "2" internally.

Then client B will follow the lock judgment logic, query all the child nodes under the "my_lock" lock node, and arrange them in order of serial numbers. At this time, what he sees is similar to:

At the same time, check the order node created by yourself, is it the first one in the collection?

Obviously not. At this time, the first one is the sequence node created by client A, the one with the serial number "01". So the lock failed!

Client B starts listening to client A

After the lock fails, client B will add a listener to the previous sequence node of his sequence node through ZK's API. zk can naturally monitor a node.

If you don't know the basic usage of zk, you can check it on Baidu, it's very simple. Client B's sequential nodes are:

Isn't his last sequential node the following one?

That is, the sequential node created by client A!

So, Client B would:

Add a listener to this node to monitor whether this node is deleted and other changes! Look at the picture below.

Then, after client A locks, it may process some code logic, and then release the lock. So, what is the process of releasing the lock?

In fact, it is very simple, that is, the sequence node created by yourself in zk, that is:

This node is deleted.

After deleting the node, zk will be responsible for notifying the listener listening to this node, that is, the listener added before client B, saying: Brother, the node you listened to has been deleted, and someone has released the lock.

At this time, the listener of client B perceives that the previous sequential node is deleted, that is, a client before him releases the lock.

Client B successfully grabs the lock

At this point, client B will be notified to retry to acquire the lock, that is, to acquire the set of child nodes under the "my_lock" node, which is:

There is only one sequential node created by client B in the collection at this time!

Then, client B judges that it is the first sequential node in the set, bingo! Can be locked! Complete the lock directly, run the subsequent business code, and release the lock again after running.

Basic implementation of distributed locks

The next step is to implement distributed locks based on ZooKeeper. First, a lock interface Lock is defined, which is very simple, with only two abstract methods: a locking method and an unlocking method. The code for the Lock interface is as follows:

package com.crazymakercircle.zk.distributedLock;

/**
 * create by Nien @ Crazy Maker Circle
 **/
public interface Lock {
    /**
     * lock method
     *
     * @return Whether the lock is successful
     */
    boolean lock() throws Exception;

    /**
     * Unlock method
     *
     * @return Whether it was successfully unlocked
     */
    boolean unlock();
}
copy

Using ZooKeeper to implement the distributed lock algorithm, there are the following points:

(1) A distributed lock is usually represented by a Znode node; if the Znode node corresponding to the lock does not exist, the Znode node is created first. Here it is assumed to be "/test/lock", which represents a distributed lock that needs to be created.

(2) All clients that preempt the lock are represented by the list of child nodes of the Znode node of the lock; if a client needs to occupy the lock, a temporary ordered child node is created under "/test/lock".

Here, all temporary ordered child nodes share a meaningful child node prefix as much as possible.

For example, if the prefix of the child node is "/test/lock/seq-", the child node corresponding to the first lock grab is "/test/lock/seq-000000000", and the child node corresponding to the second lock grab is "/test/lock/seq-000000001", and so on.

For another example, if the prefix of the child node is "/test/lock/", the child node corresponding to the first lock grab is "/test/lock/000000000", and the child node corresponding to the second lock grab is "/test/ lock/000000001", and so on, is also very intuitive.

(3) What if it is determined whether the client owns the lock? It is very simple. After the client creates a child node, it needs to determine whether the child node created by itself is the child node with the smallest serial number in the current child node list. If it is, it is considered that the lock is successful; if not, it monitors the change message of the previous Znode child node and waits for the previous node to release the lock.

(4) Once the next node in the queue gets the previous child node change notification, it starts to judge whether it is the child node with the smallest serial number in the current child node list. If so, it is considered that the locking is successful; if not , then keep listening until the lock is acquired.

(5) After acquiring the lock, start processing the business process. After completing the business process, delete its corresponding child node and complete the work of releasing the lock, so that the successor node can capture the node change notification and obtain the distributed lock.

Actual combat: the realization of locking

The locking method in the Lock interface is lock(). The general flow of the lock() method is: first try to lock, if the lock fails, wait, and then repeat.

1. Implementation code of lock() method

The implementation code of lock() method locking is roughly as follows:

package com.crazymakercircle.zk.distributedLock;

import com.crazymakercircle.zk.ZKclient;
import lombok.extern.slf4j.Slf4j;
import org.apache.curator.framework.CuratorFramework;
import org.apache.zookeeper.WatchedEvent;
import org.apache.zookeeper.Watcher;

import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * create by Nien @ Crazy Maker Circle
 **/
@Slf4j
public class ZkLock implements Lock {
    //Node Links for ZkLock
    private static final String ZK_PATH = "/test/lock";
    private static final String LOCK_PREFIX = ZK_PATH + "/";
    private static final long WAIT_TIME = 1000;
    //Zk client
    CuratorFramework client = null;

    private String locked_short_path = null;
    private String locked_path = null;
    private String prior_path = null;
    final AtomicInteger lockCount = new AtomicInteger(0);
    private Thread thread;

    public ZkLock() {
        ZKclient.instance.init();
        synchronized (ZKclient.instance) {
            if (!ZKclient.instance.isNodeExist(ZK_PATH)) {
                ZKclient.instance.createNode(ZK_PATH, null);
            }
        }
        client = ZKclient.instance.getClient();
    }

    @Override
    public boolean lock() {
//Reentrant, to ensure the same thread, can be locked repeatedly

        synchronized (this) {
            if (lockCount.get() == 0) {
                thread = Thread.currentThread();
                lockCount.incrementAndGet();
            } else {
                if (!thread.equals(Thread.currentThread())) {
                    return false;
                }
                lockCount.incrementAndGet();
                return true;
            }
        }

        try {
            boolean locked = false;
//First try to lock
            locked = tryLock();

            if (locked) {
                return true;
            }
            //If the lock fails, wait
            while (!locked) {

                await();

                //Get a list of waiting child nodes

                List<String> waiters = getWaiters();
//Determine whether the lock is successful
                if (checkLocked(waiters)) {
                    locked = true;
                }
            }
            return true;
        } catch (Exception e) {
            e.printStackTrace();
            unlock();
        }

        return false;
    }



//...omit the other methods

}
copy

2. tryLock() tries to lock

The tryLock method that tries to lock is the key and does two important things:

(1) Create a temporary sequence node and save its own node path

(2) Determine whether it is the first one, and if it is the first one, the locking is successful. If not, find the previous Znode and save its path to prior_path.

Try to lock the tryLock method, and its implementation code is as follows:

   /**
     * try to lock
     * @return Whether the lock is successful
     * @throws Exception abnormal
     */
    private boolean tryLock() throws Exception {
        //Create a temporary Znode
        locked_path = ZKclient.instance
                .createEphemeralSeqNode(LOCK_PREFIX);
        //then get all nodes
        List<String> waiters = getWaiters();

        if (null == locked_path) {
            throw new Exception("zk error");
        }
        //Get the locked queue number
        locked_short_path = getShortPath(locked_path);

        //Get the list of waiting child nodes to determine whether you are the first
        if (checkLocked(waiters)) {
            return true;
        }

        // Judge yourself
        int index = Collections.binarySearch(waiters, locked_short_path);
        if (index < 0) { // Network jitter, you may no longer have yourself in the obtained child node list
            throw new Exception("node not found: " + locked_short_path);
        }

        //If you do not acquire the lock yourself, you must listen to the previous node
        prior_path = ZK_PATH + "/" + waiters.get(index - 1);

        return false;
    }

    private String getShortPath(String locked_path) {

        int index = locked_path.lastIndexOf(ZK_PATH + "/");
        if (index >= 0) {
            index += ZK_PATH.length() + 1;
            return index <= locked_path.length() ? locked_path.substring(index) : "";
        }
        return null;
    }
copy

After the temporary sequence node is created, its full path is stored in the locked_path member; in addition, a suffix path is intercepted and placed in the locked_path member. In the locked_short_path member, the suffix path is a short path, only the last layer of the full path. Why save short paths separately? Because, when other paths in the obtained remote child node list return results, they are all short paths and only the last layer of paths. Therefore, in order to facilitate subsequent comparisons, I also save my own short path.

After creating its own temporary node, call the checkLocked method to determine whether the lock is successful. If the lock is successful, it will return true; if the lock is not obtained, the previous node needs to be monitored. At this time, the path of the previous node needs to be found and saved in the prior_path In the member, it is used for the subsequent await() waiting method to monitor. Before entering the introduction of the await() waiting method, let's talk about checkLocked Lock judgment method.

3. checkLocked() checks if the lock is held

In the checkLocked() method, determine whether the lock can be held. The judgment rule is very simple: whether the currently created node is in the first position of the child node list obtained in the previous step:

(1) If it is, it means that the lock can be held, and returns true, indicating that the lock is successful;

(2) If not, it means that other threads have already held the lock and return false.

The code for the checkLocked() method is as follows:

   private boolean checkLocked(List<String> waiters) {

        //Nodes are sorted by number, in ascending order
        Collections.sort(waiters);

        // If it is the first one, it means that it has acquired the lock
        if (locked_short_path.equals(waiters.get(0))) {
            log.info("Successfully acquired distributed lock,Node is{}", locked_short_path);
            return true;
        }
        return false;
    }
copy

The checkLocked method is relatively simple. It sorts the list of all child nodes participating in the queue from small to large according to the node name. Sorting mainly depends on the number of the node, which is the 10-digit number of the path after the Znode, because the prefix is ​​the same. After sorting, make a judgment. If your locked_short_path number position is ranked first, if it is, it means that you have obtained the lock. If not, it will return false.

If checkLocked() is false, the calling method of the outer layer will generally execute the await() waiting method, and execute the waiting logic after the lock capture fails.

4. await() listens for the previous node to release the lock

await() is also very simple, it is to listen to the deletion event of the previous ZNode node (prior_path member), the code is as follows:

   private void await() throws Exception {

        if (null == prior_path) {
            throw new Exception("prior_path error");
        }

        final CountDownLatch latch = new CountDownLatch(1);


        //Subscribe to the deletion event of the next smallest order node than yourself
        Watcher w = new Watcher() {
            @Override
            public void process(WatchedEvent watchedEvent) {
                System.out.println("monitored changes watchedEvent = " + watchedEvent);
                log.info("[WatchedEvent]Node deletion");

                latch.countDown();
            }
        };

        client.getData().usingWatcher(w).forPath(prior_path);
/*
        //Subscribe to the deletion event of the next smallest order node than yourself
        TreeCache treeCache = new TreeCache(client, prior_path);
        TreeCacheListener l = new TreeCacheListener() {
            @Override
            public void childEvent(CuratorFramework client,
                                   TreeCacheEvent event) throws Exception {
                ChildData data = event.getData();
                if (data != null) {
                    switch (event.getType()) {
                        case NODE_REMOVED:
                            log.debug("[TreeCache]Node delete, path={}, data={}",
                                    data.getPath(), data.getData());

                            latch.countDown();
                            break;
                        default:
                            break;
                    }
                }
            }
        };

        treeCache.getListenable().addListener(l);
        treeCache.start();*/
        latch.await(WAIT_TIME, TimeUnit.SECONDS);
    }
copy

First add a Watcher listener, and the listening node is the path of the previous node saved in the prior_path member. Here, only to monitor the changes of the previous node, not the changes of other nodes, to improve efficiency. After completing the monitoring, call latch.await(), and the thread enters the waiting state until the thread is awakened by latch.countDown() in the monitoring callback code, or waits for a timeout.

illustrate

The core principles and practical knowledge of CountDownLatch used in the above code, "Netty Zookeeper Redis High Concurrency Actual Combat" companion volume "Java High Concurrency Core Programming (Volume 2)".

In the above code, to monitor the deletion of the previous node, two monitoring methods can be used:

(1) Watcher subscription;

(2) TreeCache subscription.

The effects of both methods are similar. However, the deletion event here only needs to be monitored once, and does not need to be monitored repeatedly, so Watcher is used. One-time subscription. The code subscribed by TreeCache has been annotated in the source code project for your reference only.

Once the previous node priority_path node is deleted, the thread is woken up from the waiting state, and a new round of lock contention is performed until the lock is acquired and the business processing is completed.

So far, the distributed Lock algorithm is almost finished. This is to achieve reentrancy of locks.

5. reentrant implementation code

What is reentrant? It is only necessary to ensure that the same thread enters the locking code, and the locking can be repeated successfully. Modify the previous lock method and add reentrant judgment logic in front. code show as below:

@Override

public boolean lock() {

//reentrant judgment

synchronized (this) {

if (lockCount.get() == 0) {

thread = Thread.currentThread();

lockCount.incrementAndGet();

} else {

if (!thread.equals(Thread.currentThread())) {

return false;

}

lockCount.incrementAndGet();

return true;

}

}

//....

}
copy

In order to become reentrant, a locked counter lockCount is added to the code , and count the number of repeated locks. If the same thread is locked, just increase the number of times and return directly, indicating that the locking is successful.

So far, the lock() method has been introduced, the next step is to release the lock

Actual combat: the realization of releasing the lock

The unLock() method in the Lock interface means releasing the lock. There are two main tasks for releasing the lock:

(1) Reduce the count of reentrant locks. If the final value is not 0, return directly, indicating that it has been successfully released once;

(2) If the counter is 0, remove the Watchers listener and delete the created Znode temporary node.

The code of the unLock() method is as follows:

    /**
     * release lock
     *
     * @return Whether the lock was released successfully
     */
    @Override
    public boolean unlock() {
//Only the locked thread can unlock
        if (!thread.equals(Thread.currentThread())) {
            return false;
        }
//Reduce reentrant count
        int newLockCount = lockCount.decrementAndGet();
//Count cannot be less than 0
        if (newLockCount < 0) {
            throw new IllegalMonitorStateException("Lock count has gone negative for lock: " + locked_path);
        }
//If the count is not 0, return directly
        if (newLockCount != 0) {
            return true;
        }
        //delete temporary node
        try {
            if (ZKclient.instance.isNodeExist(locked_path)) {
                client.delete().forPath(locked_path);
            }
        } catch (Exception e) {
            e.printStackTrace();
            return false;
        }

        return true;
    }
copy

Here, in order to ensure thread safety as much as possible, the type of reentrant counter is not the int type, but the atomic type in Java concurrent packets - AtomicInteger.

Actual combat: the use of distributed locks

Write a use case to test the use of ZLock, the code is as follows:

   @Test
    public void testLock() throws InterruptedException {
        for (int i = 0; i < 10; i++) {
            FutureTaskScheduler.add(() -> {
                //create lock
                ZkLock lock = new ZkLock();
                lock.lock();
//Each thread, execute 10 accumulations
                for (int j = 0; j < 10; j++) {
//public resource variable accumulation
                    count++;
                }
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.info("count = " + count);
                //release lock
                lock.unlock();

            });
        }

        Thread.sleep(Integer.MAX_VALUE);
    }
copy

The above code is 10 concurrent tasks, and each task is accumulated 10 times. If the above use case is executed, the result will be the expected sum of 100. If the lock is not used, the result may not be 100, because the above count is an ordinary variable. Not thread safe.

illustrate

For the core principles and practical knowledge of thread safety, please refer to the next volume of this book, "Java High Concurrency Core Programming (Volume 2)".

In principle, a Zlock instance represents a lock and needs to occupy a Znode permanent node. If many distributed locks are required, many different Znode nodes are also required. If the above code is to be extended to multiple distributed lock versions, a simple transformation is required. This transformation is left to your own practice and implementation.

Actual combat: curator's InterProcessMutex reentrant lock

Distributed lock Zlock independently realizes the main value: learn the principle and basic development of distributed lock, that's all. In actual development, if you need to use distributed locks, and it is recommended to build your own wheels, it is recommended to directly use various officially implemented distributed locks in the Curator client, such as InterProcessMutex. Reentrant lock.

Here is a simple use example of InterProcessMutex reentrant lock, the code is as follows:

    @Test
    public void testzkMutex() throws InterruptedException {

        CuratorFramework client = ZKclient.instance.getClient();
        final InterProcessMutex zkMutex =
                new InterProcessMutex(client, "/mutex");
        ;
        for (int i = 0; i < 10; i++) {
            FutureTaskScheduler.add(() -> {

                try {
                    //acquire mutex
                    zkMutex.acquire();

                    for (int j = 0; j < 10; j++) {
//public resource variable accumulation
                        count++;
                    }
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    log.info("count = " + count);
                    //release mutex
                    zkMutex.release();

                } catch (Exception e) {
                    e.printStackTrace();
                }

            });
        }

        Thread.sleep(Integer.MAX_VALUE);
    }
copy

Advantages and Disadvantages of ZooKeeper Distributed Locks

To summarize ZooKeeper distributed locks:

(1) Advantages: ZooKeeper distributed locks (such as InterProcessMutex) can effectively solve distributed problems, non-reentrant problems, and are relatively simple to use.

(2) Disadvantages: The distributed locks implemented by ZooKeeper have low performance. why? Because every time in the process of creating and releasing locks, instantaneous nodes must be dynamically created and destroyed to realize the lock function. As we all know, the creation and deletion of nodes in ZK can only be performed through the Leader server, and then the Leader server also needs to send data to all Follower machines. Such frequent network communication has a very prominent shortcoming of performance.

In short, in high-performance and high-concurrency scenarios, it is not recommended to use ZooKeeper's distributed locks. Due to the high availability of ZooKeeper, it is recommended to use ZooKeeper's distributed locks in scenarios where the concurrency is not too high.

In the current distributed lock implementation scheme, there are two more mature and mainstream schemes:

(1) Redis-based distributed lock

(2) ZooKeeper-based distributed lock

The two types of locks are applicable to the following scenarios:

(1) ZooKeeper-based distributed locks are suitable for scenarios with high reliability (high availability) and not too much concurrency;

(2) Redis-based distributed locks are suitable for scenarios with large concurrency and high performance requirements, but reliability problems can be compensated by other solutions.

In short, there is no question of who is good or who is bad, but who is more suitable.

Finally, a summary of the content of this chapter: ZooKeeper is an important coordination tool in distributed systems. This chapter introduces the principles of distributed naming services, distributed locks, and a reference implementation based on ZooKeeper. The actual combat cases in this chapter are recommended for you to master by yourself. Whether it is the actual start of the application or the interview in a large company, it is very useful. In addition, the mainstream distributed coordination middleware is not only Zookeeper, but also the very famous Etcd middleware. However, from the learning level, the functional design and core principles between the two are similar. After mastering Zookeeper, Etcd is easy to use.

The core content of the article and the source of the source code

Book: "Netty Zookeeper Redis High Concurrency Practice" Book Introduction – Crazy Creation…

Reference documentation:

Book: "Netty Zookeeper Redis High Concurrency Practice" Book Introduction – Crazy Creation…

Distributed Tool Zookeeper(2): Distributed Locks - Programmer Sought

Simple Practice of ZooKeeper Distributed Lock | Grandpa's Blog Park

zookeeper implements distributed locks - Programmer Sought

Implementation of Distributed Lock Based on Zookeeper – SegmentFault Thinking

Distributed lock using Redis or Zookeeper - Programmer Sought

The Implementation Principle of ZooKeeper Distributed Lock

Copyright statement: The content of this article is contributed by Internet users, and the opinions of this article only represent the author himself. This site only provides information storage space services, does not own ownership, and does not assume relevant legal responsibilities. If you find any content suspected of infringing/violating laws and regulations on this site, please send an email to report. Once verified, this site will be deleted immediately.

Publisher: Full-stack programmer, please indicate the source: https://javaforall.cn/197657.html Original link: https://javaforall.cn

Tags: node.js Distribution Zookeeper

Posted by sirup_segar on Mon, 03 Oct 2022 07:11:08 +0530