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.
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 .
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"; } }
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!
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.
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.
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