The correct implementation of Redis distributed locks

Source: Internet
Author: User
Tags redis server


There are three ways to implement distributed locks: 1. Database optimistic lock; 2. Redis-based distributed locks; 3. Zookeeper-based distributed locks. This blog will introduce the second way to implement distributed locks based on Redis. Although there are a variety of blogs on the web that introduce the implementation of Redis distributed locks, their implementations have a variety of problems, and in order to avoid fraught, this blog will detail how to implement Redis distributed locks correctly. Reliability


First, to ensure that a distributed lock is available, we must at least ensure that the lock implementation meets the following four conditions:


    1. Mutex. At any given moment, only one client can hold the lock.
    2. Deadlocks do not occur. Even if a client crashes while holding a lock and does not actively unlock it, it can ensure that subsequent other clients can lock.
    3. is fault tolerant. As long as most REDIS nodes are functioning properly, the client can lock and unlock.
    4. The bell must also be fastened to the person. Lock and unlock must be the same client, the client itself can not be added to the lock to the solution.
Code implementation Component Dependencies


First we will introduceJedisthe open source component through Mavenpom.xmland add the following code to the 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, and then take it slowly to explain why this is achieved:


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";

     /**
      * Try to get a distributed lock
      * @param jedis Redis client
      * @param lockKey lock
      * @param requestId request ID
      * @param expireTime overtime
      * @return whether to succeed
      */
     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 a line of code:jedis.set(String key, String value, String nxxx, String expx, int time)This set () method has a total of five parameters:


    • The first one is key, we use key to lock, because key is unique.

    • The second is value, we pass the RequestID, many children's shoes may not understand, there is a key as a lock is enough, why use value? The reason is that when we talk about reliability, distributed locks to meet the fourth condition of the bell must also ring people, by assigning value to RequestID, we know that this lock is the request added, when the unlock can have a basis. RequestID can beUUID.randomUUID().toString()generated using methods.

    • The third is nxxx, which we filled out with NX, meaning set if not EXIST, that is, when key does not exist, we do the set operation, if the key already exists, then do nothing;

    • The fourth is EXPX, this parameter we pass is px, meaning we want to give this key an expiration setting, the time is determined by the fifth parameter.

    • The fifth is time, echoing the fourth parameter, which represents the expiration of key.


In general, executing the Set () method above will result in only two results: 1. There is currently no lock (key does not exist), then the lock operation is performed, and the lock is set to an expiration date, and value represents the locked client. 2. There is a lock existing, do not do any operation.



Cautious's children's shoes will be discovered, and our lock code satisfies the three conditions described in our reliability. First, set () adds the NX parameter, which guarantees that if a key exists, the function does not invoke success, that is, only one client can hold the lock and satisfy the mutex. Second, because we set an expiration time on the lock, even if the lock holder fails to unlock after the subsequent crash, the lock is automatically unlocked because it expires (that is, key is deleted) and no deadlock occurs. Finally, because we assign value to RequestID, which represents the client request identity for locking, the client can verify whether it is the same client when it is unlocked. Because we only consider the scenario of Redis standalone deployment, we do not consider fault tolerance.


Error Example 1


A more common example of errors is the usejedis.setnx()andjedis.expire()combination of the implementation lock, the code is 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 here, the expiration time cannot be set and a deadlock will occur.
         Jedis.expire(lockKey, expireTime);
     }

}


The action 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 seems as if the previous set () method results, however, since this is a two Redis command and does not have atomicity, if the program suddenly crashes after executing setnx (), the lock does not set an expiration time. Then a deadlock will occur. The reason people do this online is because the low version of Jedis does not support the set () method of multiple parameters.


Error Example 2
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 the lock successfully.
     If (jedis.setnx(lockKey, expiresStr) == 1) {
         Return true;
     }

     // If the latch is in, get the lock expiration time
     String currentValueStr = jedis.get(lockKey);
     If (currentValueStr != null && Long.parseLong(currentValueStr) < System.currentTimeMillis()) {
         // The lock has expired, get the expiration time of the previous lock, and set the expiration time of the current lock.
         String oldValueStr = jedis.getSet(lockKey, expiresStr);
         If (oldValueStr != null && oldValueStr.equals(currentValueStr)) {
             / / Consider the case of multi-threaded concurrency, only one thread has the same set value as the current value, it has the right to lock
             Return true;
         }
     }
        
     // In other cases, always return lock failure
     Return false;

}


