redis分布式锁
# Redis分布式锁
# 面试题
- Redis除了做缓存,你还见过Redis的什么用法?
- Redis做分布式锁要注意什么问题?
- 如果Redis是单点部署,会带来什么问题?
- 集群模式下,比如主从模式,会有什么问题?
- 你的简历上有写redisson,介绍一下RedLock吧?
- Redis分布式锁续命,看门狗知道吗?
# 什么是分布式锁
- JVM层面的锁,是单机锁。
- 分布式微服务架构,拆分各个微服务之间为了避免冲突和数据故障而加入的一种锁。
- 分布式锁方案:zookeeper,mysql,redis[推荐] -redlock -- redisson lock/unlock
# 超卖案例
# 场景:多个服务之间,同一时刻,同一个用户只能有一个请求,防止关键业务数据冲突和并发错误
# 建module
- boot-redis01
- boot-redis02
# 改pom
- pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.4</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.jjc</groupId>
<artifactId>boot-redis01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>boot-redis01</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!--web + actuator-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!--SpringBoot与redis整合-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<!--jedis-->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>3.1.0</version>
</dependency>
<!--springboot-aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.15.1</version>
</dependency>
<!--一般通用-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
# 改yaml/properties
- application.properties
# 第一个模块的配置,第二个修改端口号就好9999
server.port=8888 /
spring.redis.database=0
spring.redis.host=127.0.0.1
spring.redis.port=6379
spring.redis.password=
spring.redis.lettuce.pool.max-active=8
spring.redis.lettuce.pool.max-wait=-1
spring.redis.lettuce.pool.max-idle=8
spring.redis.lettuce.pool.min-idle=0
1
2
3
4
5
6
7
8
9
10
2
3
4
5
6
7
8
9
10
# RedisConfig配置类
- RedisConfig.java
@Configuration
public class RedisConfig{
@Bean
public RedisTemplate<String, Serializable> redisTemplate(LettuceConnectionFactory connectionFactory){
RedisTemplate<String,Serializable> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(connectionFactory);
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
@Bean
public Redisson redisson(){
Config config = new Config();
config.useSingleServer().setAddress("redis://127.0.0.1:6379").setDatabase(0);
return (Redisson)Redisson.create(config);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# JedisUtils
- JedisUtils.java
public class RedisUtils {
private static JedisPool jedisPool;
static{
JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
jedisPoolConfig.setMaxTotal(20);
jedisPoolConfig.setMaxIdle(10);
jedisPool = new JedisPool(jedisPoolConfig,"127.0.0.1",6379);
}
public static Jedis getJedis() throws Exception {
if(null != jedisPool){
return jedisPool.getResource();
}
throw new Exception("Jedispool is not ok");
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# GoodsController
- GoodsController.java
@RestController
public class GoodController{
@Autowired
private StringRedisTemplate stringRedisTemplate;
@Autowired
private Redisson redisson;
@Value("${server.port}")
private String serverPort;
final Lock lock = new ReentrantLock();
public static final String REDIS_LOCK = "REDIS_LOCK";
@GetMapping("/buy_Goods")
public String buy_Goods() throws Exception {
//1.0
//没有锁
//2.0 加单机锁
//synchronized lock
//3.0 由于是分布式,单机锁,会超卖
//stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK);
//stringRedisTemplate.delete(REDIS_LOCK);
//4.0
//stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK);
//finally{
// stringRedisTemplate.delete(REDIS_LOCK);
//}
//5.0 有服务器可能宕机,需要设置超时
//stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK);stringRedisTemplate.expire(REDIS_LOCK,10,TimeUnit.SECONDS);stringRedisTemplate.delete(REDIS_LOCK);
//6.0 加锁和设置超时不是原子操作,需要设置为原子操作
//stringRedisTemplate.opsForValue().setIfAbsent(REDIS_LOCK,value,10,TimeUnit.SECONDS);stringRedisTemplate.delete(REDIS_LOCK);
//7.0 可能会出现A删B的锁,需要判断一下
//if(value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK))){
// stringRedisTemplate.delete(REDIS_LOCK);
//}
//8.0 判断锁和删除操作不是原子操作,需要改为原子操作
//8.1 使用redis事务
//while(true){
// stringRedisTemplate.watch(REDIS_LOCK);
// if(value.equalsIgnoreCase(stringRedisTemplate.opsForValue().get(REDIS_LOCK))){
// stringRedisTemplate.setEnableTransactionSupport(true);
// stringRedisTemplate.multi();
// stringRedisTemplate.delete(REDIS_LOCK);
// List<Object> list = stringRedisTemplate.exec();
// if(null == list){
// continue;
// }
// }
// stringRedisTemplate.unwatch();
// break;
//}
//8.2 使用lua脚本
//Jedis jedis = RedisUtils.getJedis();
//String script = "if redis. call(' get', KEYS[1])==ARGV[1]" +
// "then" +
// "return redis. call(' del', KEYS[1])" +
// "else" +
// "return" +
// "end";
//try{
// Object o = jedis.eval(script, Collections.singletonList(REDIS_LOCK),Collections.singletonList(value));
// if("1".equals(o.toString())){
// System.out.println("----del redis lock ok");
// }else{
// System.out.println("----del redis lock error");
// }
//}finally {
// if(null != jedis){
// jedis.close();
// }
//}
//9.0 使用redisson进行缓存续命
//RLock redissonLock = redisson.getLock(REDIS_LOCK);
//redissonLock.lock();
//if(redissonLock.isLocked()){
// if(redissonLock.isHeldByCurrentThread()){
// redissonLock.unlock();
// }
//}
String value = UUID.randomUUID().toString() + Thread.currentThread().getName();
RLock redissonLock = redisson.getLock(REDIS_LOCK);
redissonLock.lock();
try {
//查看库存数量
String result = stringRedisTemplate.opsForValue().get("goods:001");
int goodsNumber = result == null ? 0 : Integer.parseInt(result);
//卖商品
if(goodsNumber > 0){
int realNumber = goodsNumber - 1;
//成功买入,库存减少一件
stringRedisTemplate.opsForValue().set("goods:001",String.valueOf(realNumber));
System.out.println("成功买入商品,库存还剩下:"+realNumber+"服务端口:"+serverPort);
return "成功买入商品,库存还剩下:"+realNumber+"服务端口:"+serverPort;
}else{
System.out.println("商品卖完"+"服务端口:"+serverPort);
return "商品卖完!"+"服务端口:"+serverPort;
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if(redissonLock.isLocked()){
if(redissonLock.isHeldByCurrentThread()){
redissonLock.unlock();
}
}
}
return null;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
# Redis数据
# 放入001库存100个
set goods:001 100
1
2
2
编辑 (opens new window)
上次更新: 2024/05/24, 11:36:46