This is the 23rd day of my participation in the August More Text Challenge.More challenges in August

In single machine environment, we use synchronization method, synchronization block and re-lock mechanism to solve the data security problem in multi-threaded environment. So based on distributed environment, how do we generate distributed locks through Zookeeper?

What is distributed locking

Before learning distributed lock implemented by Zookeeper, let’s first understand what is distributed lock, distributed lock implementation technology, and common types of distributed lock.

In daily development, the most familiar and common distributed lock scenario is when developing multithreading. In order to coordinate the access of multiple threads to a resource in the local application, the resource or numeric variable should be locked to ensure that the system can run correctly in a multi-threaded environment. In the program of a server, threads can communicate with each other through the system and realize locking and other operations. However, in distributed environment, the threads executing transactions exist in different network servers. In order to realize the cooperative operation of threads in distributed network, distributed lock is needed.

Distributed lock implementation has the following conditions: 1. In the distributed environment, multiple threads must have sequential access to resources. 2. High availability and high performance are required in the process of acquiring and releasing locks. 3, with lock failure mechanism and avoid deadlock. 4, non-blocking lock, no lock is directly returned to obtain the lock failure. There are many techniques for implementing distributed locks, such as Memcached, Redis, Google’s Chubby, and Zookeeper.

Shared locking: It performs better than exclusive locking because in shared locking implementations, only writes of data objects are locked, not reads of objects. In this way, not only the integrity of the data object is guaranteed, but also the read operation in the case of multiple transactions is taken into account. You can say that a shared lock is write exclusive, while a read operation is unlimited. For example, in real life, your house has a front door, and there are several keys to the front door, you have one key, your girlfriend has one key, and you can both use this key to get into your house, and this is called a shared lock.

Exclusive lock: Also known as write lock or exclusive lock. If transaction T1 holds an exclusive lock on data object O1, then only transaction T1 is allowed to read and update O1 for the entire lock period. No other transaction is allowed to perform any operations on O1 until T1 releases the exclusive lock.

The difference between an exclusive lock and a shared lock: with an exclusive lock, data objects are visible to only one transaction, while with a shared lock, data is visible to all transactions.

Zookeeper implements distributed lock design

On the Zookeeper server, we create a node called /Locks. For each Java client that requests Locks, we create a temporary ordered node under the /Locks node. Its name is created based on the /Locks node, and its node path is /Locks/Lock_. Since we are currently creating a temporary ordered node for the Java client, once the node is created, its full path /Locks/Lock_ and the node number of the current node, such as /Locks/Lock_0000000001.

The Java client fetches all the children under the current /Locks node, sorts them against all the children, and then determines if the node generated by the current client is the first in the list. If it is first in the sorting result, the current client has acquired the lock. After obtaining the lock, the corresponding code can be executed, and the lock can be released after executing the code.

If the temporary ordered node generated by the current Java client is not the first in the sorting result, there should be at least one or more corresponding nodes queuing for the lock resource. If this is the case, we can make the current client listen on the previous node of the node we created, and the previous node will listen on the previous node of the previous node. What is the purpose of listening on the previous node? For example, if the node we create is Lock_0000000002, its previous node is Lock_0000000001, we can use the Watch mechanism to make Lock_0000000002 to capture the Lock_0000000001 node deletion event.

When the Lock_0000000001 node is removed, all Java clients are queuing up to apply the lock. If the current node listens for the event that the previous node was deleted, the previous node has acquired the lock and executes the corresponding code. When the previous node is removed during the lock release process, the previous node has been unlocked, and my current Java client can try to acquire the lock again.

Create a lock

We create the lock by creating a data node on the ZooKeeper server with a Java client.

Private void createLock() throws Exception {// Check whether Locks exist. Stat = zookeeper. exists(LOCK_ROOT_PATH, false); if (stat == null) { zooKeeper.create(LOCK_ROOT_PATH,new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE,CreateMode.PERSISTENT); } // Create a temporary ordered node lockPath = zookeeper. create(LOCK_ROOT_PATH + "/" + LOCK_NODE_NAME, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); System.out.println(" node created successfully: "+ lockPath); }Copy the code

Acquiring a lock

When a transaction accesses shared data, it first needs to acquire the lock.

Private void templock () throws Exception {// Obtain all bytes at a Locks date List<String> List = zooKeeper.getChildren(LOCK_ROOT_PATH, false); Collections.sort(list); int index = list.indexOf(lockPath.substring(LOCK_ROOT_PATH.length() + 1)); If (index == 0) {system.out.println (" lock succeeded "); return; } else {// Last node path String path = list.get(index-1); Stat stat = zooKeeper.exists(LOCK_ROOT_PATH + "/" + path, watcher); if (stat == null) { attempLock(); } else { synchronized (watcher) { watcher.wait(); } attempLock(); }}}Copy the code

Release the lock

After the transaction logic completes, the shared lock held by the transaction thread needs to be released. We can use the properties of data nodes in ZooKeeper to achieve active lock release and passive lock release.

To proactively release the lock, the client invokes the DELETE function to delete data nodes on the ZooKeeper service after the logic execution is complete. In passive lock release mode, the ZooKeeper server directly deletes the temporary node to release the shared lock when the client exits due to an exception.

Private void releaseLock() throws Exception {zookeeper.delete (this.lockpath,-1); // Release the lock. zooKeeper.close(); System.out.println(" lock released "+ this.lockpath); }Copy the code

test

Public class TicketSeller {private void Sell (){system.out.println (" "); public class TicketSeller {private void Sell (){system.out.println (" "); Int sleepMillis = 5000; Thread.sleep(sleepMillis); } catch (InterruptedException e) { e.printStackTrace(); } system.out. println(" secend "); } public void sellTicketWithLock() throws Exception { MyLocks lock = new MyLocks(); AcquireLock (); sell(); // releaseLock lock.releaseLock(); } public static void main(String[] args) throws Exception { TicketSeller ticketSeller = new TicketSeller(); for(int i=0; i<10; i++){ ticketSeller.sellTicketWithLock(); }}}Copy the code