文章目录
- 前言
- Redisson简介
- Redisson的优势
- 提供了看门狗
- 提供了多种锁
- 实战
- 加依赖
- 分析源码
- 改配置
- 方式一()
- 方式二()
- 方式三()
- 简单使用
- 优化一下
- 再优化一下
前言
Redisson简介
省略…
Redisson的优势
提供了看门狗
Redisson
提供了一个监控锁的看门狗(watch dog
),它的作用是在Redisson
实例被关闭前,不断(默认每10s)的延长锁(redis
中的目标key)的有效期(默认续期到30s),也就是说,如果一个拿到锁的线程一直没有完成逻辑,那么看门狗会帮助线程不断的延长锁的超时时间,锁不会因为超时而被释放。加锁的业务只要运行完成,就不会给当前锁续期,即使不手动解锁,锁默认会在30s内自动过期,不会产生死锁问题;
默认情况下,看门狗的续期时间是30s,也可以通过修改来另行指定。
另外Redisson
还提供了可以指定leaseTime
参数的加锁方法来指定加锁的时间。超过这个时间后锁便自动解开了,不会延长锁的有效期。
提供了多种锁
redisson 还有公平锁、读写锁的实现。
public interface RedissonClient {
/**
* 获取锁(默认的非公平锁)
*/
RLock getLock(String name);
/**
* 获取公平锁
*/
RLock getFairLock(String name);
/**
* 获取读写锁
*/
RReadWriteLock getReadWriteLock(String name);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
public interface RLock extends Lock, RExpirable, RLockAsync {
void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException;
boolean tryLock(long waitTime, long leaseTime, TimeUnit unit) throws InterruptedException;
void lock(long leaseTime, TimeUnit unit);
boolean forceUnlock();
boolean isLocked();
boolean isHeldByCurrentThread();
int getHoldCount();
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
实战
加依赖
<!--redisson-->
<dependency>
<groupId></groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.13.6</version>
<exclusions>
<exclusion>
<groupId></groupId>
<artifactId>redisson-spring-data-23</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>redisson-spring-data-21</artifactId>
<version>3.13.6</version>
</dependency>
<dependency>
<groupId></groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
关于版本,直接去官网找下最新版即可,通过redisson-spring-data-xx
来适配SpringBoot
的版本。上述就是适配SpringBoot-2.1
版本的配置;
分析源码
redisson-spring-boot-starter
这个包下只有2个类:RedissonProperties
和RedissonAutoConfiguration
。阅读源码发现RedissonAutoConfiguration
会自动注册RedissonConnectionFactory
和RedissonClient
。
改配置
redisson-spring-boot-starter
支持三种配置方式
- 方式一:完全兼容
spring-boot-starter-data-redis
的配置,即只需在中使用
来配置
redis
,redisson-spring-boot-starter
会根据Redis
的配置自动配置redisson
。 - 方式二:
redisson
的方式,即在
中使用
=classpath:
来指定redisson
的配置文件路径,然后在中配置
redisson
。 - 方式三:
redisson
的方式,即在
中使用
=xxx
来直接配置Redisson
。
多种方式的优先级: > >
。详细参考#redisson()
。
方式一(spring.redis)
文件:
spring:
# ====================================================================================
# spring-redis
# ====================================================================================
redis:
password: null
# 连接超时时间
timeout: 10000
# 单机
# host: 10.210.10.154
# port: 7004
# 集群
cluster:
nodes: 10.210.13.203:6381,10.210.13.203:6382,10.210.13.203:6383,10.210.13.203:6384,10.210.13.203:6385,10.210.13.203:6386
maxRedirects: 3
# 连接池
lettuce:
pool:
# 最大连接数
max-active: 8
# 最大阻塞等待时间(负数表示没限制)
max-wait: -1
# 最小空闲
min-idle: 0
# 最大空闲
max-idle: 8
- 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
方式二()
略
方式三()
文件:
spring:
# ====================================================================================
# spring-redis
# ====================================================================================
redis:
password: null
# 连接超时时间
timeout: 10000
# 单机
# host: 10.210.10.154
# port: 7004
# 集群
cluster:
nodes: 10.210.13.203:6381,10.210.13.203:6382,10.210.13.203:6383,10.210.13.203:6384,10.210.13.203:6385,10.210.13.203:6386
maxRedirects: 3
# 连接池
lettuce:
pool:
# 最大连接数
max-active: 8
# 最大阻塞等待时间(负数表示没限制)
max-wait: -1
# 最小空闲
min-idle: 0
# 最大空闲
max-idle: 8
# ====================================================================================
# spring-redis-redisson
# ====================================================================================
redisson:
# file: classpath:
config: |
# 单机
# singleServerConfig:
# password: null
# timeout: 10000
# address: redis://10.210.10.154:7004
# 集群
clusterServersConfig:
nodeAddresses:
- "redis://10.210.13.203:6381"
- "redis://10.210.13.203:6382"
- "redis://10.210.13.203:6383"
- "redis://10.210.13.203:6384"
- "redis://10.210.13.203:6385"
- "redis://10.210.13.203:6386"
password: null
loadBalancer: !<> {}
slaveConnectionMinimumIdleSize: 8
slaveConnectionPoolSize: 16
sslEnableEndpointIdentification: false
timeout: 3000
# 所有Redis节点客户端之间共享的线程数量,默认值: 当前处理核数量 * 2
threads: 24
# Netty线程池数量,默认值: 当前处理核数量 * 2
nettyThreads: 12
# 传输模式,默认值:NIO
transportMode: NIO
- 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
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
配置项需要参照,如果你想配置集群模式的
Redisson
,就点 Config
的成员变量clusterServersConfig
去看下里边有哪些可配置项。
这种方式使用了yaml
语法中的|
,然后使用的是解析该配置项的值,此时需要注意:
- 该配置项下的所有行,即使开头是"#",对于该配置项来说,都不会被认为是注释(都是该配置项的值的一部分),只有在后续解析它们时才会被认为是注释。
- 该配置项下的第一行(即使开头是"#"),一定要缩进,否则该配置项的值直接为
null
- 该配置项下的第二行开始,任何一行(即使开头是"#")都不能超过第一行,否则该配置项的值直接为
null
- 该配置项下所有行,
key
必须与对应Properties
类的对应属性名完全一致,不支持使用"-"代替驼峰,否则解析时报错 - 该配置项下所有行,
list
类型的配置必须使用"- xxx",不支持单行逗号分隔,否则解析时报错 - 该配置项下所有行,不支持使用
${xxx}
读取上下文的配置,否则解析后的值还是"${xxx}"
简单使用
配置了redis
和redisson
的信息之后,就可以在项目中使用@Autowired
注入(
redisson
提供的默认实现类是,且
redisson-spring-boot-starter
会自动往IOC容器中注册)。
-
Redis
操作注入RedisTemplate
进行使用。 -
Redission
分布式锁可以引入RedissionClient
进行使用。
@RestController
public class RedissonController {
@Autowired
private RedissonClient redissonClient;
@GetMapping(value = "/redisson/{key}")
public String redissonTest(@PathVariable("key") String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
Thread.sleep(10000);
} catch (Exception e) {
} finally {
lock.unlock();
}
return "已解锁";
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
优化一下
封装一下对redissonClient
的使用。
package com.xx.core.redisson.lock;
/**
* Redisson分布式锁接口
* <p>
* RLock的实现有可重入非公平锁(RedissonLock)、可重入公平锁(RedissonFairLock)、联锁(RedissonMultiLock)、 红锁(RedissonRedLock)、 读锁(RedissonReadLock)、 写锁(RedissonWriteLock)等
*/
public interface DistributedLocker {
RLock lock(String lockKey);
RLock lock(String lockKey, long timeout);
RLock lock(String lockKey, TimeUnit unit, long timeout);
boolean tryLock(String lockKey, TimeUnit unit, long leaseTime);
boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime);
void unlock(String lockKey);
void unlock(RLock lock);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
/**
* Redisson分布式锁实现-使用可重入非公平锁(RedissonLock)
*/
@Component
public class RedissonDistributedLocker implements DistributedLocker {
@Autowired
private RedissonClient redissonClient;
public RLock lock(String lockKey) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
public RLock lock(String lockKey, long timeout) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock(timeout, TimeUnit.SECONDS);
return lock;
}
public RLock lock(String lockKey, TimeUnit unit, long timeout) {
RLock lock = this.redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
public boolean tryLock(String lockKey, TimeUnit unit, long leaseTime) {
RLock lock = this.redissonClient.getLock(lockKey);
try {
return lock.tryLock(0L, leaseTime, unit);
} catch (InterruptedException var7) {
return false;
}
}
public boolean tryLock(String lockKey, TimeUnit unit, long waitTime, long leaseTime){
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
public void unlock(String lockKey){
RLock lock = redissonClient.getLock(lockKey);
try {
if (lock.isLocked()) {
lock.unlock();
}
} catch (IllegalMonitorStateException localIllegalMonitorStateException) {}
}
public void unlock(RLock lock) {
try {
if (lock.isLocked()) {
lock.unlock();
}
} catch (IllegalMonitorStateException var3) {}
}
}
- 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
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
再优化一下
DistributedLocker
接口新增tryLockAndRun()
方法
public interface DistributedLocker {
/**
* @param lockKey
* @param unit
* @param waitTime
* @param leaseTime
* @param supplier 获取锁后要执行的业务逻辑
* @param scene 业务逻辑的场景,用于打印日志
* @param <T>
* @return
*/
<T> T tryLockAndRun(@Nonnull String lockKey, @Nonnull TimeUnit unit, long waitTime, long leaseTime, @Nonnull Supplier<T> supplier, String scene);
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
RedissonDistributedLocker
实现tryLockAndRun()
方法
public class RedissonDistributedLocker implements DistributedLocker {
@Override
public <T> T tryLockAndRun(@Nonnull String lockKey, @Nonnull TimeUnit unit, long waitTime, long leaseTime, @Nonnull Supplier<T> supplier, String scene) {
final long start = SystemClock.now();
// 获取分布式锁,最长等待时间:10秒,20秒后自动释放。注意锁与事务的顺序:获取分布式锁 -> 开启事务 -> 执行业务 -> 提交事务 -> 释放分布式锁!!!
final boolean tryLock = this.tryLock(lockKey, unit, waitTime, leaseTime);
final long end = SystemClock.now();
if (!tryLock) {
log.error("[{}]获取分布式锁失败,lockKey = {},耗时{}ms", scene, lockKey, end - start);
throw new RequestResultException(JsonCommonCodeEnum.E0004);
}
// 注意:一定是获取锁成功后,才进行try{}finally{释放锁}
try {
log.info("[{}]获取分布式锁成功,lockKey = {},耗时{}ms", scene, lockKey, end - start);
return supplier.get();
} finally {
this.unlock(lockKey);
}
}
}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21