一、概述
二、使用
2.1 入门
2.1.1 引入依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.0</version>
</dependency>
2.1.2 配置
首先在配置文件中做好redis相关的配置:
spring:
redis:
host:
port: 6379
password:
然后创建配置类,按照如下方式配置好Redisson客户端Bean对象:
@Configuration
public class MyRedissonConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String redisPort;
@Value("${spring.redis.password}")
private String redisPassword;
/**
* 所有对Redisson的使用都是通过RedissonClient
* @return
* @throws IOException
*/
@Bean(destroyMethod="shutdown")
public RedissonClient redisson() throws IOException {
//1、创建配置
Config config = new Config();
config.useSingleServer().setAddress("redis://"+ redisHost + ":" + redisPort);
config.useSingleServer().setPassword(redisPassword);
//2、根据Config创建出RedissonClient实例
//Redis url should start with redis:// or rediss://
RedissonClient redissonClient = Redisson.create(config);
return redissonClient;
}
}
2.1.2 测试
创建测试类如下,测试客户端是否创建成功:
@Slf4j
@RunWith(SpringRunner.class)
@SpringBootTest
public class GulimallProductApplicationTests {
@Autowired
private RedissonClient redissonClient;
@Test
public void testRedisson() {
System.out.println(redissonClient);
}
}
2.2 分布式锁
2.2.1 可重入锁
基于Redis的Redisson分布式可重入锁RLock
Java对象实现了java.util.concurrent.locks.Lock
接口。
1. 简单使用
@ResponseBody
@GetMapping(value = "/hello")
public String hello() {
//1、获取一把锁,只要锁的名字一样,就是同一把锁
RLock myLock = redisson.getLock("my-lock");
//2、加锁
myLock.lock(); //阻塞式等待。默认加的锁都是30s
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
} catch (Exception ex) {
ex.printStackTrace();
} finally {
//3、解锁 假设解锁代码没有运行,Redisson会不会出现死锁
System.out.println("释放锁..." + Thread.currentThread().getId());
myLock.unlock();
}
return "hello";
}
<br />在Redisson使用过程中,即使解锁操作没有被正确执行,锁也会在一定时间后被释放掉。锁默认会在30s内自动过期,不会产生死锁问题。默认情况下,如果业务代码没有执行完成,Redisson会通过看门狗机制,运行期间自动锁上新的30s,不用担心业务时间长,锁自动过期被删掉。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22365594/1649139323318-7bd156f1-a2a0-40db-b433-83bc6f1cc64f.png#clientId=u9cf3fa5d-9125-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=393&id=u19d66445&margin=%5Bobject%20Object%5D&name=image.png&originHeight=393&originWidth=1482&originalType=binary&ratio=1&rotation=0&showTitle=false&size=53698&status=done&style=none&taskId=ud9f0c090-d195-4bf9-b59b-a209af2898b&title=&width=1482)<br />如果我们在调用加锁方法时指定了锁的过期时间,这时候看门狗机制则会失效,锁在到期之后自动解锁,此时若执行解锁操作,若存在其他线程已重新获取了锁,则会报错,提示解锁失败。<br />![image.png](https://cdn.nlark.com/yuque/0/2022/png/22365594/1649140161300-c55bfa21-99a4-454b-8920-b1627b7bb732.png#clientId=u9cf3fa5d-9125-4&crop=0&crop=0&crop=1&crop=1&from=paste&height=154&id=ue8074b43&margin=%5Bobject%20Object%5D&name=image.png&originHeight=154&originWidth=1165&originalType=binary&ratio=1&rotation=0&showTitle=false&size=150780&status=done&style=none&taskId=u91f2e751-1a12-456a-b8eb-aee29df21d3&title=&width=1165)
自动续期操作借助于定时任务,每隔指定的时间来扫描业务是否执行完成,如果没有执行完成就执行续期操作,定时任务逻辑如下,可以看到是三分之一的看门狗时间定时执行:
2.2.2 读写锁
基于Redis的Redisson分布式可重入读写锁RReadWriteLock
,Java对象实现了java.util.concurrent.locks.ReadWriteLock
接口。其中读锁和写锁都继承了RLock
接口。
分布式可重入读写锁允许同时有多个读锁和一个写锁处于加锁状态。
/**
* 保证一定能读到最新数据,修改期间,写锁是一个排它锁(互斥锁、独享锁),读锁是一个共享锁
* 写锁没释放读锁必须等待
* 只要有读或者写的存都必须等待
* @return
*/
@GetMapping(value = "/write")
@ResponseBody
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;
}
@GetMapping(value = "/read")
@ResponseBody
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;
}
- 读 + 读 :相当于无锁,并发读,只会在Redis中记录好,所有当前的读锁。他们都会同时加锁成功
- 写 + 读 :必须等待写锁释放
- 写 + 写 :阻塞方式
- 读 + 写 :有读锁。写也需要等待
2.2.3 信号量
基于Redis的Redisson的分布式信号量Semaphore
,Java对象RSemaphore
采用了与java.util.concurrent.Semaphore
相似的接口和用法。
/**
* 车库停车
* 3车位
* 信号量也可以做分布式限流
*/
@GetMapping(value = "/park")
@ResponseBody
public String park() throws InterruptedException {
RSemaphore park = redisson.getSemaphore("park");
park.acquire(); //获取一个信号、获取一个值,占一个车位
boolean flag = park.tryAcquire();
if (flag) {
//执行业务
} else {
return "error";
}
return "ok=>" + flag;
}
@GetMapping(value = "/go")
@ResponseBody
public String go() {
RSemaphore park = redisson.getSemaphore("park");
park.release(); //释放一个车位
return "ok";
}
2.2.4 闭锁
基于Redisson的Redisson分布式闭锁CountDownLatch
,Java对象RCountDownLatch
采用了与java.util.concurrent.CountDownLatch
相似的接口和用法。
/**
* 放假、锁门
* 1班没人了
* 5个班,全部走完,我们才可以锁大门
* 分布式闭锁
*/
@GetMapping(value = "/lockDoor")
@ResponseBody
public String lockDoor() throws InterruptedException {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.trySetCount(5);
door.await(); //等待闭锁完成
return "放假了...";
}
@GetMapping(value = "/gogogo/{id}")
@ResponseBody
public String gogogo(@PathVariable("id") Long id) {
RCountDownLatch door = redisson.getCountDownLatch("door");
door.countDown(); //计数-1
return id + "班的人都走了...";
}