Network Security Internet Technology Development Database Servers Mobile Phone Android Software Apple Software Computer Software News IT Information

In addition to Weibo, there is also WeChat

Please pay attention

WeChat public account

Shulou

Correct implementation of Redis distributed Lock

2025-01-17 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)05/31 Report--

What is the correct implementation of Redis distributed locks? Many novices are not very clear about this. In order to help you solve this problem, the following editor will explain it in detail. People with this need can come and learn. I hope you can gain something.

Reliability.

First, to ensure that distributed locks are available, we need to ensure that the implementation of the lock meets at least the following four conditions:

1. Repulsion. At any given time, only one client can hold the lock.

two。 There is no deadlock. Even if one client crashes while holding the lock without actively unlocking it, it is guaranteed that other clients will be able to lock it.

3. It is fault tolerant. As long as most of the Redis nodes are functioning properly, the client can lock and unlock.

4. The person who unlocks the bell must also tie the bell. Locking and unlocking must be the same client, and the client cannot unlock the lock added by others.

Code implementation

Component dependency

First, we will introduce the Jedis open source component through Maven and add the following code to the pom.xml file:

Redis.clients jedis 2.9.0

Lock code

Correct posture

Talk is cheap, show me the code . First show the code, and then slowly explain why it is implemented this way:

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 lock * @ param jedis Redis client * @ param lockKey lock * @ param requestId request ID * @ param expireTime timeout * @ return whether to obtain successful * / 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 a total of five formal parameters:

The first is key, and we use key as the lock because key is unique.

The second is value, we pass is requestId, many children's shoes may not understand, there is key as a lock is not enough, why do you still use value? The reason is that when we talk about reliability above, if the distributed lock meets the fourth condition, the value must also be tied. By assigning the value of requestId to the lock, we will know which request added the lock, and can have a basis when unlocking it. RequestId can be generated using the UUID.randomUUID (). ToString () method.

The third is nxxx. This parameter we fill in is NX, which means SET IF NOT EXIST, that is, when key does not exist, we perform set operation; if key already exists, we do nothing.

The fourth is expx, this parameter we pass is PX, which means that we want to add an expired 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 above set () method results in only two results: 1. There is currently no lock (key does not exist), then the lock operation is performed and the lock is valid, and value represents the locked client. two。 There is already a lock and no action is done.

Careful children's shoes will find that our lock code meets the three conditions described in our reliability. First of all, set () adds a NX parameter to ensure that if key already exists, the function will not be called successfully, that is, only one client can hold the lock, which satisfies the mutex. Second, because we set the expiration time for the lock, even if the holder of the lock crashes and does not unlock the lock, the lock will be automatically unlocked (that is, the key will be deleted) without deadlock. Finally, because we assign value to requestId, which represents the locked client request ID, the client can verify whether it is the same client when it is unlocked. Since we only consider the scenario of stand-alone deployment of Redis, we will not consider fault tolerance for the time being.

Error example 1

A common example of error is locking using a combination of jedis.setnx () and jedis.expire (). 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 jedis.expire (lockKey, expireTime) will occur;}}

The function of the setnx () method is that the SET IF NOT EXIST,expire () method adds an expiration time to the lock. At first glance, it looks like the result of the previous set () method, but because these are two Redis commands and are not atomic, if the program suddenly crashes after executing setnx (), the lock does not set the expiration time. Then a deadlock will occur. The reason why people do this on the Internet is that the lower version of jedis does not support the 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 lock does not exist, return if successfully locked (jedis.setnx (lockKey, expiresStr) = = 1) {return true;} / / if the lock exists, obtain the expiration time of the lock String currentValueStr = jedis.get (lockKey) If (currentValueStr! = null & & Long.parseLong (currentValueStr) < System.currentTimeMillis ()) {/ / the lock has expired, get the expiration time of the last lock, and set the expiration time of the current lock String oldValueStr = jedis.getSet (lockKey, expiresStr); if (oldValueStr! = null & & oldValueStr.equals (currentValueStr)) {/ / consider multithreading concurrency. Only one thread has the same setting as the current value before it has the right to lock return true. }} / / otherwise, lock failure return false;} will be returned.

This kind of error example is more difficult to find, and the implementation is more complex. The idea is to use the jedis.setnx () command to add locks, where key is the lock and value is the expiration time of the lock. Execution process: 1. The lock is attempted through the setnx () method, and if the current lock does not exist, the lock is returned successfully. two。 If the lock already exists, get the expiration time of the lock. Compared with the current time, if the lock has expired, set a new expiration time and return the lock successfully.

So what's wrong with this code? 1. Because the client generates the expiration time itself, it is necessary to force the time of each client in the distributed environment to be synchronized. two。 When the lock expires, if multiple clients execute the jedis.getSet () method at the same time, although in the end only one client can add the lock, the expiration time of that client's lock may be overridden by other clients. 3. The lock does not have the owner identity, that is, any client can unlock it.

Unlock code

Correct posture

Or first show the code, and then slowly explain why it is implemented in this way:

Public class RedisTool {private static final Long RELEASE_SUCCESS = 1L; / * * release distributed lock * @ param jedis Redis client * @ param lockKey lock * @ param requestId request identifies whether @ return has been released successfully * / 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, it only takes two lines of code to unlock it! The first line of code, we wrote a simple Lua script code, the last time we saw this programming language in "hackers and painters", did not expect to use it this time. In the second line of code, we pass the Lua code to the jedis.eval () method and assign the parameter KEYS [1] to lockKey,ARGV [1] to requestId. The eval () method gives 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, check whether it is equal to requestId, and if equal, delete the lock (unlock). So why use Lua to implement it? Because you need to make sure that the above operations are atomic. For more information about the problems caused by non-atomicity, you can read [unlocking Code-error example 2]. So why the execution of the eval () method ensures atomicity stems from the nature of Redis, here is a partial explanation of the eval command on the official website:

To put it simply, when the eval command executes the Lua code, the Lua code will be executed as a command, and Redis will not execute any other commands until the eval command is completed.

Error example 1

The most common unlock code is to delete the lock directly using the jedis.del () method. This way of unlocking 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.

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

Error example 2

At first glance, this kind of unlocking code is fine, and even I almost implemented it before, which is similar to the correct posture, except that it is divided into two commands to execute. The code is as follows:

Public static void wrongReleaseLock2 (Jedis jedis, String lockKey, String requestId) {/ / determine whether locking and unlocking are the same client if (requestId.equals (jedis.get (lockKey) {/ / if suddenly the lock does not belong to this client, the lock jedis.del (lockKey) will be misunderstood;}}

For example, code comments, the problem is that if the jedis.del () method is called, the lock will be released when the lock no longer belongs to the current client. So is there really such a scene? The answer is yes, for example, client An adds a lock, client A unlocks after a period of time, and before executing jedis.del (), the lock suddenly expires, client B attempts to lock successfully, and then client An executes the del () method, client B is unlocked.

Is it helpful for you to read the above content? If you want to know more about the relevant knowledge or read more related articles, please follow the industry information channel, thank you for your support.

Welcome to subscribe "Shulou Technology Information " to get latest news, interesting things and hot topics in the IT industry, and controls the hottest and latest Internet news, technology news and IT industry trends.

Views: 0

*The comments in the above article only represent the author's personal views and do not represent the views and positions of this website. If you have more insights, please feel free to contribute and share.

Share To

Database

Wechat

© 2024 shulou.com SLNews company. All rights reserved.

12
Report