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

What are the Redis distributed locks?

2025-02-23 Update From: SLTechnology News&Howtos shulou NAV: SLTechnology News&Howtos > Database >

Share

Shulou(Shulou.com)06/01 Report--

What are the Redis distributed locks? This problem may be often seen in our daily study or work. I hope you can gain a lot from this question. The following is the reference content that the editor brings to you, let's take a look at it!

The synchronized or Lock we usually use are thread locks that are valid for multiple threads within the same JVM process. Because the essence of a lock is to store a token in memory to record who acquired the lock, this tag is visible to each thread. However, we start multiple order services, that is, multiple JVM, the locks in memory are obviously not shared, and each JVM process has its own lock, so it is impossible to guarantee the mutual exclusion of threads. At this time, we need to use distributed locks. There are three commonly used solutions: 1. Implementation based on database 2. The temporary serialization node based on zookeeper implements 3.redis implementation. In this paper, we introduce the implementation of redis.

The realization of distributed lock should satisfy three points: multi-process visible, mutually exclusive, and reentrant.

1) multiple processes are visible

Redis itself is based on something other than JVM, so it meets the requirements of multi-process visibility.

2) Mutual exclusion

That is, only one process can obtain the lock tag at a time. We can implement it through redis's setnx. Only the first process will succeed and return 1, and in other cases, it will return 0.

Release lock

To release the lock, you only need to delete the lock's key, using the del xxx instruction. However, if the service goes down suddenly before we execute the del, the lock can never be removed. So we can set the expiration time through the setex command.

