0%

SpringBoot分布式锁的实现

转码, 原文地址 https://mp.weixin.qq.com/s/WznBj2-zEtVBIbUqA5NQUQ

来源:https://urlify.cn/632yIv


前言

面试总是会被问到有没有用过分布式锁、redis 锁,大部分读者平时很少接触到,所以只能很无奈的回答 “没有”。本文通过 Spring Boot 整合 redisson 来实现分布式锁,并结合 demo 测试结果。

首先看下大佬总结的图

正文

添加依赖

1
2
3
4
5
6
7
8
9
10
11
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.10.6</version>
</dependency>

配置信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
spring:
# redis
redis:
host: 47.103.5.190
port: 6379
jedis:
pool:
# 连接池最大连接数(使用负值表示没有限制)
max-active: 100
# 连接池中的最小空闲连接
max-idle: 10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: -1
# 连接超时时间(毫秒)
timeout: 5000
#默认是索引为0的数据库
database: 0

配置类

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
/**
* redisson 配置,下面是单节点配置:
*
* @author gourd
*/
@Configuration
publicclassRedissonConfig{
@Value("${spring.redis.host}")
privateString host;
@Value("${spring.redis.port}")
privateString port;
@Value("${spring.redis.password:}")
privateString password;
@Bean
publicRedissonClient redissonClient() {
Config config = newConfig();
//单节点
config.useSingleServer().setAddress("redis://"+ host + ":"+ port);
if(StringUtils.isEmpty(password)) {
config.useSingleServer().setPassword(null);
} else{
config.useSingleServer().setPassword(password);
}
//添加主从配置
// config.useMasterSlaveServers().setMasterAddress("").setPassword("").addSlaveAddress(new String[]{"",""});
// 集群模式配置 setScanInterval()扫描间隔时间,单位是毫秒, //可以用"rediss://"来启用SSL连接
// config.useClusterServers().setScanInterval(2000).addNodeAddress("redis://127.0.0.1:7000", "redis://127.0.0.1:7001").addNodeAddress("redis://127.0.0.1:7002");
returnRedisson.create(config);
}
}

Redisson 工具类

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
/**
* redis分布式锁帮助类
*
* @author gourd
*
*/
publicclassRedisLockUtil{
privatestaticDistributedLocker distributedLocker = SpringContextHolder.getBean("distributedLocker",DistributedLocker.class);
/**
* 加锁
* @param lockKey
* @return
*/
publicstaticRLocklock(String lockKey) {
return distributedLocker.lock(lockKey);
}
/**
* 释放锁
* @param lockKey
*/
publicstaticvoid unlock(String lockKey) {
distributedLocker.unlock(lockKey);
}
/**
* 释放锁
* @param lock
*/
publicstaticvoid unlock(RLocklock) {
distributedLocker.unlock(lock);
}
/**
* 带超时的锁
* @param lockKey
* @param timeout 超时时间 单位:秒
*/
publicstaticRLocklock(String lockKey, int timeout) {
return distributedLocker.lock(lockKey, timeout);
}
/**
* 带超时的锁
* @param lockKey
* @param unit 时间单位
* @param timeout 超时时间
*/
publicstaticRLocklock(String lockKey, int timeout,TimeUnit unit ) {
return distributedLocker.lock(lockKey, unit, timeout);
}
/**
* 尝试获取锁
* @param lockKey
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
publicstaticboolean tryLock(String lockKey, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, TimeUnit.SECONDS, waitTime, leaseTime);
}
/**
* 尝试获取锁
* @param lockKey
* @param unit 时间单位
* @param waitTime 最多等待时间
* @param leaseTime 上锁后自动释放锁时间
* @return
*/
publicstaticboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
return distributedLocker.tryLock(lockKey, unit, waitTime, leaseTime);
}
/**
* 获取计数器
*
* @param name
* @return
*/
publicstaticRCountDownLatch getCountDownLatch(String name){
return distributedLocker.getCountDownLatch(name);
}
/**
* 获取信号量
*
* @param name
* @return
*/
publicstaticRSemaphore getSemaphore(String name){
return distributedLocker.getSemaphore(name);
}
}

