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

How to implement Redis distributed Lock

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

Share

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

This article will explain in detail how to implement Redis distributed locks. The editor thinks it is very practical, so I share it with you as a reference. I hope you can get something after reading this article.

What is a distributed lock?

To introduce distributed locks, we should first mention that thread locks and process locks correspond to distributed locks.

Thread locks: mainly used to lock methods and code blocks. When a method or code uses a lock, only one thread executes the method or code snippet at a time. Thread locks only work in the same JVM, because thread locks are fundamentally implemented by shared memory between threads, such as synchronized is a shared object header, showing that the lock Lock is sharing a variable (state).

Process lock: in order to control multiple processes in the same operating system to access a shared resource, because the process is independent, each process cannot access the resources of other processes, so it is impossible to realize the process lock through thread locks such as synchronized.

Distributed lock: when multiple processes are not in the same system, use distributed lock to control the access of multiple processes to resources.

Preface

Now the business scenario is becoming more and more complex, and the architecture used is becoming more and more complex. Distributed and high concurrency have become the norm of business requirements. For example, there are many services in Tencent, such as CDN optimization, remote multi-backup and so on.

When it comes to distribution, it is bound to involve the concept of distributed locks. How to ensure the synchronization of distributed locks on different machines and different threads?

Key points of realization

Mutually exclusive, at the same time, the intelligence has a client holding the lock.

To prevent the occurrence of deadlock, if the client holding the lock crashes and does not actively release the lock, it is also necessary to ensure that the lock can be released normally and that other clients can lock normally.

Locking and releasing locks must be the same client.

Fault tolerance: normal locking and unlocking operations can be performed only if the redis still has a node to survive.

Correct redis distributed Lock implementation of wrong locking method

Wrong way one

To ensure mutual exclusion and prevent deadlocks, the first thing that comes to mind is to use redis's setnx command to ensure mutual exclusion. In order to prevent deadlocks, locks need to set a timeout.

Public static void wrongLock (Jedis jedis, String key, String uniqueId, int expireTime) {Long result = jedis.setnx (key, uniqueId); if (1 = = result) {/ / if the redis instance crashes, then jedis.expire (key, expireTime);}} cannot be set.

In a multithreaded concurrency environment, any non-atomic operation can cause problems. In this code, if the redis instance crashes when the expiration time is set, the expiration time cannot be set. If the client does not release the lock correctly, the lock (which will never expire) will never be released.

Wrong way two

What is easier to think of is that setting values and timeouts for atomic operations can solve the problem. Wouldn't it be ok to use the setnx command to set value to expire?

Public static boolean wrongLock (Jedis jedis, String key, int expireTime) {long expireTs = System.currentTimeMillis () + expireTime; / / lock does not exist. Current thread lock result if (jedis.setnx (key, String.valueOf (expireTs)) = = 1) {return true;} String value = jedis.get (key) / / if the current lock exists and the lock has expired if (value! = null & & NumberUtils.toLong (value) < System.currentTimeMillis ()) {/ / the lock expires, set the new expiration time String oldValue = jedis.getSet (key, String.valueOf (expireTs)) If (oldValue! = null & & oldValue.equals (value)) {/ / under multithreading concurrency, only one thread will set successful / / successful thread, and the old value of key must be consistent with the value of key before setting. Return true;} / / other cases, lock failure return true;}

At first glance, there is no problem. However, after careful analysis, there are the following problems:

When value is set to expiration time, each client is required to strictly synchronize the clock, which requires the use of a synchronous clock. Even if there is a synchronous clock, distributed servers generally have a little error in time.

When the lock expires, using jedis.getSet can guarantee that only one thread is set successfully, but it is not guaranteed to lock and unlock to the same client, because there is no indication of which client set the lock.

Error unlocking mode

Unlock error mode 1

Delete key directly

Public static void wrongReleaseLock (Jedis jedis, String key) {/ / is not a self-locked key, it will also be released jedis.del (key);}

Simple and rude, directly unlocked, but not self-locking, will also be deleted, this seems to be a bit too casual!

Unlock error mode 2

Determine whether you are the holder of the lock, and if so, only the holder can release the lock.

Public static void wrongReleaseLock (Jedis jedis, String key, String uniqueId) {if (uniqueId.equals (jedis.get (key) {/ / if the lock is automatically released when the lock expires and is locked by another thread, the thread will release the lock jedis.del (key) that does not belong to it;}}

It looks perfect, but if you judge that the lock is held by yourself, the lock timeout is automatically released. Then it is re-locked by other clients, and then the current thread executes to jedis.del (key), so this thread removes the locks on other threads, which seems to be a bit out of order.

Correct lock release mode

Basically avoid the above several wrong ways, is the right way. The following conditions must be met:

Commands must be mutually exclusive

The set key must have an expiration time to prevent the lock from being released in the event of a crash

Value uses a unique id to mark each client to ensure that only the holder of the lock can release the lock

Locking directly uses the set command to set a unique id and expiration time at the same time; unlocking is slightly more complicated, and you can return a unique id after locking, indicating that the lock is owned by the client lock; when releasing the lock, you need to determine whether the owner is yourself, and then delete it. This requires the lua script of redis to ensure the atomic execution of the two commands.

Here is the specific code for locking and releasing the lock:

@ Slf4jpublic class RedisDistributedLock {private static final String LOCK_SUCCESS = "OK"; private static final Long RELEASE_SUCCESS = 1L; private static final String SET_IF_NOT_EXIST = "NX"; private static final String SET_WITH_EXPIRE_TIME = "PX"; / / timeout of the lock private static int EXPIRE_TIME = 5 * 1000; / / lock wait time private static int WAIT_TIME = 1 * 1000; private Jedis jedis; private String key; public RedisDistributedLock (Jedis jedis, String key) {this.jedis = jedis This.key = key;} / / keep trying to lock public String lock () {try {/ / exceeds the waiting time, lock failure long waitEnd = System.currentTimeMillis () + WAIT_TIME; String value = UUID.randomUUID () .toString (); while (System.currentTimeMillis () < waitEnd) {String result = jedis.set (key, value, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, EXPIRE_TIME) If (LOCK_SUCCESS.equals (result)) {return value;} try {Thread.sleep (10);} catch (InterruptedException e) {Thread.currentThread (). Interrupt ();}} catch (Exception ex) {log.error ("lock error", ex);} return null;} public boolean release (String value) {if (value = = null) {return false } / / judging the existence of key and deleting key must be an atomic operation / / and who owns the lock, who releases String script = "if redis.call ('get', KEYS [1]) = = ARGV [1] then return redis.call (' del', KEYS [1]) else return 0 end"; Object result = new Object (); try {result = jedis.eval (script, Collections.singletonList (key), Collections.singletonList (value)) If (RELEASE_SUCCESS.equals (result)) {log.info ("release lock success, value: {}", value); return true;}} catch (Exception e) {log.error ("release lock error", e);} finally {if (jedis! = null) {jedis.close ();}} log.info ("release lock failed, value: {}, result: {}", value, result); return false }} this is the end of the article on "how to implement Redis distributed locks". I hope the above content can be of some help to you, so that you can learn more knowledge. if you think the article is good, please share it for more people to see.

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