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 distributed Lock with Redis

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

Share

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

This article will explain in detail how to use Redis to achieve 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?

We all know the concept of lock in Java, such as synchronous lock synchronized based on JVM and a set of code-level locking mechanism lock provided by jdk. We often use these two locks in concurrent programming to ensure that the code runs correctly in a multi-threaded environment. However, these locking mechanisms are not applicable in distributed scenarios, because in distributed business scenarios, our code runs on different JVM or even different machines, and synchronized and lock can only work in the same JVM environment. So this is the time to use distributed locks.

For example, there is now a scene of grabbing consumption coupons on the hour (due to the epidemic, Alipay recently opened to grab consumption coupons at 8: 00 or 12:00). Consumption coupons have a fixed quantity, first-come-first-served, and then gone. Multiple online services are deployed, and the general structure is as follows:

So at this time, we have to use distributed locks to ensure the correctness of access to shared resources.

Back to the top.

Why use distributed locks, huh?

Assuming that distributed locks are not used, let's see if synchronized can guarantee it. Actually, it can 't. let's demonstrate it.

Below I wrote a simple springboot project to simulate the scenario of grabbing consumption coupons, the code is very simple, it roughly means that first get the remaining consumption coupons from Redis, and then judge that it is greater than 0, then minus one simulation is grabbed by a user, and then after minus one, modify the remaining consumption coupons of Redis, print the deduction successfully, and how much is left, otherwise the deduction fails and you do not get it. The whole code is wrapped by synchronized, and the inventory quantity set by Redis is 50.

