基于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>
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
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 }
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
欢迎点赞关注和收藏
强者自救 圣者渡人