我想我们用到 Redisson 最多的场景一定是分布式锁,一个基础的分布式锁具有三个特性:
互斥:在分布式高并发的条件下,需要保证,同一时刻只有有一个线程获得锁,这是最基本的一点。
防止死锁:在分布式高并发的条件下,比如有个线程获得锁的同时,还没有来的及去释放锁,就因为系统故障或者其它原因使它无法执行释放锁的命令,导致其它线程都无法获得锁,造成死锁。
可重入:我们知道 ReentrantLock 是可重入锁,那它的特点就是同一个线程可以重复拿到同一个资源的锁。
org.redisson
redisson-spring-boot-starter
3.14.0
spring:
redis:
host: 127.0.0.1
port: 6379
timeout: 10000
password:
lettuce:
pool:
#最大连接数据库连接数,设 0 为没有限制
max-active: 8
#最大建立连接等待时间。如果超过此时间将接到异常。设为-1表示无限制。
max-wait: 1000
#最大等待连接中的数量,设 0 为没有限制
max-idle: 500
#最小等待连接中的数量,设 0 为没有限制
min-idle: 300
jedis:
pool:
max-active: 8
max-wait: 1000
max-idle: 500
min-idle: 300
@Configuration
public class RedissonConfig {
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private String port;
/**
* 对 Redisson 的使用都是通过 RedissonClient 对象
* @return
*/
@Bean(name = "redissonClient", destroyMethod = "shutdown") // 服务停止后调用 shutdown 方法
public RedissonClient redissonClient() {
// 1、创建配置
Config config = new Config();
// 2、集群模式
// config.useClusterServers().addNodeAddress("127.0.0.1:7004", "127.0.0.1:7001");
// 根据 Config 创建出 RedissonClient 示例
config.useSingleServer()
.setPassword(StringUtils.isEmpty(password) ? null : password)
.setAddress(host.contains("://") ? "" : "redis://" + host + ":" + port);
return Redisson.create(config);
}
}
@Autowired
private RedissonClient redissonClient;
@Test
public void test() {
System.out.println(redissonClient); // org.redisson.Redisson@40a8a26f
}
用锁的一般步骤:
Redisson 提供很多种类型的锁,其中最常用的就是可重入锁(Reentrant Lock)了。
RLock lock = redissonClient.getLock(String lockName);
获取的锁实例实现了 RLock 接口,而该接口拓展了 JUC 包中的 Lock 接口,以及异步锁接口 RLockAsync。
从同步与异步特性来区分,加锁方法可分为同步加锁和异步加锁两类。异步加锁方法的名称一般是在相应的同步加锁方法后加上“Async”后缀。
从阻塞与非阻塞特性来区分,加锁方法可分为阻塞加锁和非阻塞加锁两类。非阻塞加锁方法的名称一般是“try”开头。
下面以比较常用的同步加锁方法来说明加锁的一些细节。
//创建锁
RLock helloLock = redissonClient.getLock("hello");
//加锁
helloLock.lock();
try {
log.info("locked");
Thread.sleep(1000 * 10);
} finally {
//释放锁
helloLock.unlock();
}
log.info("finished");
String key ="product:001";
RLock lock = redisson.getLock(key);
try {
boolean res = lock.tryLock(10,TimeUnit.SECONDS);
if ( res){
System.out.println("这里是你的业务代码");
} else {
System.out.println("系统繁忙");
}
} catch (Exception e){
e.printStackTrace();
} finally {
lock.unlock();
}
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
解读:尝试加锁,最多等待 100 秒,上锁以后 10 s自动解锁,没有 watch dog 机制。
void unlock():释放锁。如果当前线程是锁的持有者(即在该锁实例上加锁成功的线程),则会释放成功,否则会抛出异常。
/**
* 异步锁
*/
lock = redissonClient.getLock("erbadagang-lock");
Future res = null;
try {
// lock.lockAsync();
// lock.lockAsync(100, TimeUnit.SECONDS);
res = lock.tryLockAsync(3, 100, TimeUnit.SECONDS);
if (res.get()) {
System.out.println("这里是你的Async业务代码");
} else {
System.out.println("系统繁忙Async");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (res.get()) {
lock.unlock();
}
}
log.info("finished");
String lockName = ...
RLock lock = redissonClient.getLock(lockName);
// 阻塞式加锁
lock.lock();
try {
// 操作受锁保护的资源
} finally {
// 释放锁
lock.unlock();
}
String lockName = ...
RLock lock = redissonClient.getLock(lockName);
if (lock.tryLock()) {
try {
// 操作受锁保护的资源
} finally {
lock.unlock();
}
} else {
// 执行其他业务操作
}
优秀的分布式锁需要具备以下特性:
Redisson的分布式锁除了实现上述几个特性外,还具有锁的自动续期功能。即当我们加锁而未指定锁的有效时长时,Redisson会按一定的周期,定时检查当前线程是否活跃,若是则自动为锁续期,这一特性称为watchdog(看门狗)机制。
有了这个特性,我们就可以不必为设定锁的有效时间而纠结了(设得太长,则会在客户端崩溃后仍长时间占有锁;设得太短,则可能在业务逻辑执行完成前,锁自动释放),Redisson分布式锁可以在客户端崩坏时自动释放,业务逻辑未执行完时自动续期。
// 拿锁失败时,会不停的重试
RLock lock = redissonClient.getLock("guodong");
// 具有 watch Dog 自动延期机制,默认续 30 s
lock.lock();
// 尝试拿锁10s后停止重试,返回false,具有 watch dog 自动延期机制默认续30s
boolean res1 = lock.tryLock(10, TimeUnit.SECONDS);
// 没有 watch dog,10s后自动释放
lock.lock(10, TimeUnit.SECONDS);
// 尝试拿锁100s后停止重试,返回false,没有 watch Dog,10s后自动释放
boolean res2 = lock.tryLock(100, 10, TimeUnit.SECONDS);
Thread.sleep(40000L);
lock.unlock();
如果拿到分布式锁的节点宕机,且这个锁正好处于锁住的状态时,会出现锁死的状态,为了避免这种情况的发生,锁都会设置一个过期时间。这样也存在一个问题,加入一个线程拿到了锁设置了30s超时,在30s后这个线程还没有执行完毕,锁超时释放了,就会导致问题,Redisson 给出了自己的答案,就是 watch dog 自动延期机制。
Redisson 提供了一个监控锁的看门狗,它的作用是在 Redisson实例被关闭前,不断地延长锁的有效期,也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断地延长锁超时时间,锁不会因为超时而被释放。
默认情况下,看门狗的续期时间是30s,也可以通过修改 Config.lockWatchdogTimeout 来另行指定。另外 Redisson 还提供了可以指定 leaseTime 参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。