Redis implementation of distributed lock and precautions

I. Introduction

Distributed Lock There are three general ways:

1. Database optimistic locking;

2. Redis-based distributed lock;

3. Based on ZooKeeper distributed lock.

This blog will introduce the second way, Redis achieve Distributed Lock based. Although the Internet has introduced a variety of distributed lock Redis implemented blog, but they realize they have a variety of problems, in order to avoid fraught, this blog will detail how to implement Redis distributed lock properly.

Second, reliability

First, in order to ensure that the distributed lock is available, we at least want to ensure the lock at the same time meet the following four conditions:

1, mutually exclusive. At any time, only one client can hold the lock.

2, the deadlock will not occur. Even with a client during a crash without holding the lock to unlock the initiative, but also to ensure follow-up to other clients can lock.

3, fault tolerant. As long as most of the Redis functioning node, the client can lock and unlock.

4, the trouble should end it. Locking and unlocking must be the same client, the client's own people can not be added to the solution of the lock.

Code

Third, the addition of component depends

First, we want to introduce Jedis open source components by Maven, add the following code in the pom.xml file:

<dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
    <version>2.9.0</version>
</dependency>

 

Fourth, the code lock

Correct posture

First impressions code, and then slowly bring you explain why this realization:

public  class RedisTool { 

Private  static  Final String LOCK_SUCCESS = "the OK" ; 

Private  static  Final String SET_IF_NOT_EXIST = "NX" ; 

Private  static  Final String SET_WITH_EXPIRE_TIME = "PX" ; 

  / ** 

  * attempts to acquire a Distributed Lock 

  * @param jedis the Redis client 

  * @param lockKey lock 

  * @param requestId request identification 

  * @param expireTime extended time 

  * @return whether for 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;

  }

}

 

 

You can see, we are locked on one line of code: jedis.set (String key, String value, String nxxx, String expx, int time), the set () method, a total of five parameter:

The first is the key, when we use the key to lock because the key is unique.

The second is the value, we preach is requestId, a lot of children's shoes may not understand, there is a key lock is not enough yet, why use value? The reason is that when we talked about the reliability of the above, distributed lock to meet the fourth condition started the trouble should end it, by giving value assigned to requestId, we know that this lock is which request added, and in the unlocked when we can have a basis. requestId can UUID.randomUUID (). toString () method to generate.

The third is nxxx, we fill this parameter is NX, meaning that SET IF NOT EXIST, that is, when the key is not present, we set operations; if the key already exists, do nothing;

The fourth is expx, we pass this parameter is PX, meaning that we give this key plus an expired set the specific time decided by the fifth parameter.

The fifth is the time, and the fourth argument echoes, representatives of key expiration time.

Overall, the implementation of the above set () method will only lead to two results: 1. There are currently no lock (key does not exist), then the process of locking operation and a lock setting period, while value is the lock clients. 2. have been latched, does nothing.

Cautious of children's shoes will be found, we lock code meets the three conditions described in our reliability. First, set () joined the NX parameters, you can guarantee that if there is an existing key, the function will not be called successful, that is, only one client can hold a lock to meet mutually exclusive. Secondly, because we set the expiration time of the lock, even if the lock holder subsequent collapse occurred without unlock the lock will be because of the expiration time and automatically unlock (ie key is deleted), the deadlock will not occur. Finally, because we value assigned to requestId, on behalf of the client request identification lock, then the client can be unlocked at the time of whether the check is the same client. Since we only consider Redis standalone deployment scenario, so we will not consider fault tolerance.

Examples of common errors 1

Comparative example is the use of common error jedis.setnx () and jedis.expire () a combination lock, code is as follows:

public  static  void wrongGetLock1 (Jedis jedis, lockKey String, String the requestId, int expireTime) { 

  Long Result = jedis.setnx (lockKey, the requestId); 

  IF (Result ==. 1 ) { 

    // If this sudden collapse in the program can not be set expiration time, a deadlock occurs 

    jedis.expire (lockKey, expireTime); 

  } 

}

 

setnx () method function is to SET IF NOT EXIST, expire () method is to add a lock expiration time. At first glance, it seems the previous set () method as a result, however, since this is two Redis commands, having no atomic, if the sudden collapse in the program after performing SETNX (), resulting in the lock is not set the expiration time. Then the deadlock will occur. The reason why the Internet was achieved, because of the low version of jedis does not support multi-parameter set () method.

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 key is not present, returns a successful lock 

    IF ( jedis.setnx (lockKey, expiresStr) ==. 1 ) {
    return  to true ; 
    } 

    // If lock, the lock acquired expiration time 

    String currentValueStr = jedis.get (lockKey); 

    IF ! (currentValueStr = null && Long.parseLong (currentValueStr ) < System.currentTimeMillis ()) { 

        //Lock expired, obtain a lock on the expiration time, and set the expiration time now lock 

        String oldValueStr = jedis.getSet (lockKey, expiresStr); 

      IF (! OldValueStr = null && oldValueStr.equals (currentValueStr)) { 

        // consider more than case of concurrent threads, only one thread of the setting value and the current value of the same, which have the right locking 

        return  to true ; 

      } 

  } 

  // other circumstances, all return lock failure 