/ / suppose the inventory number is 00001private String key = "stock:00001"; @ Autowiredprivate StringRedisTemplate stringRedisTemplate;/** * deducts the inventory synchronized synchronization lock * / @ RequestMapping ("/ deductStock") public String deductStock () {synchronized (this) {/ / get the current inventory int stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get (key)); if (stock > 0) {int afterStock = stock-1 StringRedisTemplate.opsForValue (). Set (key,afterStock+ "); / / modify inventory System.out.println (" inventory reduction succeeded, surplus inventory "+ afterStock);} else {System.out.println (" inventory reduction failed ");} return" ok ";}

Then start two springboot projects with port 8080d8081, and then configure load balancer in nginx

Upstream redislock {server 127.0.0.1 server 8080; server 127.0.0.1 server 8081;} server {listen 80; server_name 127.0.0.1; location / {root html; index index.html index.htm; proxy_pass http://redislock;}}

Then use the jmeter pressure test tool to test

Then we look at the console output and we can see that we are running two web instances, and many of the same consumption coupons are snatched by different threads, which proves that synchronized does not work in this case, so we need to use distributed locks to ensure the correctness of resources.

Back to the top.

How to use Redis to implement distributed locks?

Before implementing a distributed lock, we first consider how to implement it and what functions of the lock we want to implement.

1. Distributed feature (this lock can be accessed by instances deployed on multiple machines)

2. Exclusivity (only one thread can hold a lock at a time)

3. Automatic timeout release (the thread holding the lock needs to be given a certain maximum time to hold the lock to prevent the thread from dying and unable to release the lock)

4 、...

Based on the basic features that the distributed locks listed above need to have, let's consider how to implement them using Redis.

1. The first distributed feature, Redis, is already supported. Multiple instances together with a Redis can be used.

2. The second exclusiveness, that is, to implement an exclusive lock, can be implemented using Redis's setnx command

3. The third automatic timeout release feature. Redis can set the expiration time for a certain key.

4. Release the distributed lock after execution

Popular science time

Redis Setnx command

The Redis Setnx (SET if Not eXists) command sets the specified value for key when the specified key does not exist.

Grammar

The basic syntax for redis Setnx commands is as follows:

Redis 127.0.0.1 6379 > SETNX KEY_NAME VALUE

Available version: > = 1.0.0

Return value: set successfully, return 1, failed to set, return 0

@ RequestMapping ("/ stock_redis_lock") public String stock_redis_lock () {/ / the underlying layer uses the setnx command Boolean aTrue = stringRedisTemplate.opsForValue () .setIfAbsent (lock_key, "true"); stringRedisTemplate.expire (lock_key,10, TimeUnit.SECONDS); / / sets the expiration time for 10 seconds if (! aTrue) {/ / failed to get the distributed lock return "error" / / you can give the user a friendly hint here} / / get the current inventory int stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get (key)); if (stock > 0) {int afterStock = stock-1; stringRedisTemplate.opsForValue (). Set (key,afterStock+ "); System.out.println (" inventory reduction succeeded, remaining inventory "+ afterStock) } else {System.out.println ("inventory reduction failed");} stringRedisTemplate.delete (lock_key); / / release the distributed lock return "ok" after execution;}

Still set the inventory to 50, let's test it again with jmeter, change the test address of jmeter to 127.0.0.1/stock_redis_lock, and test it again with the same setting.

No dirty data has been tested for 5 times, and it is no problem to change the sending time to 0, and then change the number of threads to 600, the time is 0, cycle 4 times, and it is normal to test several times.

The above code to implement the distributed lock is already a more mature implementation of the distributed lock, which has met the requirements for most software companies. But there is still room for optimization in the above code, for example:

1) We do not consider the exception situation in the above code. In fact, the code is not so simple, and there may be many other complex operations that may cause exceptions, so the code that releases the lock needs to be placed in the finally block to ensure that even if the code throws an exception, the code that releases the lock will still be executed.

2) also, have you noticed that the code for acquiring and setting the expiration time of our distributed lock above is a two-step operation on lines 4 and 5, that is, non-atomic operations. It is possible that the machine has just executed line 4 before it has time to execute line 5, so the lock has not set a timeout, and other threads have been unable to acquire it, unless human intervention, so this is an one-step optimization. Redis also provides atomic operations, that is, SET key value EX seconds NX

Popular science time

SET key value [EX seconds] [PX milliseconds] [NX | XX] associates the string value value to key

Optional parameter

Starting with Redis version 2.6.12, the behavior of the SET command can be modified with a series of parameters:

EX second: sets the expiration time of the key to second seconds. SET key value EX second effect is equivalent to SETEX key second value.

PX millisecond: sets the expiration time of the key to millisecond milliseconds. SET key value PX millisecond effect is equivalent to PSETEX key millisecond value.

NX: set the key only when the key does not exist. SET key value NX effect is equivalent to SETNX key value.

XX: set the key only if the key already exists

SpringBoot's StringRedisTemplate also has a corresponding method implementation, as shown in the following code:

/ / assume that the inventory number is 00001private String key = "stock:00001"; private String lock_key = "lock_key:00001"; @ Autowiredprivate StringRedisTemplate stringRedisTemplate;@RequestMapping ("/ stock_redis_lock") public String stock_redis_lock () {String uuid = UUID.randomUUID () .toString (); try {/ / atomic setting key and timeout Boolean aTrue = stringRedisTemplate.opsForValue (). SetIfAbsent (lock_key, "true", 30, TimeUnit.SECONDS) If (! aTrue) {return "error";} int stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get (key)); if (stock > 0) {int afterStock = stock-1; stringRedisTemplate.opsForValue (). Set (key, afterStock + "); System.out.println (" inventory reduction succeeded, remaining inventory "+ afterStock) } else {System.out.println ("inventory reduction failed");} catch (NumberFormatException e) {e.printStackTrace ();} finally {/ / avoid deadlock if (uuid.equals (stringRedisTemplate.opsForValue (). Get (lock_key)) {stringRedisTemplate.delete (lock_key);}} return "ok";}

Is it perfect to achieve this? Well, for scenarios with low concurrency requirements or non-large concurrency, this is fine. However, for the scenario of panic buying and second kill, when the traffic is very large, the server network card, disk IO, and CPU load may all reach the limit, then the server's response time to a request is bound to become much slower than normal. Then suppose that the timeout time of the lock just set is 10 seconds. If a thread acquires the lock and fails to complete execution within 10 seconds for some reason, the lock will expire. At this point, other threads will preempt the distributed lock to execute the business logic, and then the previous thread will execute the lock release code in finally to release the lock of the thread that is occupying the distributed lock. In fact, before the thread that is occupying the lock has finished executing, other threads will have a chance to acquire the lock. In this way, the entire distributed lock will fail and will have unexpected consequences. The following figure simulates this scenario.

So to sum up this problem, it is because the expiration time of the lock is not set properly or for some reason the execution time of the code is longer than the expiration time of the lock, which leads to the concurrency problem and the lock is released by other threads, so that the distributed lock is confused. In a nutshell, there are two problems: 1) your own lock is released by others; 2) the lock timeout cannot be extended.

The first problem is easy to solve. When setting a distributed lock, we produce a unique string in the current thread to set value to this unique value, and then execute delete in the finally block when it is determined that the value of the current lock is the same as that set by ourselves, as follows:

String uuid = UUID.randomUUID (). ToString (); try {/ / atomic setting key and timeout, lock unique value Boolean aTrue = stringRedisTemplate.opsForValue (). SetIfAbsent (lock_key,uuid,30,TimeUnit.SECONDS); / /. Finally {/ / execute delete if (uuid.equals (stringRedisTemplate.opsForValue (). Get (lock_key)) {stringRedisTemplate.delete (lock_key)) {stringRedisTemplate.delete (lock_key); / / avoid deadlock}}

As soon as the problem is solved (imagine what else is wrong with the above code, which will be discussed later), the timeout of the lock is critical, neither too large nor too small, which requires evaluating the execution time of the business code, such as setting 10 seconds or 20 seconds. Even if your lock sets the appropriate timeout, the above analysis may not be avoided because for some reason the code does not finish within the normal evaluation time, so the solution at this time is to give the lock a timeout. The general idea is that the business thread sets up a separate sub-thread to regularly monitor whether the distributed lock set by the business thread still exists, which means that the business thread has not finished executing, then extend the timeout of the lock. If the lock no longer exists, the business thread finishes execution and then ends itself.

The logic of "Lock continues Life" is really a bit complicated. There are too many problems to consider. If you don't pay attention to it, there will be bug. Don't look at the few lines of code above to implement distributed locks, just think that it's easy to implement. If you don't have actual high concurrency experience when you implement it, you will certainly step on a lot of holes, such as

1) the lock setting and expiration time setting is non-atomic, which may lead to deadlock.

2) there is the one left above. Determine whether the lock is set by yourself in the finally block, and if so, delete the lock. These two steps are not atomic either. Assuming that the true service is hung up as soon as it is judged, the code to delete the lock will not be executed, resulting in a deadlock. Even if the expiration time is set, it will still be deadlocked during this period of time. So here is also a point to pay attention to, to ensure the atomic operation, Redis provides the ability to execute Lua scripts to ensure the atomicity of the operation, exactly how to use no longer expand.

Therefore, the implementation of this set of logic of "lock life" is still a bit complicated, but there is already a ready-made open source framework on the market to help us implement it, that is, Redisson.

Back to the top.

Implementation principle of Redisson distributed Lock

The principle of implementation:

1. First of all, Redisson will try to add locks. The principle of locking is to lock atoms using setnx commands similar to Redis. If the locking is successful, a child thread will be opened inside.

2. The child thread is mainly responsible for listening, in fact, it is a timer to monitor whether the main thread still holds the lock, which will delay the time of the lock, otherwise the thread will end.

3. If the lock fails, the spin keeps trying to lock.

4. Release the lock actively after executing the main thread of the code

Let's take a look at what the code looks like after using Redisson.

1. First add the maven coordinates of Redisson to the pom.xml file.

Org.redisson redisson 3.12.5

2. To get the object of Redisson, configure Bean as follows

@ SpringBootApplicationpublic class RedisLockApplication {public static void main (String [] args) {SpringApplication.run (RedisLockApplication.class, args);} @ Bean public Redisson redisson () {Config config = new Config (); config.useSingleServer () .setAddress ("redis://localhost:6379") .setDatabase (0); return (Redisson) Redisson.create (config);}}

3. Then we obtain the instance of Redisson and use its API to lock and release locks.

/ / suppose the inventory number is 00001private String key = "stock:00001"; private String lock_key = "lock_key:00001"; @ Autowiredprivate StringRedisTemplate stringRedisTemplate;/** * uses Redisson to implement distributed locks * @ return * / @ RequestMapping ("/ stock_redisson_lock") public String stock_redisson_lock () {RLock redissonLock = redisson.getLock (lock_key); try {redissonLock.lock () Int stock = Integer.parseInt (stringRedisTemplate.opsForValue (). Get (key)); if (stock > 0) {int afterStock = stock-1; stringRedisTemplate.opsForValue (). Set (key, afterStock + "); System.out.println (" inventory reduction succeeded, surplus inventory "+ afterStock);} else {System.out.println (" inventory reduction failed ") }} catch (NumberFormatException e) {e.printStackTrace ();} finally {redissonLock.unlock ();} return "ok";}

Is it very simple to see the API provided by Redisson's distributed lock? Just as Java concurrency becomes the Lock mechanism of AQS, get a RedissonLock as follows

RLock redissonLock = redisson.getLock (lock_key)

The object returned by default is the object of RedissonLock, which implements the RLock interface, while the RLock interface inherits the Lock interface in the JDK concurrent programming packet.

When using Redisson locking, it also provides a number of API, as follows

Now we choose to use the simplest no-parameter lock method. Simply click in and take a look at its source code. We find the final code that executes locking as follows:

We can see that the underlying layer uses Lua scripts to ensure atomicity, locking using Redis's hash structure, and reentrant locks.

It seems easier than our own implementation of distributed locks, but he has all the locking functions we wrote, and he has what we don't have. For example, the distributed lock he implements supports both reentrants and waits, that is, try to wait for a certain amount of time and return false if you don't get the lock. The redissonLock.lock () in the above code is waiting all the time, and the internal spin tries to lock it.

Distributed Java locks and synchronizers

Lock

FairLock

MultiLock

RedLock

ReadWriteLock

Semaphore

PermitExpirableSemaphore

CountDownLatch

Redisson.org

Redisson provides a wealth of API, internal use of a large number of Lua scripts to ensure atomic operation, the length of the reason redisson code to achieve the lock will not be analyzed for the time being.

Note: in the above sample code, in order to facilitate the demonstration, querying redis inventory and modifying inventory are not atomic operations. In fact, these two operations also have to guarantee atomic rows, which can be realized by using the Lua script function of redis.

This is the end of the article on "how to use Redis to achieve 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

Internet Technology

Wechat

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

12
Report