catalogue
Warm tips: This article requires you to master the data structure of zk and the temporary serial number node!
zk implements distributed locks entirely by using temporary sequence number nodes among zk node types
Part of the knowledge in this article comes from the zoomeeper explained by Qianfeng education!
1, The concept of lock in Java
1.1. What is a lock
Locks are used to control how multiple threads access shared resources. Generally speaking, a lock can prevent multiple threads from accessing shared resources at the same time.
1.2. Lock usage scenarios
Take inventory reduction as an example. At this time, there is only one inventory left. Then we must ensure that only one request will actually complete the operation. If the code logic is, first check the inventory from the library, and judge through the if condition. If there is any, it will be reduced, and if there is no, it will return to purchase failure.
At this time, the inventory reduction interface is accessed concurrently. Perhaps at this time, the if judgment in our code has become invalid, multiple requests are found at the same time, and the if judgment has been entered. If the lock is not added, the situation will be more serious, and the inventory will suddenly become a negative number.
For this code, we don't want someone to reduce inventory at the same time. At this time, we need to add a lock to control it. After adding a lock, we change the concurrent requests to serial. That is, no matter how many concurrent requests are made, I can only allow one request to reduce inventory by locking. You can compete for lock resources. Whoever gets the lock first is the one who gets it.
1.3. What is a distributed lock
Distributed locking is a way to control the synchronous access of shared resources between micro service clusters.
1.4. Usage scenarios of distributed locks
Prerequisite for using distributed locking: microservices must be clusters. Microservices here do not refer to zk, but to our business modules. Generally, in projects, due to the high concurrency, businesses are often divided into modules, such as order modules and inventory modules. One of the purposes of module disassembly is to implement cluster deployment for the concurrency of modules. For example, order modules are used more, I can build multiple order modules, but although they have multiple modules, the data around them are common (one database).
In the case of a cluster, we can't solve the problem by adding a common lock to the code. If there are three inventory microservices and load balancing access is set up, the common lock can only control its own services, but it can't control the other two services. The data is common, so we can only use a distributed lock to control the three services. When there is only one commodity in the inventory, Only one service access request can reduce inventory successfully.
2, zk implementation of distributed lock
2.1. Types of locks in ZK:
- READ lock: create_temporary s / N nodes. The name of the node will contain the letter READ, indicating that the lock is READ and can be READ by all users. The prerequisite for reading lock is that previous lock has no write lock
- Write lock: create_temporary Sn nodes. The name of the node will contain the letter write, indicating that it is a write lock. Only those who have obtained a write lock can write. The premise of writing locks is that there are no locks before.
Read locks and write locks are distinguished completely by the name of the created temporary sequence number node!
- Sn node: the created node will be followed by a "value" according to the order of the node. The later the node is, the higher the "value" will be. This is similar to the automatic increment of the primary key in mysql
- Temporary node: the temporary node is deleted after the session ends. Through this feature, zk can achieve the effect of service registration and discovery. If the session is closed for about 10s, the created temporary node will disappear. This session refers to the client connecting to zk.
- Temporary Sn node: a combination of the above two nodes
When a lock is required, a temporary Sn node is created. When the lock is released, the node is deleted.
2.2.zk how to read locks
- Create_temporary Sn nodes
- Get all nodes with serial number in the current zk
- Determine whether the latest node is a read lock:
- If the lock is not read, the lock fails and listening is set for the most remote node. Block waiting. zk's watch mechanism will notify the current node when the latest node changes, and then execute the process in step
- If the lock is read, the lock is successful
If you want to read a lock, you need to check whether there is a write lock in a node smaller than it. If there is a write lock, you need to delete the node after it is used up, and notify him through the watch mechanism that the write lock has been released, and then he can make the second step of judgment.
2.3.zk how to write lock
- Create_temporary Sn nodes
- Get all_nodes in zk
- Determine whether the node is the most important node:
- If yes, the write lock is successful
- If it is not, it means that there is a lock in the front node, and the locking fails. Listen to the most important node. If the most important node changes, go back to step.
2.4. group effect
If the above locking mode is adopted, as long as a node changes, the listening events of other nodes will be triggered. In this way, the pressure on zk is constant, which is the "group effect". It can be adjusted to chain monitoring. Solve this problem.
If 100 concurrent requests need to obtain write locks, 100 nodes are created to listen to the smallest node. When the smallest node changes, it means that it will notify 100 nodes at once. zk will be under great pressure instantly.
So chain monitoring can be used at this time. Chain monitoring still depends on the characteristics of serial number nodes. For example, after mysql is set to auto increment, no matter how many concurrent requests, it can still ensure the uniqueness of the id, and the sequence number node of zk is the same. Let them no longer listen to the smallest node, but listen to its previous node. After the previous node releases the lock, the current node can create a write lock.
There will also be a problem here. If the previous node is accidentally deleted, but it is not waiting for the lock to be released after it is obtained, but simply does not want to wait. Therefore, the node is deleted, and many nodes on it are locked. Therefore, we cannot simply lock the current node after the previous node is deleted. When we add a write lock, we need to ensure that no node is locked on it. At this time, let him listen to the last node. If the previous node still has problems, listen to the previous node. In short, try to avoid multiple nodes listening to a node at the same time.
3, springboot integrates distributed locks
springboot integrates curator client: https://blog.csdn.net/weixin_43888891/article/details/125442668
I directly practiced distributed locking based on the project in the previous article!
According to the zk distributed lock implementation idea mentioned above, we don't need to write it ourselves. The curator client has provided us with ready-made methods. We can implement the distributed lock function by simply calling the methods provided by the client!
<pre class="prettyprint hljs java" style="padding: 0.5em; font-family: Menlo, Monaco, Consolas, "Courier New", monospace; color: rgb(68, 68, 68); border-radius: 4px; display: block; margin: 0px 0px 1.5em; font-size: 14px; line-height: 1.5em; word-break: break-all; overflow-wrap: break-word; white-space: pre; background-color: rgb(246, 246, 246); border: none; overflow-x: auto; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-thickness: initial; text-decoration-style: initial; text-decoration-color: initial;">@Autowired CuratorFramework curatorFramework; /** * The condition for obtaining a read lock is that there is no previous write lock * When the /lock1 node does not exist, we do not need to create it manually. It will be created automatically when the lock is obtained * A temporary node is created automatically. It will be deleted when the lock is released after it is used up * After obtaining the lock, a temporary sequence number node will be created under the /lock1 node * Then the thread that has not obtained the lock will also create a node, which is in the waiting period * When a lock is released, its serial number node will be deleted first. Then, if no one is queuing to use the lock, the /lock1 node will also be deleted * * @throws Exception */ @Test void testGetReadLock() throws Exception { // Read write lock InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/lock1"); // Acquire the read lock object (it takes only 18 milliseconds to create the object) InterProcessLock interProcessLock = interProcessReadWriteLock.readLock(); System.out.println("Waiting for lock read object!"); // Obtain the lock (if the lock is not obtained, the method will always be blocked. Even if it is not blocked, the method takes a long time, up to 17 seconds) interProcessLock.acquire(); // If our normal code reaches this step, it indicates that the lock has been obtained. Write the relevant business code here. Remember to release the lock after execution System.out.println("Lock obtained!"); for (int i = 1; i <= 100; i++) { Thread.sleep(3000); System.out.println(i); } // Release lock (method takes 13 MS) interProcessLock.release(); // At this point, the node has been deleted System.out.println("Waiting to release the lock!"); } /** * The condition for obtaining a write lock is that there is no previous lock * * @throws Exception */ @Test void testGetWriteLock() throws Exception { // Read write lock InterProcessReadWriteLock interProcessReadWriteLock = new InterProcessReadWriteLock(curatorFramework, "/lock1"); // Get write lock object InterProcessLock interProcessLock = interProcessReadWriteLock.writeLock(); System.out.println("Waiting to acquire write lock object!"); // Obtain the lock (this method will always be blocked if the lock is not obtained) interProcessLock.acquire(); for (int i = 1; i <= 100; i++) { Thread.sleep(3000); System.out.println(i); } // Release lock interProcessLock.release(); System.out.println("Waiting to release the lock!"); } /** * Easy to test multiple threads to obtain read lock * * @throws Exception */ @Test void testGetReadLock1() throws Exception { testGetReadLock(); }</pre>
This is the node scenario when two read locks are obtained:
This is a scenario of acquiring a read lock and a write lock. The read lock node is 0004, so it is created later. It can only acquire a read lock after 0003 releases the write lock.
Can zk distributed locks cause deadlocks?
This is definitely not true, because if a client gets the lock, has not released it, and the service hangs, then according to the characteristics of the temporary node, when the temporary node and the client disconnect for a few seconds, it will be deleted automatically. Deleting the node also means that the lock is automatically released!