This kind of error example is more difficult to find, and the implementation is more complex. Implementation of the idea: the use ofjedis.setnx()command implementation lock, where key is a lock, value is the lock expiration time. Execution process: 1. The lock is attempted by the Setnx () method, and if the current lock does not exist, the lock is returned successfully. 2. If the lock already exists, gets the expiration time of the lock, compared with the current time, if the lock has expired, set a new expiration time, return lock success. The code is as follows:



So where is this code problem? 1. Because the client itself generates an expiration time, it needs to be forced to require that the time for each client in the distribution must be synchronized. 2. When the lock expires, if more than one client executes the method at the same time, thejedis.getSet()expiration time of the lock on the client may be overwritten by other clients, although only one client can eventually be locked. 3. The lock does not have the owner's identity, which means that any client can unlock it.


Unlock code correct Posture


Let's show the code first, and then explain why this is done:


Public class RedisTool {

     Private static final Long RELEASE_SUCCESS = 1L;

     /**
      * Release distributed locks
      * @param jedis Redis client
      * @param lockKey lock
      * @param requestId request ID
      * @return whether the release is successful
      */
     Public static boolean releaseDistributedLock(Jedis jedis, String lockKey, String requestId) {

         String script = "if redis.call(‘get‘, KEYS[1]) == ARGV[1] then return redis.call(‘del‘, KEYS[1]) else return 0 end”;
         Object result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

         If (RELEASE_SUCCESS.equals(result)) {
             Return true;
         }
         Return false;

     }

}


As you can see, we only need two lines of code to unlock it! The first line of code, we wrote a simple Lua script code, the last time to see this programming language or in the "Hacker and painter", did not expect this time to use. The second line of code, we pass the LUA code to thejedis.eval()method, and the parameter keys[1] assignment to lockkey,argv[1] is assigned to RequestID. The eval () method is to give the LUA code to the Redis server for execution.



So what is the function of this LUA code? In fact, it is very simple to first get the value of the lock, check if it is equal to RequestID, and delete the lock (unlock) if it is equal. So why use the Lua language to implement it? Because you want to make sure that the above operation is atomic. For questions about non-atomicity, you can read "Unlocking code-error Example 2". So why does the eval () method ensure atomicity, derived from the features of Redis, and the following is part of the website's interpretation of the eval command:






Simply put, the LUA code is executed as a command when the eval command executes the LUA code, and Redis executes other commands until the eval command finishes.


Error Example 1


The most common unlocking code is to use thejedis.del()method to delete the lock directly, this way, without first judging the owner of the lock and then unlocking it, any client can be unlocked at any time, even if the lock is not it.


public static void wrongReleaseLock1(Jedis jedis, String lockKey) {
    jedis.del(lockKey);
}




Error Example 2


This unlock code at first glance is also no problem, even I have almost achieved this, and the correct posture is similar, the only difference is divided into two commands to execute, the code is as follows:


Public static void wrongReleaseLock2(Jedis jedis, String lockKey, String requestId) {
        
     / / Determine whether the lock and unlock are the same client
     If (requestId.equals(jedis.get(lockKey))) {
         // If at this time, the lock is suddenly not the client, it will be unlocked by mistake.
         Jedis.del(lockKey);
     }

} 


As a code comment, the problem is that if the method is called, the lockjedis.del()will be unlocked when it is not part of the current client. So is there really such a scenario? The answer is yes, for example, client a plus lock, after a period of time after client a unlocked,jedis.del()before execution, the lock suddenly expired, at this time, client B attempts to lock successfully, then client A and then execute the Del () method, then the lock of Client B is released.


Summarize


This article mainly introduces how to use Java code to realize the Redis distributed lock correctly, and gives two more classic error examples for lock and unlock. In fact, it is not difficult to achieve a distributed lock through Redis, as long as the four conditions in the reliability are guaranteed to be met. Although the internet has brought us convenience, as long as there are problems can Google, but the online answer must be right? In fact, so we should always keep the spirit of questioning, many want more verification.



If Redis is a multi-machine deployment in your project, you can try toRedissonimplement distributed locks, which are the official Redis Java components, which are provided in the Reference reading section.



The correct implementation of Redis distributed locks


Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.