What are the five ways Redis implements distributed locks

01-21-2023

The editor of this article will introduce in detail the five methods of Redis to implement distributed locks. The content is detailed, the steps are clear, and the details are handled properly. I hope this article on what are the five methods of Redis to implement distributed locks can help you To solve the doubts, let’s follow the editor’s ideas and slowly deepen, let’s learn new knowledge together.

1. Stand-alone data consistency

The stand-alone data consistency architecture is shown in the figure below: Multiple clients can access the same server and connect to the same database .

1.jpg

Scene description: The client simulates the process of purchasing goods, and sets the total inventory in Redis to 100 , more Clients can purchase concurrently at the same time.


@RestController 

public class IndexController1 { @Autowired StringRedisTemplate template; @RequestMapping("/buy1") 

public String index(){ 

// Goods: No. 001 is stored in Redis, and the quantity is 100 

String result = template.opsForValue().get("goods:001"); 

// Get the number of remaining items 

int total = result == null ? 0 : Integer. parseInt(result); `` 

if( total > 0 ){       

// If the number of remaining items is greater than 0, then the deduction will be performed 

int realTotal = total -1; 

// Write the number of commodities back to the database     

template.opsForValue().set("goods:001",String.valueOf(realTotal));         

System.out.println("Purchased successfully, inventory remaining: "+realTotal +" pieces, service port is 8001");                                                                                  ,  }else{  System.out.println("Failed to purchase goods, service port is 8001"); } return "Failed to purchase goods, service port is 8001"; } }

Using Jmeter to simulate high concurrency scenarios

Test results showed that multiple users purchased the same product, A data inconsistency has occurred!

Solution: In the case of a single application, lock the concurrent operation to ensure the atomicity of the data operation

  • synchronized

  • ReentrantLock

@RestController public class IndexController2 { // Use the ReentrantLock lock to solve the concurrency problem of the single application Lock lock = new ReentrantLock(); @Autowired StringRedisTemplate template; @RequestMapping("/buy2") public String index() { lock. lock(); try { String result = template.opsForValue().get("goods:001"); int total = result == null ? 0 : Integer. parseInt(result); `` if (total > 0) { int realTotal = total - 1;      template.opsForValue().set("goods:001", String.valueOf(realTotal));         System.out.println("Purchased successfully, remaining inventory: " + realTotal + "pieces, service port is 8001"); return "Purchase the product successfully, and the remaining inventory is: " + realTotal + "pieces, the service port is 8001"; } else { ``System.out.println("Failed to purchase goods, service port is 8001"); } } catch (Exception e) { lock. unlock(); } finally { lock. unlock(); } return "Failed to purchase goods, the service port is 8001"; } }


2. Distributed data consistency

The above solves the data consistency problem of a single application, but if it is a distributed architecture deployment, The architecture is as follows:

Provide two services, the ports are 8001 and 8002 respectively, connecting to the same Redis service, in There is a Nginx in front of the service as a load balancer


The two service codes are the same, but the ports are different

Set 8001 , 8002 Two services are started, each service is still locked with ReentrantLock, and Jmeter is used for concurrency testing, and it is found that Data consistency problem!


3. Redis implements distributed lock

3.1 Method 1

Cancel stand-alone lock, use redis's set command to realize distributed locking

SET KEY VALUE [EX seconds] [PX milliseconds] [NX|XX]

  • EX seconds sets the specified expiration time (in seconds)

  • PX milliseconds sets the specified expiration time (in milliseconds)

  • NX set key only if key does not exist

  • XX set key only if it exists

@RestController 

public class IndexController4 { 

