清明在家无事,并且因为上海疫情原因只能宅在家里,突然想到之前计划着写一篇Redisson
的分布式锁快速入门教程,自己平常在工作中也只能简单会使用,所以文章可能写的比较简单,希望大佬勿喷。此文章也作为个人的笔记,用于查漏补缺。
补充:目前只是使用Redisson
作为分布式锁的用途。
概述
什么是Redisson
呢?
Redisson是一个在Redis的基础上实现的Java驻内存数据网格(In-Memory Data Grid)。它不仅提供了一系列的分布式的Java常用对象,还提供了许多分布式服务。其中包括(BitSet, Set, Multimap, SortedSet, Map, List, Queue, BlockingQueue, Deque, BlockingDeque, Semaphore, Lock, AtomicLong, CountDownLatch, Publish / Subscribe, Bloom filter, Remote service, Spring cache, Executor service, Live Object service, Scheduler service) Redisson提供了使用Redis的最简单和最便捷的方法。Redisson的宗旨是促进使用者对Redis的关注分离(Separation of Concern),从而让使用者能够将精力更集中地放在处理业务逻辑上。
使用
首先,Redisson
对于SpringBoot
有对于的整合包,不过我们出于学习目的,先只引入Redisson
。
<!-- https://mvnrepository.com/artifact/org.redisson/redisson -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.17.0</version>
</dependency>
配置方法
我们在官方文档中看到有这么多的配置方法,我们先选择使用程序化配置方法进行Redisson
的配置。我们可以在右侧Wiki Home
里找到第三方框架整合,我们可以选择Spring Cache整合中提供的方式来进行配置
@Configuration
public class MyRedissonConfig {
@Bean(destroyMethod = "shutdown")
RedissonClient redisson() throws IOException {
Config config = new Config();
// 单Redis节点模式
config.useSingleServer()
// 这样配置是有问题的,请先看我下边的解释
.setAddress("192.168.200.12:6379");
return Redisson.create(config);
}
}
如果我们直接这样写,一定会报出**Redis url should start with redis:// or rediss:// (for SSL connection)**这个错误,解决方式是我们需要把192.168.200.12:6379
加上redis://
或rediss://
,即redis://192.168.200.12:6379
可重入锁(Reentrant Lock)
@GetMapping("/hello")
@ResponseBody
public String hello() {
RLock lock = redisson.getLock("anyLock");
// 最常见的使用方法
lock.lock();
System.out.println("获取锁");
try {
System.out.println("业务");
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("解锁");
lock.unlock();
}
return "hello";
}
大家都知道,如果负责储存这个分布式锁的Redisson节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
另外Redisson还通过加锁的方法提供了leaseTime的参数来指定加锁的时间。超过这个时间后锁便自动解开了。
@GetMapping("/hello")
@ResponseBody
public String hello() {
RLock lock = redisson.getLock("anyLock");
// 更推荐使用明显标注leaseTime的这种方式,可以省去延期锁
lock.lock(10, TimeUnit.SECONDS);
System.out.println("获取锁--" + Thread.currentThread().getId());
try {
System.out.println("业务--" + Thread.currentThread().getId());
Thread.sleep(15000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("解锁--" + Thread.currentThread().getId());
lock.unlock();
}
return "hello";
}
当然,如果我们想设置最长等待时间的话。
@GetMapping("/hello")
@ResponseBody
public String hello() throws InterruptedException {
RLock lock = redisson.getLock("anyLock");
// 尝试加锁,最多等待100秒,上锁以后10秒自动解锁
boolean res = lock.tryLock(100, 10, TimeUnit.SECONDS);
if (res) {
System.out.println("获取锁--" + Thread.currentThread().getId());
try {
System.out.println("业务--" + Thread.currentThread().getId());
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("解锁--" + Thread.currentThread().getId());
lock.unlock();
}
}
return "hello";
}
读写锁(ReadWriteLock)
基于Redis的Redisson分布式可重入读写锁RReadWriteLock Java对象实现了java.util.concurrent.locks.ReadWriteLock接口。其中读锁和写锁都继承了RLock接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
大家都知道,如果负责储存这个分布式锁的Redis节点宕机以后,而且这个锁正好处于锁住的状态时,这个锁会出现锁死的状态。为了避免这种情况的发生,Redisson内部提供了一个监控锁的看门狗,它的作用是在Redisson实例被关闭前,不断的延长锁的有效期。默认情况下,看门狗的检查锁的超时时间是30秒钟,也可以通过修改Config.lockWatchdogTimeout来另行指定。
@GetMapping("/read")
@ResponseBody
public String readValue() {
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
RLock rLock = rwlock.readLock();
// 上锁
rLock.lock();
System.out.println("读锁" + Thread.currentThread().getId());
String uuid = "";
try {
uuid = stringRedisTemplate.opsForValue().get("key");
// 业务
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("解锁" + Thread.currentThread().getId());
// 解锁
rLock.unlock();
}
return uuid;
}
@GetMapping("/write")
@ResponseBody
public String writeValue() {
RReadWriteLock rwlock = redisson.getReadWriteLock("anyRWLock");
// 最常见的使用方法
RLock rLock = rwlock.writeLock();
// 上锁
rLock.lock();
System.out.println("写锁" + Thread.currentThread().getId());
String uuid = "";
try {
uuid = UUID.fastUUID().toString();
stringRedisTemplate.opsForValue().set("key", uuid);
// 业务
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("解锁" + Thread.currentThread().getId());
// 解锁
rLock.unlock();
}
return uuid;
}
信号量(Semaphore)
基于Redis的Redisson的分布式信号量(Semaphore)Java对象RSemaphore采用了与java.util.concurrent.Semaphore相似的接口和用法。
我们拿停车场作为例子,park为停车方法,go为离开停车场方法,当停车场的车位(semaphore)大于0时,可以随便停车,但是当车位(semaphore)等于0时,则park方法会一直阻塞到go方法被激活后停车场的车位(semaphore)大于0
@GetMapping("/park")
@ResponseBody
public String park() throws InterruptedException {
// 按名称返回信号量实例
RSemaphore semaphore = redisson.getSemaphore("semaphore");
// 获得许可。如有必要,等待许可证可用。
semaphore.acquire();
return "park";
}
@GetMapping("/go")
@ResponseBody
public String go() {
// 按名称返回信号量实例
RSemaphore semaphore = redisson.getSemaphore("semaphore");
// 发放许可证。增加可用许可证的数量。
semaphore.release();
return "go";
}
如果我们不想一直让park方法阻塞的话,可以使用以下方式
@GetMapping("/park")
@ResponseBody
public String park() {
// 按名称返回信号量实例
RSemaphore semaphore = redisson.getSemaphore("semaphore");
// 尝试获取当前可用的许可证。
// 如果获得了许可,则为true ,否则为false
boolean flag = semaphore.tryAcquire();
if (flag) {
// 业务
} else {
// 业务
}
return "park--" + flag;
}
闭锁(CountDownLatch)
基于Redisson的Redisson分布式闭锁(CountDownLatch)Java对象RCountDownLatch采用了与java.util.concurrent.CountDownLatch相似的接口和用法。
我们可以拿放学这个例子来说明闭锁,当所有的班级全部锁门之后,学校才可以锁门。
@GetMapping("/lockSchool")
@ResponseBody
public String lockSchool() throws InterruptedException {
// 按名称返回 countDownLatch 实例。
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
// 仅当先前的计数已达到零或根本未设置时才设置新的计数值。
latch.trySetCount(5);
// 等到计数器达到零。
latch.await();
return "放学啦";
}
@GetMapping("/afterSchool")
@ResponseBody
public String afterSchool() {
// 按名称返回 countDownLatch 实例。
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
// 减少锁存器的计数器。当计数达到零时通知所有等待线程。
latch.countDown();
return "走了一个班";
}