Import java.util.UUID;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Component;import redis.clients.jedis.Jedis;import redis.clients.jedis.JedisPool;/** * the first distributed lock * / @ Componentpublic class RedisService {private final Logger log = LoggerFactory.getLogger (this.getClass ()); @ Autowired JedisPool jedisPool / / timeout before acquiring lock (waiting retry time for acquiring lock) private long acquireTimeout = 5000; / / timeout after acquiring lock (preventing deadlock) private int timeOut = 10000; / * * acquiring distributed lock * @ return lock identity * / public boolean getRedisLock (String lockName,String val) {Jedis jedis = null Try {jedis = jedisPool.getResource () / / 1. Calculate the time to acquire the lock Long endTime = System.currentTimeMillis () + acquireTimeout; / / 2. Try to acquire the lock while (System.currentTimeMillis () < endTime) {/ / 3. If the lock is acquired successfully, set the expiration time if (jedis.setnx (lockName, val) = = 1) {jedis.expire (lockName, timeOut/1000); return true;} catch (Exception e) {log.error (e.getMessage ());} finally {returnResource (jedis) } return false;} / * release distributed lock * @ param lockName lock name * / public void unRedisLock (String lockName) {Jedis jedis = null; try {jedis = jedisPool.getResource (); / / release lock jedis.del (lockName) } catch (Exception e) {log.error (e.getMessage ());} finally {returnResource (jedis);}} / / = = public String get (String key) {Jedis jedis = null; String value = null; try {jedis = jedisPool.getResource (); value = jedis.get (key) Log.info (value);} catch (Exception e) {log.error (e.getMessage ());} finally {returnResource (jedis);} return value;} public void set (String key, String value) {Jedis jedis = null; try {jedis = jedisPool.getResource () Jedis.set (key, value);} catch (Exception e) {log.error (e.getMessage ());} finally {returnResource (jedis);}} / * close connection * / public void returnResource (Jedis jedis) {try {if (jediscounted null) jedis.close () } catch (Exception e) {}

The above distributed lock is implemented, but two other problems may arise at this time:

One: when acquiring the lock

The setnx acquired the lock successfully, and the setex service went down before it had time, and the deadlock occurred again because of this non-atomic operation. In fact, redis provides commands to use nx with ex.

Two: when releasing the lock

1. 3 processes: an and B and C, performing tasks and scrambling for locks, when An acquires the lock and sets the automatic expiration time to 10s

2. A starts to execute the business, for some reason, the business is blocked and takes more than 10 seconds, and the lock is automatically released.

3. B just at this time began to try to acquire the lock, because the lock has been automatically released, successfully acquired the lock

4. A completes the execution of the business and executes the release lock logic (delete key), so the lock of B is released, while B is actually still executing the business.

5. At this point, process C attempts to acquire the lock, which is also successful because A removes B's lock.

There is a problem: B and C acquire the lock at the same time, violating the mutual exclusion! How to solve this problem? We should determine whether the lock is self-set before deleting the lock, and if not (for example, our own lock has been released during a timeout), then do not delete it. So we can store the unique ID of the current thread when the set is locked! Before deleting the lock, judge whether the value inside is released with your own identity. If it is inconsistent, it means that it is not your own lock, so do not delete it.

/ * second distributed lock * / public class RedisTool {private static final String LOCK_SUCCESS = "OK"; private static final Long RELEASE_SUCCESS = 1L / * 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, "NX", "PX", expireTime) If (LOCK_SUCCESS.equals (result)) {return true;} return false } / * release distributed locks * @ param jedis Redis client * @ param lockKey locks * @ param requestId request identification * @ return whether the release was successful * / public static boolean releaseDistributedLock (Jedis jedis, String lockKey String requestId) {if (jedis.get (lockKey) .equals (requestId)) {System.out.println ("release lock..." + Thread.currentThread () .equals () + ", identifierValue:" + requestId) Jedis.del (lockKey); return true;} return false;}}

After implementing distributed locks as described above, most of the problems can be easily solved. Many blogs on the Internet are also implemented in this way, but there are still some scenarios that are not satisfactory. for example, after a method acquires a lock, it may not be able to obtain a lock when the method is internally tuned. At this point, we need to improve the lock to a reentrant lock.

3) reenter the lock:

Also known as recursive lock, it means that in the same thread, after the outer function acquires the lock, the inner recursive function can still acquire the lock. To put it another way: when the same thread enters the synchronization code again, it can use the lock it has acquired. Reentrant locks can avoid deadlocks caused by multiple locks acquired in the same thread. For example, synchronized is a reentrant lock, which is implemented by recording the current thread information through the moniter function. There are two things to consider when implementing a reentrant lock:

Get the lock: first try to acquire the lock, if the acquisition fails, determine whether the lock is your own, if so, allow it to be acquired again, and you must record the number of times the lock has been acquired repeatedly.

Release lock: the release lock cannot be deleted directly, because the lock is reentrant. If the lock is entered multiple times, the lock is deleted directly in the inner layer, causing the external business to execute without the lock, which will cause security problems. Therefore, the cumulative number of reentrants during lock must be obtained, and the number of reentrants is subtracted when released, and if reduced to 0, the lock can be deleted.

Below, we assume that the key of the lock is "lock", and hashKey is the id of the current thread: "threadId". The automatic release time of the lock is assumed to be 20 steps to acquire the lock: 1. To determine whether the lock exists EXISTS lock 2, if it does not exist, it acquires the lock by itself, and the number of reentry layers of the record is 1. 2, which means that someone has acquired the lock. The following is to judge whether it is your own lock, that is, to determine whether the current thread id exists as a hashKey: HEXISTS lock threadId 3. If it does not exist, it means that the lock already exists and is not acquired by yourself, and the lock acquisition failed. 3. Exist, indicating that the lock is acquired by yourself. The number of reentrants is + 1: HINCRBY lock threadId 1, and the automatic release time of the lock is updated at last, and the steps of releasing the lock by EXPIRE lock 20 are as follows: 1. Judge whether the current thread id exists as hashKey: HEXISTS lock threadId 2, does not exist, indicating that the lock has expired and does not exist, indicating that the lock is still there, and the number of reentrants minus 1: HINCRBY lock threadId-1 3. Get the new number of reentrants, and determine whether the number of reentrants is 0. If 0 means all locks are released, delete key: DEL lock

Therefore, the information stored in the lock must contain: key, thread identity, number of reentrants. You can no longer use a simple key-value structure, but the hash structure is recommended here.

The script that acquires the lock (delete the comment, or run the error report)

Local key = KEYS [1];-- the first parameter, the keylocal threadId of the lock = ARGV [1];-- the second parameter, the thread uniquely identifies local releaseTime = ARGV [2];-- the third parameter, the automatic release time of the lock if (redis.call ('exists', key) = = 0) then-- determines whether the lock already exists redis.call (' hset', key, threadId,'1') -- if it does not exist, acquire the lock redis.call ('expire', key, releaseTime);-- set the validity period return 1;-- return the result end;if (redis.call (' hexists', key, threadId) = = 1) then-- the lock already exists to determine whether the threadId is its own redis.call ('hincrby', key, threadId,' 1') -- if it is you, then the number of reentrants is + 1 redis.call ('expire', key, releaseTime);-- set the validity period return 1;-- return the result end;return 0;-- this indicates that you are not the one who acquired the lock and failed to acquire the lock.

The script that releases the lock (delete the comment, or run the error report)

Local key = KEYS [1];-- the first parameter, the keylocal threadId of the lock = ARGV [1];-- the second parameter, the thread uniquely identifies if (redis.call ('HEXISTS', key, threadId) = = 0) then-- determines whether the current lock is still held by itself return nil;-- if it is not itself, directly returns end;local count = redis.call (' HINCRBY', key, threadId,-1). -- if it is your own lock, then the number of reentrants-1if (count = = 0) then-- determines whether the number of reentrants is 0 redis.call ('DEL', key);-- if it equals to 0, the lock can be released and the return nil; end can be deleted directly.

Complete code

Import java.util.Collections;import java.util.UUID;import org.springframework.core.io.ClassPathResource;import org.springframework.data.redis.core.StringRedisTemplate;import org.springframework.data.redis.core.script.DefaultRedisScript;import org.springframework.scripting.support.ResourceScriptSource;/** * Redis reentrant lock * / public class RedisLock {private static final StringRedisTemplate redisTemplate = SpringUtil.getBean (StringRedisTemplate.class); private static final DefaultRedisScript LOCK_SCRIPT; private static final DefaultRedisScript UNLOCK_SCRIPT Static {/ / load script LOCK_SCRIPT = new DefaultRedisScript () for releasing locks; LOCK_SCRIPT.setScriptSource (new ResourceScriptSource (new ClassPathResource ("lock.lua"); LOCK_SCRIPT.setResultType (Long.class); / / load scripts for releasing locks UNLOCK_SCRIPT = new DefaultRedisScript (); UNLOCK_SCRIPT.setScriptSource (new ResourceScriptSource (new ClassPathResource ("unlock.lua") } / * * get lock * @ param lockName lock name * @ param releaseTime timeout (in seconds) * @ return key unlock identification * / public static String tryLock (String lockName,String releaseTime) {/ / prefix of stored thread information to prevent conflicts with thread information in other JVM String key = UUID.randomUUID (). ToString () / / execute script Long result = redisTemplate.execute (LOCK_SCRIPT, Collections.singletonList (lockName), key + Thread.currentThread (). GetId (), releaseTime); / / judgment result if (result! = null & & result.intValue () = = 1) {return key;} else {return null }} / * release lock * @ param lockName lock name * @ param key unlock identification * / public static void unlock (String lockName,String key) {/ / execute script redisTemplate.execute (UNLOCK_SCRIPT, Collections.singletonList (lockName), key + Thread.currentThread () .getId () Null) Thank you for your reading! After reading the above, do you have a general understanding of Redis distributed locks? I hope the content of the article will be helpful to all of you. If you want to know more about the relevant articles, you are welcome to follow the industry information channel.

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