在之前我介绍过如何保证缓存与数据库的双写一致性,说过使用分布式锁优化可以实现双写一致性,那么今天就来介绍一种具体实现方式redisson-分布式锁
原理
原理图如下:
![图片[1]-redisson-分布式锁入门-编程社](https://cos.bianchengshe.com/wp-content/uploads/2024/04/image-34.png?imageMogr2/format/webp/interlace/1/quality/100)
主要分析如下:
何时加锁?
什么是WatchDog自动延期机制?
- 在一个分布式环境下,假如一个线程获得锁后,突然服务器宕机了,那么这个时候在一定时间后这个锁会自动释放,你也可以设置锁的有效时间(不设置默认30秒),这样的目的主要是防止死锁的发生
- 线程A业务还没有执行完,时间就过了,线程A 还想持有锁的话,就会启动一个watch dog后台线程,不断的延长锁key的生存时间
lua脚本
- 主要是如果你的业务逻辑复杂的话,通过封装在lua脚本中发送给redis,而且redis是单线程的,这样就保证这段复杂业务逻辑执行的原子性
基本使用
RLock是继承Lock锁,所以他有Lock锁的所有特性,比如lock、unlock、trylock等特性,同时它还有很多新特性:强制锁释放,带有效期的锁
public interface RLock {
//----------------------Lock接口方法-----------------------
/**
* 加锁 锁的有效期默认30秒
*/
void lock();
/**
* 加锁 可以手动设置锁的有效时间
*
* @param leaseTime 锁有效时间
* @param unit 时间单位 小时、分、秒、毫秒等
*/
void lock(long leaseTime, TimeUnit unit);
/**
* tryLock()方法是有返回值的,用来尝试获取锁,
* 如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false .
*/
boolean tryLock();
/**
* tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,
* 只不过区别在于这个方法在拿不到锁时会等待一定的时间,
* 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
*
* @param time 等待时间
* @param unit 时间单位 小时、分、秒、毫秒等
*/
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
/**
* 比上面多一个参数,多添加一个锁的有效时间
*
* @param waitTime 等待时间
* @param leaseTime 锁有效时间
* @param unit 时间单位 小时、分、秒、毫秒等
*/
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
/**
* 解锁
*/
void unlock();
}
lock方法
**lock()**:此方法为加锁,但是锁的有效期采用默认30秒,如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制,如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
@GetMapping("/lock")
public String lock() {
//获得分布式锁对象,注意,此时还没有加锁成功
RLock lock = redissonClient.getLock("mylock");
try {
//加锁:锁的有效期默认30秒
lock.lock();
long timeToLive=lock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S",Thread.currentThread().getName(),timeToLive/1000);
//休眠一下
Thread.sleep(2000);
}catch (Exception ex){
System.out.println("出现异常!!!");
}finally {
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
return "OK";
}
注意:加锁:锁的有效期默认30秒
lock(long leaseTime, TimeUnit unit): 可以手动设置锁的有效时间,如果主线程未释放,且当前锁未调用unlock方法,则锁到期后会自动释放,如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
@GetMapping("/lockLaseTime")
public String lockLaseTime() {
//获得分布式锁对象,注意,此时还没有加锁成功
RLock lock = redissonClient.getLock("lockLaseTime");
try {
//这里可以手动设置锁的有效时间,锁到期后会自动释放的
lock.lock(10,TimeUnit.SECONDS);
long timeToLive=lock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S",Thread.currentThread().getName(),timeToLive/1000);
//休眠一下
Thread.sleep(2000);
}catch (Exception ex){
System.out.println("出现异常!!!");
}finally {
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
return "OK";
}
tryLock()方法
tryLock(): 用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制,如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
@GetMapping("/tryLock")
public String tryLock() {
//获得分布式锁对象,注意,此时还没有加锁成功
RLock lock = redissonClient.getLock("tryLock");
try {
//如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false .
boolean flag=lock.tryLock();
if(flag){
long timeToLive=lock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S,加锁状态:{}",Thread.currentThread().getName(),timeToLive/1000,flag);
//休眠一下
Thread.sleep(2000);
}
}catch (Exception ex){
System.out.println("出现异常!!!");
}finally {
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
return "OK";
}
tryLock(long time, TimeUnit unit): tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间, 在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true,如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
@GetMapping("/tryLockWaitTime")
public String tryLockWaitTime() {
//获得分布式锁对象,注意,此时还没有加锁成功
RLock lock = redissonClient.getLock("tryLockWaitTime");
try {
//只不过区别在于这个方法在拿不到锁时会等待一定的时间,
//在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
boolean flag=lock.tryLock(6, TimeUnit.SECONDS);
if(flag){
long timeToLive=lock.remainTimeToLive();
log.info("线程:{},获得锁,锁存活时间:{}S,加锁状态:{}",Thread.currentThread().getName(),timeToLive/1000,flag);
//休眠一下
Thread.sleep(2000);
}
}catch (Exception ex){
System.out.println("出现异常!!!");
}finally {
//如果主线程未释放,且当前锁未调用unlock方法,则进入到watchDog机制
//如果主线程未释放,且当前锁调用unlock方法,则直接释放锁
log.info("线程:{},释放锁",Thread.currentThread().getName());
}
return "OK";
}
注意:tryLock(long time, TimeUnit unit),这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true
SpringBoot项目依赖
<!--redis-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
<version>2.4.0</version>
</dependency>
<!--使用redisson作为分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.8</version>
</dependency>
配置类和配置文件
- 配置文件
spring:
redis:
# Redis数据库索引(默认为0)
database: 0
# Redis服务器地址
host: 127.0.0.1
# Redis服务器连接端口
port: 6379
# Redis服务器链接密码(默认为空)
password:
jedis:
pool:
# 连接池最大链接数(负值表示没有限制)
max-active: 20
# 连接池最大阻塞等待时间(负值表示没有限制)
max-wait: -1
# 链接池中最大空闲链接
max-idle: 10
# 连接池中最小空闲链接
min-idle: 0
# 链接超市时间(毫秒)
timeout: 1000
server:
port: 8899
- 配置类
@Configuration
public class RedissonConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
@Bean
public RedissonClient redissonClient(){
Config config = new Config();
config.useSingleServer().setAddress("redis://" + host + ":" + port);
final RedissonClient client = Redisson.create(config);
return client;
}
}
暂无评论内容