redisson使用

  1. 1. 引入依赖
  2. 2. 配置redisson
  3. 3. redisson使用
  4. 4. redisson读写锁
  5. 5. redisson信号量
  6. 6. countDownLatch闭锁

本笔记基于redisson依赖,springBoot项目环境建议使用redisson-spring-boot-starter依赖。redisson锁实现同juc包中的锁。

引入依赖

真实项目中推荐redisson-spring-boot-starter。我这里使用纯redisson依赖。

1
2
3
4
5
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.22.1</version>
</dependency>

配置redisson

新建MyRedissonConfig文件,此文件从nacos中的配置获取redis地址

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
@Configuration
@RefreshScope // 动态从nacos获取(自动刷新)
public class MyRedissonConfig {

@Value("${redis.address}")
private String redisAddress;

@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() {
Config config = new Config();
// java.lang.NoClassDefFoundError: io/netty/channel/epoll/EpollEventLoopGroup, 需要额外引入netty依赖
// config.setTransportMode(TransportMode.EPOLL);
// 单体模式启动
config.useSingleServer()
// use "rediss://" for SSL connection
.setAddress("redis://" + redisAddress);
return Redisson.create(config);
}
}

redis的地址是配置在nacos中动态获取的。见上面配置类注释。

1
2
redis:
address: 127.0.0.1:6379

redisson使用

redisson是根据锁的名字来区分是否同一把锁的。
通过lock.lock(10, TimeUnit.SECONDS)方法上锁,此方法可以传参过期时间,表示时间到了之后会自动释放锁,(或者手动释放锁)。即使任务没有执行完毕,锁依旧会被释放。后续在尝试手动释放锁的时候会报锁不存在错误。
若不传时间参数lock.lock()。则锁的过期时间为30s。并且redisson的看门狗会在经过10s后自动给锁续期为30s。直到主动释放锁。假设出现硬件故障(如断电)导致程序问题主动锁释放锁失败,此时由于程序问题,看门狗不会再续期,因此时间到之后,redis中的锁会自动过期。保证了不会由于硬件问题导致的死锁。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@ResponseBody
@GetMapping("/hello")
public String hello() {
// 1. 获取锁,根据名字区分是否同一把锁
RLock lock = redisson.getLock("my-lock");
// 2. 加锁
lock.lock(); // 不加自动过期时间,会默认过期时间30s,看门狗会在1/3的30秒时间(即每隔10s)自动续期至30s。
// lock.lock(10, TimeUnit.SECONDS); // 10秒秒后自动解锁。不会自动续期,必须保证大于业务执行时间
try {
System.out.println("加锁成功,执行业务..." + Thread.currentThread().getId());
// 模拟执行任务耗时
Thread.sleep(10000L);
} catch (Exception e) {
System.out.println(e.getMessage());
} finally {
// 假设解锁代码没运行,会出现死锁吗?
// 不会,redisson中的看门狗会自动对锁续期,默认续期30秒,若程序中断,看门狗不会自动续期,锁会自动过期
System.out.println("释放锁..." + Thread.currentThread().getId());
// 解锁
lock.unlock();
}

return "hello";
}

redisson读写锁

读写锁用于读多写少的并发情况。(读多写少的数据如果不要求强一致性,只要求最终一致性,非常适合放入redis中。注意,此段括号内容说的是数据存入缓存。锁还是要的)。
读写锁互斥情况:

  1. 读、读:不互斥,可以并发
  2. 读、写:互斥,只要读锁没有释放,持有写锁的就得等待
  3. 写、读:互斥,只要写锁没有释放,持有读锁的就得等待
  4. 写、写:互斥,只要写锁没有释放,尝试获取写锁就得等待。

测试代码改数据加写锁、读数据加读锁。write接口往redis中写数据,read接口从redis中读数据

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
// 测试读写锁,保证一定能读到最新的数据。读读不互斥;读写、写读、写写互斥
@GetMapping("/write")
@ResponseBody
public String writeValue() {
// 同一把读写锁
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
String s = "";
// 获取写锁
RLock rLock = lock.writeLock();
// 加写锁
rLock.lock();
try {
s = UUID.randomUUID().toString();
Thread.sleep(3000);
// 将数据存入redis,供获取就读锁的对象读取
stringRedisTemplate.opsForValue().set("writevlue", s);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}

@GetMapping("/read")
@ResponseBody
public String readValue() {
RReadWriteLock lock = redisson.getReadWriteLock("rw-lock");
String s = "";
// 获取读锁
RLock rLock = lock.readLock();
// 加锁
rLock.lock();
try {
// 从redis中读取数据
s = stringRedisTemplate.opsForValue().get("writevlue");
} catch (Exception e) {
e.printStackTrace();
} finally {
rLock.unlock();
}
return s;
}

redisson信号量

同juc中的Semaphore,一般用于限制流量(如果需要限流可以使用专业的限流中间件,如GitHub - alibaba/Sentinel (面向云原生微服务的高可用流控防护组件)GitHub - Netflix/Hystrix)。初始化Semaphore有多少个资源。每次场次acquire会将资源减1,直到资源变为0, 此时其他想要acquire的会阻塞。直到有对象release信号量,信号量+1,才能被其他尝试acquire的获取。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 手动设置redis中的park键的值vlue为3,表示Semaphore的上限为3.可根据需要手动设置,因此不建议直接写死在代码中。
@GetMapping("/park")
@ResponseBody
public String park() {
RSemaphore park = redissonClient.getSemaphore("park");
try {
// 获取资源,Semaphore资源-1。如果Semaphore资源为0会阻塞。
// 如果获取不了时需要其他操作,可以使用tryAcquire()方法。返回bool值,表示是否获取成功,若不成功,可以自定义执行逻辑。不会阻塞。
park.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
return "停进";
}

@GetMapping("/go")
@ResponseBody
public String go() {
RSemaphore park = redissonClient.getSemaphore("park");
// 释放资源,Semaphore资源+1.
park.release();
return "开走";
}

countDownLatch闭锁

同juc的countDownLatch,调用await()方法的会阻塞,直到countDown被减为0。才会执行,一般用于使多个线程的任务全部完成后,在统一处理后续操作。

1
2
3
4
5
6
7
8
9
10
11
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
// 设置countDown上限为1
latch.trySetCount(1);
// 阻塞等待其他线程执行任务后减去countDown至0。
latch.await();

// 在其他线程或其他JVM里
RCountDownLatch latch = redisson.getCountDownLatch("anyCountDownLatch");
// 执行任务后,将countDown减去1
doSomething();
latch.countDown();