底层封装

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
/**
* @author gourd
*/
publicinterfaceDistributedLocker{
RLocklock(String lockKey);
RLocklock(String lockKey, int timeout);
RLocklock(String lockKey, TimeUnit unit, int timeout);
boolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime);
void unlock(String lockKey);
void unlock(RLocklock);
}
/**
* @author gourd
*/
@Component
publicclassRedisDistributedLockerimplementsDistributedLocker{
@Autowired
privateRedissonClient redissonClient;
@Override
publicRLocklock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock();
returnlock;
}
@Override
publicRLocklock(String lockKey, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
returnlock;
}
@Override
publicRLocklock(String lockKey, TimeUnit unit ,int timeout) {
RLocklock= redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
returnlock;
}
@Override
publicboolean tryLock(String lockKey, TimeUnit unit, int waitTime, int leaseTime) {
RLocklock= redissonClient.getLock(lockKey);
try{
returnlock.tryLock(waitTime, leaseTime, unit);
} catch(InterruptedException e) {
returnfalse;
}
}
@Override
publicvoid unlock(String lockKey) {
RLocklock= redissonClient.getLock(lockKey);
lock.unlock();
}
@Override
publicvoid unlock(RLocklock) {
lock.unlock();
}
}

测试

模拟并发测试

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
/**
* redis分布式锁控制器
* @author gourd
* @since 2019-07-30
*/
@RestController
@Api(tags = "redisson", description = "redis分布式锁控制器")
@RequestMapping("/redisson")
@Slf4j
publicclassRedissonLockController{
/**
* 锁测试共享变量
*/
privateInteger lockCount = 10;
/**
* 无锁测试共享变量
*/
privateInteger count = 10;
/**
* 模拟线程数
*/
privatestaticint threadNum = 10;
/**
* 模拟并发测试加锁和不加锁
* @return
*/
@GetMapping("/test")
@ApiOperation(value = "模拟并发测试加锁和不加锁")
publicvoidlock(){
// 计数器
finalCountDownLatch countDownLatch = newCountDownLatch(1);
for(int i = 0; i < threadNum; i ++) {
MyRunnable myRunnable = newMyRunnable(countDownLatch);
Thread myThread = newThread(myRunnable);
myThread.start();
}
// 释放所有线程
countDownLatch.countDown();
}
/**
* 加锁测试
*/
privatevoid testLockCount() {
String lockKey = "lock-test";
try{
// 加锁,设置超时时间2s
RedisLockUtil.lock(lockKey,2, TimeUnit.SECONDS);
lockCount--;
log.info("lockCount值:"+lockCount);
}catch(Exception e){
log.error(e.getMessage(),e);
}finally{
// 释放锁
RedisLockUtil.unlock(lockKey);
}
}
/**
* 无锁测试
*/
privatevoid testCount() {
count--;
log.info("count值:"+count);
}
publicclassMyRunnableimplementsRunnable{
/**
* 计数器
*/
finalCountDownLatch countDownLatch;
publicMyRunnable(CountDownLatch countDownLatch) {
this.countDownLatch = countDownLatch;
}
@Override
publicvoid run() {
try{
// 阻塞当前线程,直到计时器的值为0
countDownLatch.await();
} catch(InterruptedException e) {
log.error(e.getMessage(),e);
}
// 无锁操作
testCount();
// 加锁操作
testLockCount();
}
}
}

调用接口后打印值:

测试结果

根据打印结果可以明显看到,未加锁的 count– 后值是乱序的,而加锁后的结果和我们预期的一样。

由于条件问题没办法测试分布式的并发。只能模拟单服务的这种并发,但是原理是一样,希望对大家有帮助。如有错误之处,欢迎指正。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如有文章对你有帮助,

“在看”和转发是对我最大的支持!







关注Java开发宝典

每天学习Java技术







点赞是最大的支持
-------------本文结束感谢您的阅读-------------
请我吃辣条吧~~ 谢谢打赏!