return to false ; }

 

Examples of this kind of error is more difficult to find the problem, but also more complicated to achieve. Realization of ideas: Use jedis.setnx () command to achieve lock, which is a key lock, value is the expiration time lock. Execution: 1. () method attempts by setnx lock, if the current key is not present, return lock success. 2. expiration of the time to acquire the lock if the lock already exists, and the current time, and if the lock has expired, then set a new expiration time, return to lock success. code show as below:

So where is the problem in this code? 1. Because it is the client's own generation expiration time, so it is necessary mandatory time for each client distributed must be synchronized. 2. When the lock expired time, if multiple clients simultaneously execute jedis.getSet () method, then although ultimately only one client can be locked, but the lock client expiration time may be covered by other clients. 3. The lock owner does not have the identity, that is, any client can be unlocked.

Unlock code

Correct posture

I would first display code, and then slowly bring you explain why this realization:

public  class RedisTool { 

Private  static  Final Long RELEASE_SUCCESS = 1L ; 

/ ** 

* Distributed Lock release 

* @param jedis the Redis client 

* @param lockKey lock 

* @param the requestId request identifier 

* @return whether to release success 

* / 

  public  static  Boolean releaseDistributedLock (jedis jedis, lockKey String, String the requestId) { 

    String Script = "IF redis.call ( 'GET', KEYS [. 1]) == ARGV [. 1] redis.call the then return ( 'del', KEYS [. 1]) 0 End return the else " ; 
  
    Object Result = jedis.eval(script, Collections.singletonList(lockKey), Collections.singletonList(requestId));

    if (RELEASE_SUCCESS.equals(result)) {

      return true;

    }

    return false;

  }

}

 

We can see that we need to unlock only two lines of code to get! The first line of code, we wrote a simple Lua script code, the last time to see this programming language, or in the "Hackers and Painters" in, did not think this actually spend. The second line, we will Lua code spread jedis.eval () method, the parameters and KEYS [1] assigned lockKey, ARGV [1] assigned requestId. eval () method is Redis Lua code to perform the service side.

So what's the function of this Lua code is it? In fact, very simple, first get value corresponding to the value of the lock, check for equal requestId, if they are equal then remove the lock (unlock). So why use the Lua language to achieve it? To ensure that the operation was as atomic. About non-atomic would bring any questions, you can read. So why the implementation of eval () method ensures atomicity, due to the characteristics Redis, the following is a partial explanation of the official website of the eval command:

Simply put, when eval command Lua code, Lua code will be treated as a command to execute, and until the eval command is completed, Redis will execute other commands.

Error Example 1

The most common unlock code is used directly jedis.del () method to remove the lock, this is not to determine the owner of the lock and unlock a direct way, will lead to 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

At first glance this unlock code is not the problem, even before I have almost achieved, and almost correct posture, the only difference is divided into two commands to execute code as follows:

public  static  void wrongReleaseLock2 (Jedis jedis, lockKey String, String the requestId) { 

   // Analyzing lock and unlock a client is not the same 

  IF (requestId.equals (jedis.get (lockKey))) { 

    // If at this time, this lock suddenly not the client will lock misunderstanding 

    jedis.del (lockKey); 

  } 

}

 

When such as code comments, the problem is that if you call jedis.del () method when the lock does not belong to the current client will lift others plus the lock. So really there is such a scenario? The answer is yes, such as the client A lock, after a period of time the client A unlocked before executing jedis.del (), lock suddenly expired, then the client B lock attempt is successful, then the client A and then execute del () method, the client will lock to release the B.


to sum up

This article describes how to use Java code properly implemented Redis distributed lock for locking and unlocking were also given two more classic example of an error. In fact we want to achieve through a distributed lock Redis is not difficult, as long as the four conditions to ensure reliability in the meet. Although the Internet has brought us convenience, there is a problem as long as you can google, but the online answer must be right? In actual fact, we should always keep the skepticism, and more would like to verify.

 

This switched link: https: //www.jianshu.com/p/249e3c8c8bd3



Guess you like

Origin www.cnblogs.com/luxianyu-s/p/11326322.html