Redis distributed locks in the right way (Java version)
Distributed locks generally have three kinds of implementation methods: 1. Database optimistic lock; 2. Distributed lock based on Redis; 3. Distributed locks based on zookeeper. This blog will introduce the second way to implement distributed locks based on Redis. Although the Internet has a variety of redis distributed lock implementation of the blog, but their implementation has a variety of problems, in order to avoid fraught, this blog will detail how to implement the Redis distributed lock.
Reliability
First, to ensure that distributed locks are available, we must at least ensure that the implementation of the lock meets the following four conditions:
Mutual exclusion. At any given moment, only one client can hold a lock.
No deadlock occurs. Even if a client crashes while holding a lock and does not actively unlock it, it can also ensure that subsequent clients can lock up.
is fault tolerant. As long as most of the REDIS nodes are running properly, the client can lock and unlock.
The bell must be fastened to the bell-man. Lock and unlock must be the same client, the client itself can not add the lock to the solution.
Code implementation
Component dependencies
First we'll introduce the Jedis open source component through MAVEN, and add the following code to the Pom.xml file:
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.9.0</version>
</dependency>
Lock code
Correct posture
Talk is cheap, show me the code. Show the code first, then explain why this is done:
public class Redistool {
private static final String lock_success = "OK";
private static final String set_if_not_exist = "NX";
private static final String Set_with_expire_time = "PX";
/**
* Attempt to acquire distributed locks
* @param jedis Redis Client
* @param lockkey Lock
* @param RequestID Request identification
* @param Expiretime Extended time
* @return whether to obtain success
*/
public static Boolean Trygetdistributedlock (Jedis Jedis, String Lockkey, string requestid, int expiretime) {
String result = Jedis.set (Lockkey, RequestID, Set_if_not_exist, Set_with_expire_time, expiretime);
if (lock_success.equals (result)) {
return true;
}
return false;
}
}
As you can see, we lock on one line of code: Jedis.set (String key, String value, String nxxx, string expx, int time), this set () method has five parameters:
The first is key, we use key to be a lock, because key is unique.
The second for value, we preach is RequestID, many children's shoes may not understand, there is key as a lock is enough, why use the value. The reason is that when we talk about reliability, distributed lock to meet the fourth condition to solve the bell also need to fasten the person, by assigning value to the RequestID, we know this lock is which request added, in the unlock time can have the basis. RequestID can be generated using the Uuid.randomuuid (). ToString () method.
The third is nxxx, this parameter we fill in NX, meaning is set if not EXIST, that is, when the key does not exist, we do set operation; If key already exists, do not do any operation;
The fourth one is EXPX, this parameter we pass is px, meaning we want to give this key to add an expiration setting, the time is decided by the fifth parameter.
The fifth is time, echoing the fourth parameter, representing the expiration of the key.
In general, executing the Set () method above will only result in two kinds of results: 1. There is currently no lock (key does not exist), then the lock operation, and the lock set a validity period, while value represents the locked client. 2. There is a lock existing, do not do any operation.
Forestall's children's shoes will find that our lock code meets the three conditions described in our reliability. First, the set () adds the NX parameter to ensure that if there is already a key, the function does not invoke success, that is, only one client can hold the lock and satisfy the mutex. Second, because we set the expiration time on the lock, even if the lock holder's subsequent crash without unlocking, the lock will be automatically unlocked because of the expiration date (that is, key is removed) and no deadlock will occur. Finally, because we assign value to RequestID, which represents the lock client request identity, the client can verify whether it is the same client when unlocking. Because we only consider Redis single deployment scenarios, we do not consider fault tolerance.
Error Example 1
A more common example of an error is the use of jedis.setnx () and Jedis.expire () combinations to add locks, as follows:
public static void WrongGetLock1 (Jedis Jedis, String Lockkey, string requestid, int expiretime) {
Long result = Jedis.setnx (Lockkey, RequestID);
if (result = = 1) {
If the program suddenly crashes, you cannot set the expiration time, a deadlock will occur
Jedis.expire (Lockkey, expiretime);
}
}
The effect of the Setnx () method is that the set IF not Exist,expire () method is to add an expiration time to the lock. At first glance it looks like the result of the previous set () method, however, since this is a two Redis command and does not have atomicity, the lock does not set an expiration time if the program crashes suddenly after the setnx () is executed. Then there will be a deadlock. This is done on the web because a Jedis set () method is not supported by a lower version.
Error Example 2
This kind of error example is more difficult to detect and is more complex to implement. Implementation idea: Use the jedis.setnx () command to implement the lock, where key is a lock, value is the expiration time of the lock. Execution process: 1. Attempts to lock with the Setnx () method, if the current lock does not exist, the return lock succeeds. 2. If the lock already exists, get the expiration time of the lock and compare the current time, if the lock has expired, set a new expiration time, return lock success. The code is as follows:
public static Boolean WrongGetLock2 (Jedis Jedis, String lockkey, int expiretime) {
Long expires = System.currenttimemillis () + expiretime;
String EXPIRESSTR = string.valueof (expires);
If the current lock does not exist, return lock succeeded
if (Jedis.setnx (Lockkey, expiresstr) = = 1) {
return true;
}
Gets the expiration time of the lock if the lock exists
String currentvaluestr = Jedis.get (Lockkey);
if (currentvaluestr!= null && long.parselong (CURRENTVALUESTR) < System.currenttimemillis ()) {
The lock has expired, gets the expiration time of the previous lock, and sets the expiration time for the lock now
String oldvaluestr = Jedis.getset (Lockkey, EXPIRESSTR);
if (oldvaluestr!= null && oldvaluestr.equals (CURRENTVALUESTR)) {
Consider multithreading concurrency, only one thread has the same set value as the current value, it has the right to lock
return true;
}
}