Springboot整合Redission分布式锁
引言:实际项目中,我们经常会遇到一些需要考虑使用分布式锁的场景,以防止页面重复请求或者多系统之间相互重复调用的产生业务偏差的问题;
例如:
1.并发的场景下,生成订单需要进行使用分布式锁来锁定商品库存,避免出现超卖情况。
2.定时任务,部署多个服务,存在某些任务被同时执行的情况;
一、springboot引入redission依赖;
<!-- redis 分布式锁 -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.5.0</version>
</dependency>
二、配置redis链接信息:实际各配置信息根据项目具体情况而定
##默认密码为空
redis:
host: 127.0.0.1
port: 6379
jedis:
pool:
#连接池最大连接数(使用负值表示没有限制)
max-active: 100
# 连接池中的最小空闲连接
max-idle: 10
# 连接池最大阻塞等待时间(使用负值表示没有限制)
max-wait: 100000
timeout: 5000
database: 1
三、RedissonClient客户端配置(单机模式)
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* 单机模式分布式锁配置
*/
@Configuration
public class RedissionConfig {
@Value("${redis.host}")
private String redisHost;
@Value("${redis.port}")
private String redisPort;
@Value("${redis.database}")
private int database;
@Bean(destroyMethod = "shutdown")
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://" + redisHost + ":" + redisPort)
.setDatabase(database);
return Redisson.create(config);
}
}
四、测试
4.1 lockTest1方法对锁进行锁定,让现场睡眠1分钟,模拟业务一直占用锁的场景;
4.2 lockTest2 在业务1执行时候尝试去获取锁,看是否能获取到锁,同时等30秒后锁过期再去获取锁,是否可以正常获取到锁;
@Resource
private RedissonClient redissonClient;
@ApiOperation(value = "分布式可重入锁测试1", notes = "分布式锁测试1")
@PostMapping("/lockTest1")
Result lockTest1(@RequestBody RedisRequestDto dto){
RLock lock = redissonClient.getLock(dto.getKey());
String message = "";
try {
// lock.lock(); 会有看门狗机制,默认30秒;
// lock.lock();
// 此处采用手动设置过期时间,不会触发看门狗机制
boolean locked = lock.tryLock(30, 60, TimeUnit.SECONDS);
if(locked){
message = "lockTest1 == 锁成功了";
log.info(message);
}else {
message = "lockTest1 == 1锁失败了";
log.info(message);
}
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
if(Objects.nonNull(lock) && lock.isHeldByCurrentThread()){
log.info("{}锁被释放",dto.getKey());
lock.unlock();
}
}
return Result.ok(message);
}
@ApiOperation(value = "分布式可重入锁测试2", notes = "分布式锁测试2")
@PostMapping("/lockTest2")
Result lockTest2(@RequestBody RedisRequestDto dto){
RLock lock = redissonClient.getLock(dto.getKey());
try {
boolean isLocked = lock.isLocked();
if(isLocked){
log.info("{}锁被占用,请稍后",dto.getKey());
return Result.ok("锁占用,请稍后");
}else {
log.info("{}锁未占用,继续执行",dto.getKey());
return Result.ok("锁未占用,继续执行");
}
} catch (Exception e) {
e.printStackTrace();
}finally {
if(Objects.nonNull(lock) && lock.isHeldByCurrentThread()){
lock.unlock();
}
}
return Result.ok("");
}
测试结果:
请求参数如下:
1.lockTest1 执行以后,我们可以看到有在db1中有一条记录,同时返回锁成功;
2.lockTest2 执行时候就是锁占用,证明“test_lock”已经被占用,无法获取;
3.等待30秒以后,test_lock锁自动过期,再次执行lockTest2,可以正常获取到锁;
通过日志,我们也能看出整个锁的锁定和释放的情况;
2024-01-15 15:15:13.560 INFO 191576 --- [nio-1012-exec-1] c.s.house.controller.RedisController : lockTest1 == 锁成功了
2024-01-15 15:15:16.960 INFO 191576 --- [nio-1012-exec-3] c.s.house.controller.RedisController : test_lock锁被占用,请稍后
2024-01-15 15:16:24.566 INFO 191576 --- [nio-1012-exec-5] c.s.house.controller.RedisController : test_lock锁未占用,继续执行
除此以外:redisson还有一些其它类型的分布式锁,下面举例栅栏锁;
@ApiOperation(value = "分布式计数(班长锁门)", notes = "分布式计数(班长锁门)")
@PostMapping("/lockDoor")
Result lockDoor(){
RCountDownLatch lockDoor = redissonClient.getCountDownLatch("lockDoor");
lockDoor.trySetCount(3);
log.info("有3个人,等待离开");
try {
lockDoor.await();
} catch (InterruptedException e) {
log.info("lockDoor has error : {}",e);
}
log.info("人走完了,可以锁门了");
return Result.ok("人走完了,可以锁门了");
}
@ApiOperation(value = "分布式计数(同学离开)", notes = "分布式计数(同学离开)")
@PostMapping("/studentGo")
Result studentGo() {
RCountDownLatch lockDoor = redissonClient.getCountDownLatch("lockDoor");
lockDoor.countDown();
log.info("走了一个同学");
return Result.ok("走了一个同学");
}
先执行lockDoor方法,设置3个计数,只有当学生全部离开教室才可以锁门;当调用3次studentGo方法以后,通过日志我们可以看到锁释放完成;
日志如下:
2024-01-18 17:21:18.036 INFO 452100 --- [nio-1012-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 8 ms
2024-01-18 17:21:18.075 INFO 452100 --- [nio-1012-exec-1] c.s.house.controller.RedisController : 有3个人,等待离开
2024-01-18 17:21:24.105 INFO 452100 --- [nio-1012-exec-2] c.s.house.controller.RedisController : 走了一个同学
2024-01-18 17:21:25.068 INFO 452100 --- [nio-1012-exec-3] c.s.house.controller.RedisController : 走了一个同学
2024-01-18 17:21:29.354 INFO 452100 --- [nio-1012-exec-5] c.s.house.controller.RedisController : 走了一个同学
2024-01-18 17:21:29.361 INFO 452100 --- [nio-1012-exec-1] c.s.house.controller.RedisController : 人走完了,可以锁门了