 // The key of the Redis distributed lock public static final String REDIS_LOCK = "good_lock"; @Autowired theStringRedisTemplate template; 

@RequestMapping("/buy4") public String index(){ 

//Everyone must first lock when they come in, the key value is "good_lock", and the value is randomly generated 

String value = UUID.randomUUID().toString().replace("-",""); try{ 

// lock      

Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value); 

// Failed to lock if(!flag){ return "Failed to grab the lock!"; }       

System.out.println( value+ " lock grab successful"); 

String result = template.opsForValue().get("goods:001");   int total = result == null ? 0 : Integer. parseInt(result);        

 if (total > 0) { int realTotal = total - 1;      template.opsForValue().set("goods:001", String.valueOf(realTotal));         

// If an exception occurs after grabbing the lock but 

before deleting the lock, the lock cannot be released,         

// The lock release operation cannot be performed here, it must be processed in finally // template.delete(REDIS_LOCK);    System.out.println("Purchased successfully, inventory remaining: " + realTotal + "pieces, service port is 8001");           

return "Purchased the product successfully, and the stock is still availableRemaining: " + realTotal + " pieces, the service port is 8001"; } else {        

System.out.println("Failed to purchase goods, the service port is 8001"); } 

return "Failed to purchase goods, service port is 8001"; } finally { 

// Release the lock   template.delete(REDIS_LOCK); } } }

The above code can solve the data consistency problem in the distributed architecture. But if you think about it carefully, there will still be problems, and we will make improvements below.

3.2 Method 2 (improvement method 1)

In the above code, if the program is running, the machine where the microservice jar package is deployed suddenly Hanging up, the code level has not reached the finally code block at all, that is to say, the lock has not been deleted before the downtime, so there is no way to guarantee unlocking

So, here you need to add an expiration time to this key. There are two ways to set the expiration time in Redis:

  • template.expire(REDIS_LOCK,10, TimeUnit.SECONDS)

  • template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L, TimeUnit.SECONDS)

The first method requires a single line of code, and it is not in the same step as locking, so it is not atomic , there will also be problems.

The second method sets the expiration time while locking, and there is no problem. This method is used here

Adjust the code and add it While locking, set the expiration time:

// Add an expiration time to the key, and the rest of the code remains unchanged Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK,value,10L,TimeUnit.SECONDS);

This method solves the problem that the lock cannot be released due to the sudden downtime of the service. But if you think about it, there will still beproblem, the following improvements are made.

3.3 Method 3 (improved method 2)

Method 2 sets the expiration time of key, which solves the problem that key cannot be deleted

The expiration time of the key is set to 10 seconds. If the business logic is more complicated, you need to call other micro Service, the processing time needs 15 seconds (simulation scene

, don’t be serious), and when 10 seconds pass, the key expires, and other requests can set this key again. At this time, if the request that takes 15 seconds

is processed, If you come back and continue to execute the program, the key set by others will be deleted. This is a very serious problem!

So, whoever locks it can delete it

@RestController 

public class IndexController6 { 

public static final String REDIS_LOCK = "good_lock"; 

@Autowired StringRedisTemplate template; 

@RequestMapping("/buy6") 

public String index(){ 

 //Everyone must first lock when they come in, and the key value is "good_lock" 

String value = UUID.randomUUID().toString().replace("-",""); try{ 

// Add an expiration time to the key     

Boolean flag = template.opsForValue().setIfAbsent(REDIS_LOCK, value,10L,TimeUnit.SECONDS); 

// Failed to lock if(!flag){ return "Failed to grab the lock!"; }        

System.out.println( value+ " lock grabsuccess"); String result = template.opsForValue().get("goods:001");   int total = result == null ? 0 : Integer. parseInt(result);         if (total > 0) { 

// If you need to call other microservices here, the processing time will be longer. . . int realTotal = total - 1;      

template.opsForValue().set("goods:001", String.valueOf(realTotal));           

System.out.println("Purchased successfully, inventory remaining: " + realTotal + "pieces, service port is 8001");                                                                                                              ,  } else {       

 System.out.println("Failed to purchase goods, the service port is 8001"); } return "Failed to purchase goods, service port is 8001"; } finally { 

// Whoever adds the lock can delete it! ! ! !         

if(template.opsForValue().get(REDIS_LOCK).equals(value)){ template.delete(REDIS_LOCK); } } } }

This method solves the problem of releasing other people's locks because the service processing time is too long. Is that all right?

Copyright Description:No reproduction without permission。

Knowledge sharing community for developers。

Let more developers benefit from it。

Help developers share knowledge through the Internet。

Follow us

Recommended reading

high perspicacity