Redis implements related distributed locks

Why is a distributed lock needed
? We know that when multiple threads operate on an object concurrently, synchronized can be used to ensure that only one thread acquires the object lock at the same time and then processes the code block or method modified by the synchronized keyword. Since there are already synchronized locks, why should distributed locks be introduced here?

Because the current system is basically deployed in a distributed manner, and an application will be deployed on multiple servers, synchronized can only control the thread safety of the current server itself, and cannot control concurrency safety across servers. For example, in the figure below, there are 4 threads adding the same product at the same time, two threads are processed by server A, and the other two threads are processed by server B, then the final result is that each of the two servers executes a new action. This is obviously not as expected.
Insert image description here

The distributed lock introduced in this article is designed to solve this problem.

What is a distributed lock
? A distributed lock is an implementation of a lock that controls different processes in a distributed system to jointly access the same shared resource.

The so-called authorities are obsessed, and bystanders are clear. Let me give you an example from life. Take the high-speed rail as an example. Each high-speed rail has its own operating routes, but these routes may overlap with other high-speed rail routes. If only the drivers inside the high-speed rail are allowed to control the routes, Then a crash may occur because the driver does not know what other high-speed train routes are. Therefore, the central control room comes into play. The central control room will monitor each high-speed train. What time and route the high-speed train takes are all directed by the central control room.

Distributed lock is implemented based on this idea. It needs to use a third-party component (such as database, Redis, Zookeeper, etc.) outside our distributed application to monitor the global lock. This component decides when to lock. When to release the lock.
Insert image description here

How Redis implements distributed locks.
Before talking about how Redis implements distributed locks, we need to first talk about a command of redis: setnx key value. We know that the most commonly used command for Redis to set a key is: set key value, which will set the value of the key to value regardless of whether the key exists or not, and return success:
Insert image description here

setnx key value also sets the value of the key to value, but it will first determine whether the key already exists, if the key does not exist, then set the value of the key to value, and return 1; if the key already exists, the key will not be updated Value, return 0 directly:
** bold style
**

● The simplest version: setnx key value

Based on the characteristics of the setnx command, we can implement the simplest distributed lock. We send the setnx command to Redis, and then determine whether the result returned by Redis is 1. If the result is 1, it means setnx is successful, then the lock is obtained this time, and the business logic can continue to be executed; if the result is 0, it means setnx failed. , then the lock was not acquired this time. You can keep trying to acquire the lock in a loop until other clients release the lock (delete the key), and then you can execute the setnx command normally to acquire the lock. The process is as follows:
Insert image description here

Although this method realizes the function of distributed lock, there is an obvious problem: there is no expiration time set for the key. If the program crashes before sending the delete command to release the lock, then the key will be permanently stored in Redis is hit, and other clients will never be able to obtain this lock.

● Upgrade version: set the expiration time of the key

In response to the above problem, we can set an expiration time for the key based on setnx key value. Redis already provides such a command: set key value ex seconds nx. Among them, ex seconds means setting the expiration time for the key in seconds, and nx means that the set command has the characteristics of setnx. The effect is as follows:
Insert image description here

We set the expiration time of name to 60 seconds. When the set command is executed within 60 seconds, nil will be returned directly. After 60 seconds, we execute the set command again and it can be executed successfully. The effect is as follows:
Insert image description here

Based on this feature, the upgraded distributed lock process is as follows:
Insert image description here

Although this method solves some problems, it also leads to another problem: locks are accidentally deleted, that is, locks added by others are released. For example, client1 starts to perform business processing after acquiring the lock, but the business processing takes a long time and exceeds the expiration time of the lock. As a result, before the business processing is completed, the lock expires and is automatically deleted (equivalent to the lock belonging to client1 being released). ), at this time, client2 will obtain the lock, and then perform its own business processing. At this time, client1's business processing ends, and then sends the delete key command to Redis to release the lock. Redis receives After issuing the command, the key is deleted directly, but at this time the key belongs to client2, so it is equivalent to client1 releasing the lock of client2:
Insert image description here

● Second upgrade version: use a unique value for value, and determine whether the value belongs to the current thread when deleting the lock.

To solve the above problem, the easiest way is to set the lock expiration time longer, much longer than the business processing time, but this will seriously affect the performance of the system. If a server goes down before releasing the lock, and The lock timeout is set to one hour, so within this hour, other threads will be blocked there when accessing this service. Therefore, this method is generally not recommended.

Another solution is to set the value to a unique value when setting key value ex seconds nx. The value of each thread is different. Before deleting the key, first get the value through the get key command, and then determine whether the value is Generated by its own thread, if it is, the key will be deleted and the lock will be released. If not, the key will not be deleted. The normal process is as follows:
Insert image description here

When the business processing is not over, the key automatically expires, and you can release your own lock normally without affecting other threads:

Insert image description here

The solution after the second upgrade seems to have no problems, but it is not. After carefully analyzing the process, we found that the two steps of determining whether the lock belongs to the current thread and releasing the lock are not atomic operations. Normally, if the value obtained by thread 1 from Redis through the get operation is 123, then the operation of deleting the lock will be executed, but if the system freezes for a few seconds before executing the action of deleting the lock, it happens to Within seconds, the key automatically expires, and thread 2 successfully acquires the lock and starts to execute its own logic. At this time, thread 1 freezes and resumes, and continues to execute the action of deleting the lock, so what is deleted at this time is still thread 2. Lock.
Insert image description here

● Ultimate version: Lua script

To address the problem that the above-mentioned Redis original commands cannot meet the atomic operations of some businesses, Redis provides support for Lua scripts. Lua script is a lightweight and compact scripting language that supports atomic operations. Redis will execute the entire Lua script as a whole without being inserted by other requests in the middle, so the execution of Lua script by Redis is an atomic operation.

In the above process, we write the three steps of getting key value, judging whether the value belongs to the current thread, and deleting the lock into the Lua script, so that they become a whole and hand them over to Redis for execution. The modified process is as follows:
Insert image description here

After this transformation, the problem that multiple steps such as obtaining the value, judging the value, and deleting the lock when releasing the lock cannot guarantee atomic operations has been solved. You can learn the syntax of Lua script by yourself. It is not complicated and very simple, so I won’t go into details here.

Since the Lua script can be used when releasing the lock, it must also be used when locking, and in general, it is recommended to use the Lua script, because when using the above set key value ex seconds nx command to lock, it cannot be repeated. The effect of entering the lock, that is, when a thread acquires the lock, the current thread itself cannot acquire the lock before releasing the lock, which will obviously affect the performance of the system. This problem can be solved by using Lua script. We can first judge whether the lock (key) exists in the Lua script. If it exists, then judge whether the thread holding the lock is the current thread. If not, the lock fails, otherwise the current The thread holds the lock again and increases the number of reentrants of the lock by 1. When releasing the lock, it is also first judged whether the thread holding the lock is the current thread. If so, the number of reentries of the lock is -1 until the number of reentries is reduced to 0, and the lock (key) can be deleted.
Insert image description here

In actual project development, in fact, it is basically not necessary to write the implementation logic of the above distributed locks by yourself, but to use some very mature third-party tools. The most popular one is Redisson, which not only provides the encapsulation of the basic commands of Redis, but also provides It encapsulates the Redis distributed lock and is very simple to use, just call the corresponding method directly. But although the tool is easy to use, the underlying principles still need to be understood. This is the purpose of this article.

Guess you like

Origin blog.csdn.net/qq_36644198/article/details/131673954