一、引入依赖
xml
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.16.0</version>
</dependency>
二、配置类
java
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* @Author ZGM
* @DateTime 2023/8/15
* @description
*/
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient(){
// 配置,
Config config = new Config();
//单例模式
config.useSingleServer().setAddress("redis://127.0.0.1:6379");
// .setPassword("123456");
//修改看门狗的默认时间30s到60s
//config.setLockWatchdogTimeout(60000);
// 创建RedissonClient对象
return Redisson.create(config);
}
//这个只针对转化为string类型
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate();
template.setConnectionFactory(redisConnectionFactory);
//默认是JDK序列化 发现key和value前面都多了一串特殊字符
StringRedisSerializer stringRedisSerializer = new StringRedisSerializer();
template.setKeySerializer(stringRedisSerializer);
template.setValueSerializer(stringRedisSerializer);
return template;
}
}
三、实现类
java
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* @Author ZGM
* @DateTime 2023/8/15
* @description
*/
@Service
@Slf4j
public class UserService {
@Autowired
private RedissonClient redissonClient;
@Autowired
RedisTemplate<String, Object> redisTemplate;
public void test1() throws InterruptedException {
//定义一个key
String key = UUID.randomUUID().toString();
// 占锁 没有拿到锁的会自动阻塞
// watchDog 机制 : 锁自动加了默认30秒过期
// 如果业务代码耗时长,锁也会自动续期
RLock lock = redissonClient.getLock(key);
//只有lock()和tryLock(5000, TimeUnit.MILLISECONDS)会触发看门狗机制
Boolean isLocked = lock.tryLock(5000, TimeUnit.MILLISECONDS);
if(!isLocked) {
log.info("获取锁失败");
}
long a = System.currentTimeMillis();
try {
Thread.sleep(60000);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
long b = System.currentTimeMillis();
System.out.println(b-a);
}
}
四、测试结果
可以下载redis desktop manager软件来查看redis里面存放的东西
红色框内的TTL值就是过期时间,默认-1,表示永不过期,指定过期时间后就变成你指定的值了。
上面的方法,我们让线程睡眠60S,代表我们的业务执行时间,在调用这个方法时,我们可以在
redis desktop manager软件上实时查看锁的过期时间,第一次过期时间为30S,10S后刷新过期时间为30S,也就是说,它每隔10S会检验这个锁是否释放,没有的话,会一直给你刷新锁的持有时间变为30S,直到任务完成。
五、看门狗机制
上面所说的自动刷新锁的持有时间,就是通过这个看门狗机制来实现的。它默认的锁的持有时间是30S,每隔30/3也就是10S会刷新锁的持有时间,我们也可以通过redisson的Config 类的config.setLockWatchdogTimeout(60000)来修改过期时间。实际上锁的持有时间就是lockWatchdogTimeout的值,只不过默认设置的30S而已,同样的,锁的刷新时间也是每隔lockWatchdogTimeout/3秒执行一次,如果设置的时间为60S,就是锁的过期时间为60S,每隔20S执行一次。
所谓的自动刷新,其实是在获取锁的时候,开了一个线程来监控这个锁,我们先按照默认的30S过期时间来计算,假设当前业务的执行时间在10S之内,在第10S时此线程监控到该锁已被正常释放,则不会刷新锁的过期时间,反之,在第10S时此线程监控到该锁仍被此线程持有,那也就是业务还未执行完,它就会帮你刷新过期时间。即使redis的服务器宕机了,也不会出现死锁,因为当它监控到异常时,也不会刷新过期时间,当redis的服务器恢复时,自动就把它删除了。
六、注意
注意:只有只有lock()和tryLock(waitTime, TimeUnit.MILLISECONDS)会触发看门狗机制,在这两个方法里我们都没有指定releaseTime,它默认值就是-1,然后才能自动触发看门狗机制,或者我们在调用获取锁的方法时直接指定releaseTime为-1,这样也可以触发看门狗机制,一定要注意这一点。
lock()会一直尝试获取锁,知道成功
tryLock(long time, TimeUnit unit),同样会一直尝试获取锁,但它有等待时间,超过这个时间就直接返回false,不在继续调用,我比较喜欢这个方法。
七、建议
看门狗机制虽然可以自动刷新锁的过期时间,用起来也非常方便,但并不是说,所有的方法,都应该开启此机制。因为启动此机制的同时,意味着会额外开启一个线程来监控它,那么就会占用CPU内存。少量线程的情况下,这部分内存占用可以忽略,当请求过多时,就占用比略高了,只能增加服务器,也就是增加成本了。
所以我建议是,在获取资源的方法里,不开启看门狗机制,比如,一个接口是返回个人信息的,那么我在调用时,由于某种原因导致锁已经释放,但业务还未执行完成,那么会报错,
信息如下:[Request processing failed; nested exception is java.lang.IllegalMonitorStateException: attempt to unlock lock, not locked by current thread by node id: 62e61f35-6cd5-4fc2-849e-78ad649435e4 thread-id: 72] with root cause
,我们直接捕获此异常,返回系统繁忙即可。
在更新或者保存资源的方法里,是可以开启看门狗机制的,这样虽然执行时间会稍微长一些,但最终会完成任务,不至于让用户重复操作。