基于Redis的SETNX实现分布式锁

原创转载请注明出处:https://www.cnblogs.com/agilestyle/p/13200032.html

原理

SET key value [NX] [XX] [EX <seconds>] [PX [millseconds]]

必选参数说明 

  • SET:命令
  • key:待设置的key
  • value: 设置的key的value

可选参数说明

  • NX:表示key不存在才设置,如果存在则返回NULL
  • XX:表示key存在时才设置,如果不存在则返回NULL
  • EX seconds:设置过期时间,过期时间精确为秒
  • PX millseconds:设置过期时间,过期时间精确为毫秒

以上set 代替了 setnx + expire 需要分2次执行命令操作的方式,保证了原子性。

如果setnx 返回ok 说明拿到了锁;如果setnx 返回 nil,说明拿锁失败,被其他线程占用。

Project Directory

Maven Dependency

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.8.RELEASE</version>
        <relativePath/>
    </parent>

    <modelVersion>4.0.0</modelVersion>

    <groupId>org.fool.redis</groupId>
    <artifactId>redis-lock-setnx</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-redis</artifactId>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>
View Code

src/main/resources

application.properties

spring.application.name=redis-lock-setnx
server.port=8888

logging.level.org.fool.redis=debug

spring.redis.host=localhost
spring.redis.port=6379
spring.redis.database=0
spring.redis.password=
spring.redis.timeout=2000
spring.redis.pool.max-active=10
spring.redis.pool.max-wait=1000
spring.redis.pool.max-idle=10
spring.redis.pool.min-idle=5
spring.redis.pool.num-tests-per-eviction-run=1024
spring.redis.pool.time-between-eviction-runs-millis=30000
spring.redis.pool.min-evictable-idle-time-millis=60000
spring.redis.pool.soft-min-evictable-idle-time-millis=10000
spring.redis.pool.test-on-borrow=true
spring.redis.pool.test-while-idle=true
spring.redis.pool.block-when-exhausted=false
View Code

src/main/java

Application.java

 1 package org.fool.redis;
 2 
 3 import org.springframework.boot.SpringApplication;
 4 import org.springframework.boot.autoconfigure.SpringBootApplication;
 5 
 6 @SpringBootApplication
 7 public class Application {
 8     public static void main(String[] args) {
 9         SpringApplication.run(Application.class, args);
10     }
11 }

JsonUtils.java

 1 package org.fool.redis.controller;
 2 
 3 import com.fasterxml.jackson.core.JsonProcessingException;
 4 import com.fasterxml.jackson.databind.JavaType;
 5 import com.fasterxml.jackson.databind.ObjectMapper;
 6 
 7 import java.util.List;
 8 import java.util.Map;
 9 
10 public class JsonUtils {
11     private static final ObjectMapper MAPPER = new ObjectMapper();
12 
13     public static String objectToJson(Object data) {
14         try {
15             String string = MAPPER.writeValueAsString(data);
16             return string;
17         } catch (JsonProcessingException e) {
18             e.printStackTrace();
19         }
20         return null;
21     }
22 
23     public static <T> T jsonToPojo(String jsonData, Class<T> beanType) {
24         try {
25             T t = MAPPER.readValue(jsonData, beanType);
26             return t;
27         } catch (Exception e) {
28             e.printStackTrace();
29         }
30 
31         return null;
32     }
33 
34     public static <T> List<T> jsonToList(String jsonData, Class<T> beanType) {
35         JavaType javaType = MAPPER.getTypeFactory().constructParametricType(List.class, beanType);
36 
37         try {
38             List<T> list = MAPPER.readValue(jsonData, javaType);
39             return list;
40         } catch (Exception e) {
41             e.printStackTrace();
42         }
43 
44         return null;
45     }
46 
47     public static <K, V> Map<K, V> jsonToMap(String jsonData, Class<K> keyType, Class<V> valueType) {
48         JavaType javaType = MAPPER.getTypeFactory().constructMapType(Map.class, keyType, valueType);
49 
50         try {
51             Map<K, V> map = MAPPER.readValue(jsonData, javaType);
52             return map;
53         } catch (Exception e) {
54             e.printStackTrace();
55         }
56 
57         return null;
58     }
59 }
View Code

OrderRequest.java

 1 package org.fool.redis.controller;
 2 
 3 import lombok.Data;
 4 
 5 @Data
 6 public class OrderRequest {
 7     private Integer id;
 8     private Integer productId;
 9     private Integer price;
10     private Integer userId;
11     private Integer tradeId;
12     private Integer tradeStatus;
13 }

OrderController.java

 1 package org.fool.redis.controller;
 2 
 3 import lombok.extern.slf4j.Slf4j;
 4 import org.springframework.beans.factory.annotation.Autowired;
 5 import org.springframework.data.redis.core.StringRedisTemplate;
 6 import org.springframework.util.DigestUtils;
 7 import org.springframework.web.bind.annotation.PostMapping;
 8 import org.springframework.web.bind.annotation.RequestBody;
 9 import org.springframework.web.bind.annotation.RestController;
10 
11 import java.util.concurrent.TimeUnit;
12 
13 @RestController
14 @Slf4j
15 public class OrderController {
16 
17     @Autowired
18     private StringRedisTemplate stringRedisTemplate;
19 
20     @PostMapping(value = "/createOrder", produces = "application/json;charset=utf-8")
21     public String createOrder(@RequestBody OrderRequest request) {
22 
23         String json = JsonUtils.objectToJson(request);
24         String md5 = DigestUtils.md5DigestAsHex(json.getBytes()).toUpperCase();
25 
26         /*
27          * setIfAbsent <=> SET key value [NX] [XX] [EX <seconds>] [PX [millseconds]]
28          * set expire time 5 mins
29          */
30         Boolean flag = stringRedisTemplate.opsForValue().setIfAbsent(md5, "1", 60 * 5, TimeUnit.SECONDS);
31         if (flag) {
32             // lock success, start to handle business
33             log.debug("{} lock success, start to handle business", md5);
34             try {
35                 //mock to handle business
36                 Thread.sleep(1000 * 10);
37             } catch (InterruptedException e) {
38                 e.printStackTrace();
39             }
40             //end to handle business, need to unlock
41             stringRedisTemplate.delete(md5);
42             log.debug("{} unlock success,end to handle business", md5);
43             return "SUCCESS";
44         } else {
45             log.debug("{} lock failure", md5);
46             return "try again later";
47         }
48     }
49 }

Test

Console Output

猜你喜欢

转载自www.cnblogs.com/agilestyle/p/13200032.html