This article mainly introduces the relevant knowledge of how to implement distributed locks using Redis. The content is detailed and easy to understand, the operation is simple and fast, and has certain reference value. What we have gained, let's take a look at it together.
When we write multi-threaded code, different threads may compete for resources, in order to avoid For errors caused by resource contention, we will lock the resource, and only the thread that acquires the lock can continue to execute.
A lock in a process is essentially a variable in memory. When a thread executes an operation to apply for a lock, if it can successfully set the value of the variable representing the lock to 1, it means that the lock has been obtained. Other threads will block when they want to acquire the lock, and the thread that owns the lock will set the value of the lock to 0 after the thread that owns the lock completes the operation, which means that the lock is released.
What we are talking about above is the lock between different threads in the process of a server. This lock is placed in memory, and for distributed applications , different applications (processes or threads) are deployed on different servers, so that locks cannot be represented by variables in memory.
Since locks can be expressed on a server through the shared space of memory, then for distributed applications, the storage system can be shared to store a shared lock, which is a distributed lock , and Redis
, as a memory database, executes very fast, and is very suitable as a shared storage system for implementing distributed locks.
For a lock, there are actually only two operations, lock and release lock, let’s see below Let's see how to achieve it through Redis
?
Redis
The setnx
command will determine whether the key exists, if If it exists, do nothing and return 0, if it does not exist, create and assign a value, and return 1, so we can execute setnx
to set a value for a representative lock key, if it can be set successfully, then Indicates that the lock is obtained, and the lock cannot be obtained if it fails.
# Use key as lock to represent a lock setnx lock 1
After performing the operation, when you want to release the lock, directly set the Redis
Just delete the key value lock
, so that other processes can pass the
# Release the lock del lock
Through the above two commands, we have implemented a simple distributed lock, but there is a problem here: if a process is locked by the setnx
command, When a specific operation goes wrong, there is no way to release the lock in time, then other processes cannot obtain the lock, and the system cannot continue to execute. The solution to this problem is to set a validity period for the lock. After the validity period, the lock is automatically released .
It is very simple to set the validity period for the lock, directly use expire
of Redis
code> command is fine, such as:
# lock setnx lock 1 # Set a 10s validity period for the lock expire lock 10
However, there is another problem now. If we set the lock and execute the expire
command before the process hangs up, then expire
is not executed successfully, and the lock is not released, so we must ensure that the above two commands are executed together, how to guarantee it?
There are two methods, one is to use the script written in LUA
language, and the other is to use the set
command of Redis
, after the set
command is followed by the nx
parameter, the execution effect is consistent with setnx
, and the set
command can be followed by < code>ex parameter to set the expiration time, so we can use the set
command to combine setnx
and expire
together, In this way, the atomicity of execution can be guaranteed.
# Determine whether the key value exists, ex is followed by the validity period of the key value, 10s set lock 1 nx ex 10
Solved the effective problem of the lock, now let's look at another problem.
As shown in the picture above, there are now three different A
, B
, C
A process on the server needs to acquire a lock when performing an operation, and release the lock after execution.
The current situation is process A
got stuck when executing step 2 (shown in the green area above), and the time has exceeded the lock validity period, so the lock set by process A
is automatically released. At this time, process B< /code> obtained the lock and started to execute the operation, but because
process A
is just stuck, so when it will continue to execute, the lock will be released manually in step 3, but at this time , the lock is owned by thread B
, that is to say, what process A deletes is not its own lock, but the lock of process B, at this time process BNot finished yet, but after the lock is released,
Process C
can lock it, That is to say, because process A freezes and releases the wrong lock, process B and process C can acquire it at the same time lock.
How to avoid this situation? How to distinguish the locks of other processes and avoid deleting the locks of other processes? The answer is that when each process locks, it sets a unique value for the lock, and when releasing the lock, it judges whether it is a lock set by itself.
When setting a unique value for the lock, the same is to use the set
command, the only The difference is to change the key value 1 to a randomly generated unique value, such as uuid.
# rand_uid means unique id set lock rand_id nx ex 10
When the value in the lock is set by the process and the lock is released, you need to judge whether the lock is your own. The steps are as follows:
Get the value of the lock through the get
command of Redis
According to the obtained value, determine whether the lock is your own If it is set
, release the lock through the del
command.
At this point, we can see that three operations need to be performed to release the lock. If the three operations are performed in sequence, there is no way to guarantee atomicity, such as the process A
is about to execute the del
command after executing step 2, but the lock expires and is automatically released, and is used by process B on other servers
Get the lock, but at this time thread A
executes del
or deletes the lock of thread B
.
The solution to this problem is to ensure the execution of the above three operationsAtomicity, that is, during the three operations of releasing the lock, other processes cannot acquire the lock. To do this, you need to use LUA scripts.
Redis
supports LUA
scripts, When the code in LUA
is executed, the requests of other clients will not be executed, which can ensure atomic operation, so we can use the following script to release the lock:
if redis. call("get",KEYS[1]) == ARGV[1] then return redis. call("del",KEYS[1]) else return 0 end
After saving the above script as a script, you can call the Redis
client command redis-cli
to execute, as follows:
# lock is the key, and rand_id represents the value saved in the key redis-cli --eval unlock.lua lock , rand_id
Copyright Description:No reproduction without permission。
Knowledge sharing community for developers。
Let more developers benefit from it。
Help developers share knowledge through the Internet。
Follow us