Redisson 是一个 Redis 客户端,并且 Redisson 功能强大,所以使用 Redisson 可以很方便实现 Redis 分布式锁。关于分布式锁的更多知识可以参考我的另一篇博客:【Redis】之分布式锁
基于 Redis 实现的分布式锁存在一个锁的续期问题:持有锁的线程在锁过期时间内还没有执行完业务,此时锁超时被自动释放,这样会导致多个线程同时持有锁的问题,所以需要给锁的过期时间进行续期。
而 Redisson 就是能够很好的给我们解决锁的续期问题,同时 Redisson 还给我们实现了各种各样的锁,比如红锁(RedLock)、 可重入锁(Reentrant Lock)、公平锁(Fair Lock)、读写锁(ReadWriteLock)、 信号量(Semaphore)。
更多关于 Redisson 介绍可以参考官方文档:Redisson的分布式锁和同步器
2-1、看门狗机制
Redisson 实现锁的续期功能使用的是看门狗机制,具体原理是:Redisson 在获取锁之后,会维护一个看门狗线程,当锁即将过期还没有释放时,不断的延长锁 key 的生存时间:
2-2、加锁机制:
2-3、watchdog 自动延期机制:
redisson在获取锁之后,会维护一个看门狗线程,在每一个锁设置的过期时间的1/3处,如果线程还没执行完任务,则不断延长锁的有效期。看门狗的检查锁超时时间默认是30秒,可以通过 lockWactchdogTimeout 参数来改变。
加锁的时间默认是30秒,如果加锁的业务没有执行完,那么每隔 30 ÷ 3 = 10秒,就会进行一次续期,把锁重置成30秒,保证解锁前锁不会自动失效。
那万一业务的机器宕机了呢?如果宕机了,那看门狗线程就执行不了了,就续不了期,那自然30秒之后锁就解开了呗。
需要注意:
如何理解锁的失效时间和锁的过期时间?
所以我们如果要使用 Redisson 的自动续期功能的话,就不能设置锁的过期时间,只需要设置失效时间即可,锁的解除就交给 Redisson 执行。
2-4、Redisson 分布式锁的关键点总结:
1-1、引入依赖
<dependency>
<groupId>org.redissongroupId>
<artifactId>redissonartifactId>
<version>3.12.0version>
dependency>
1-2、配置 Redisson 客户端对象
@Configuration
public class MyRedissonConfig {
/**
* 所有对Redisson的使用都是通过RedissonClient
* @return
* @throws IOException
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
// 1、创建配置
Config config = new Config();
// Redis url should start with redis:// or rediss://
config.useSingleServer().setAddress("redis://192.168.56.10:6379");f
// 2、根据Config创建出RedissonClient实例
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
配置好后就可以通过依赖注入方式创建 Redisson 客户端对象:
@Autowired
private RedissonClient redissonClient;
2-1、普通加锁
@ResponseBody
@GetMapping(value = "/hello")
public String hello() {
// 1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock myLock = redisson.getLock("my-lock");
// 2、加锁,阻塞式等待,默认加的锁都是30s
myLock.lock();
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
try { TimeUnit.SECONDS.sleep(20); }
catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception ex) {
ex.printStackTrace();
} finally {
// 3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁
System.out.println("释放锁..." + Thread.currentThread().getId());
myLock.unlock();
}
return "hello";
}
设置锁的到期时间:
// 设置10秒钟自动解锁,自动解锁时间一定要大于业务执行时间
myLock.lock(10,TimeUnit.SECONDS);
问题:在锁时间到了以后,如何自动续期
2-2、读写锁
// 测试写锁
public String writeValue() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock"); // 构造读写锁
RLock rLock = readWriteLock.writeLock(); // 获取写锁
try {
//1、改数据加写锁,读数据加读锁
rLock.lock();
s = UUID.randomUUID().toString();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
ops.set("writeValue",s);
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
// 测试读锁
public String readValue() {
String s = "";
RReadWriteLock readWriteLock = redisson.getReadWriteLock("rw-lock");
RLock rLock = readWriteLock.readLock();
try {
// 加读锁
rLock.lock();
ValueOperations<String, String> ops = stringRedisTemplate.opsForValue();
s = ops.get("writeValue");
try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); }
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}
读写锁保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁,写锁没释放读锁必须等待:
总结就是:加读锁,可以读不能写;加写锁,既不能读也不能写。
2-3、信号量
/**
* 信号量也可以做分布式限流
*/
public String park() throws InterruptedException {
// 构建信号量
RSemaphore park = redisson.getSemaphore("park");
// 获取一个信号、获取一个值,redis 中的 park 值减一
park.acquire(); // 阻塞
boolean flag = park.tryAcquire(); // 非阻塞
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
public String go() {
RSemaphore park = redisson.getSemaphore("park");
park.release(); // 释放一个信号量,redis 中的 park 值加一
return "ok";
}
2-4、闭锁
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}
更多关于锁的资料可以参考官方文档:分布式锁和同步器。