Java language describes the correct implementation of redis distributed lock
Distributed locks are generally implemented in three ways: 1 Database lock; 2. Redis based distributed locks; 3. Distributed lock based on zookeeper. This blog will introduce the second way to implement distributed locks based on redis. Although there are various blogs on the Internet that introduce the implementation of redis distributed lock, their implementation has various problems. In order to avoid harming people's children, this blog will introduce in detail how to correctly implement redis distributed lock.
reliability
First, in order to ensure the availability of distributed locks, we should at least ensure that the implementation of locks meets the following four conditions at the same time:
Mutex. At any time, only one client can hold the lock.
No deadlock occurs. Even if one client crashes while holding the lock without actively unlocking, it can ensure that other subsequent clients can lock.
Fault tolerance. As long as most redis nodes operate normally, the client can lock and unlock.
Whoever unties the bell must tie it. Locking and unlocking must be the same client. The client cannot unlock the lock added by others.
code implementation
Component dependency
First of all, we will introduce jedis open source components through maven, which can be found in POM. Com Add the following code to the XML file:
Lock code
Correct posture
Talk is cheap,show me the code。 Show the code first, and then explain why:
As you can see, we lock in one line of code: jedis Set (stringkey, stringvalue, stringnxxx, stringexpx, inttime). This set () method has five formal parameters:
The first one is the key. We use the key as the lock because the key is unique.
The second is value. We pass requestid. Many children's shoes may not understand it. Isn't it enough to have a key as a lock? Why use value? The reason is that when we talk about reliability above, the distributed lock must meet the fourth condition. The person who tied the bell must unlock the lock. By assigning value to requestid, we can know which request added the lock and have a basis when unlocking. Requestid can use UUID randomUUID(). Generated by tostring() method.
The third parameter is nxxx. We fill in NX, which means setifnotexist, that is, when the key does not exist, we perform set operation; If the key already exists, no operation will be performed;
The fourth parameter is expx. We pass Px, which means that we need to add an expiration setting to the key. The specific time is determined by the fifth parameter.
The fifth is time, which corresponds to the fourth parameter and represents the expiration time of the key.
In general, executing the set () method above will only lead to two results: 1 If there is no lock at present (the key does not exist), perform the lock operation and set a valid period for the lock. Meanwhile, value indicates the locked client. 2. The existing lock exists and no operation is performed.
Careful children's shoes will find that our locking code meets the three conditions described in our reliability. First, the NX parameter is added to set() to ensure that if there is an existing key, the function will not be called successfully, that is, only one client can hold the lock and meet mutual exclusion. Secondly, because we set the expiration time for the lock, even if the lock holder crashes and does not unlock it later, The lock will also be unlocked automatically when it expires (that is, the key is deleted) and no deadlock will occur. Finally, because we assign value to requestid, which represents the request ID of the locked client, we can verify whether the client is the same client when the client is unlocked. Since we only consider the scenario of redis single machine deployment, we will not consider the fault tolerance temporarily.
Error example 1
A common error example is using jedis Setnx() and jedis The combination of expire() implements locking, and the code is as follows:
Setnx () is setifnotexist, and expire () is to add an expiration time to the lock. At first glance, it seems to be the same as the result of the previous set () method. However, because these are two redis commands and are not atomic, if the program crashes suddenly after executing setnx (), the lock does not set the expiration time. Then a deadlock will occur. The reason why people implement this on the Internet is that the lower version of jedis does not support multi parameter set () method.
Error example 2
This kind of error example is more difficult to find problems, and the implementation is also more complex. Implementation idea: use jedis The setnx () command implements locking, where key is the lock and value is the expiration time of the lock. Execution process: 1 Try locking through the setnx () method. If the current lock does not exist, it returns that locking succeeded. 2. If the lock already exists, obtain the expiration time of the lock and compare it with the current time. If the lock has expired, set a new expiration time and return the success of locking. The code is as follows:
So what's the problem with this code? 1. Since the expiration time is generated by the client itself, it is mandatory that the time of each client must be synchronized under distributed. 2. When the lock expires, if multiple clients execute jedis at the same time GetSet () method, although only one client can lock in the end, the expiration time of this client's lock may be overwritten by other clients. 3. The lock does not have owner ID, that is, any client can be unlocked.
unlock code
Correct posture
Let's show the code first, and then slowly explain why it is implemented in this way:
As you can see, we only need two lines of code to unlock! In the first line of code, we wrote a simple Lua script code. The last time we saw this programming language was in hacker and painter. Unexpectedly, it was used this time. The second line of code, we pass Lua code to jedis Eval() method, and assign the parameter keys [1] to lockkey and argv [1] to requestid. The eval () method is to hand over the Lua code to the redis server for execution.
So what is the function of this Lua code? In fact, it is very simple. First, get the value corresponding to the lock and check whether it is equal to requestid, If it is equal, the lock will be deleted (unlocked). So why use Lua language to implement it? Because it is necessary to ensure that the above operations are atomic. For the problems caused by non atomicity, please read [unlock code - error example 2]. Then why executing eval() method can ensure atomicity is due to the characteristics of redis. The following is a partial explanation of Eval command on the official website:
Simply put, when the eval command executes Lua code, Lua code will be executed as a command, and redis will not execute other commands until the eval command is executed.
Error example 1
The most common unlocking code is to directly use jedis Del () method deletes the lock. This method of directly unlocking the lock without first judging the owner of the lock will cause any client to unlock it at any time, even if the lock is not its own.
Error example 2
At first glance, this unlocking code is OK. Even I almost realized it before. It is similar to the correct posture. The only difference is that it is divided into two commands to execute. The code is as follows:
As the code notes, the problem is that if jedis. Is called When the del () method is used, the lock added by others will be released when the lock does not belong to the current client. So is there really such a scene? The answer is yes. For example, client a locks. After a period of time, client a unlocks and executes jedis Before del (), the lock suddenly expires. At this time, client B attempts to lock successfully, and then client a executes the del () method to unlock client B.
summary
This paper mainly introduces how to correctly implement redis distributed lock using java code, and gives two classic error examples for locking and unlocking. In fact, it is not difficult to implement distributed locks through redis, as long as it can meet the four conditions in reliability.
What scenarios are distributed locks mainly used in? Where synchronization is required, for example, to insert a piece of data, you need to check whether the database has similar data in advance. When multiple requests are inserted at the same time, it may be judged that there is no similar data returned by the database, you can join. Synchronization is required at this time, but direct database locking takes too much time, so redis distributed locking is adopted. At the same time, only one thread can insert data, and other threads wait.
The above is all about the correct implementation of redis distributed lock described in Java language in this paper. I hope it will be helpful to you. Interested friends can continue to refer to other related topics on this site. If there are deficiencies, please leave a message to point out. Thank you for your support!