在分布式项目中经常需要用到分布式锁,直接使用redis相关命令SET key value [EX seconds] [PX milliseconds] [NX] [XX]或者命令SETNX可以实现分布式锁。
使用命令方式实现分布式锁存在一些问题,比如解锁之前发生程序异常,导致无法解锁,或者设置了过期时间,线程A获得的锁因过期被redis删除,而此时A还去执行DEL命令等问题,为了解决这些问题,还需要引入Lua脚本,这样就使程序更加复杂了,redis推出了更加方便实现分布式锁的组件Redisson,详情参看官方文档
基于Redis的Redisson分布式可重入锁RLock Java对象实现了java.util.concurrent.locks.Lock接口,使用方式跟ReentrantLock一样。
本文主要介绍SpringBoot2.3整合Redisson实现分布式锁的简单实现,为了方便测试,使用了swagger作为测试工具
SpringBoot整合Redisson核心依赖如下:
<dependency>
<groupId>org.redissongroupId>
<artifactId>redisson-spring-boot-starterartifactId>
<version>3.13.5version>
dependency>
<dependency>
<groupId>org.springframework.bootgroupId>
<artifactId>spring-boot-starter-data-redisartifactId>
dependency>
Redisson有两种配置方式,程序化配置和yml文件配置
程序化配置方法是通过构建Config对象实例来实现,代码如下:
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = new Config();
config.useSingleServer().setAddress("redis://192.168.108.11:6379");
return Redisson.create(config);
}
Redisson支持多种模式,比如集群模式、哨兵模式、主从模式等,本文才有单节点模式
yml文件配置需要新建一个yml文件单独配置Redisson相关属性,不能配置在application.yml配置文件中,主要的配置如下:
singleServerConfig:
idleConnectionTimeout: 10000 #连接空闲超时(毫秒),默认10000
connectTimeout: 10000 #连接空闲超时(毫秒),默认10000
timeout: 3000 #命令等待超时(毫秒),默认3000
retryAttempts: 3 #命令失败重试次数
retryInterval: 1500 #命令重试发送时间间隔(毫秒),默认1500
password: null
subscriptionsPerConnection: 5 #单个连接最大订阅数量,默认5
clientName: null #客户端名称
address: "redis://127.0.0.1:6379"
subscriptionConnectionMinimumIdleSize: 1 #发布和订阅连接的最小空闲连接数,默认1
subscriptionConnectionPoolSize: 50 #发布和订阅连接池大小,默认50
connectionMinimumIdleSize: 24 #最小空闲连接数,默认32
connectionPoolSize: 64 #连接池大小,默认64
database: 0
dnsMonitoringInterval: 5000 #DNS监测时间间隔(毫秒),默认5000
threads: 16
nettyThreads: 32
codec: ! {
}
"transportMode": "NIO"
此处需要注意的是Redisson中文网提供的示例在项目启动的时候会报错,使用英文网提供的示例是可以正常启动项目的,估计是中文网的文档没有及时更新,以上配置摘自Redisson英文网
使用yml文件配置还需要新建一个配置类加载配置文件,实例化RedissonClient,代码如下:
@Bean(destroyMethod = "shutdown")
public RedissonClient redisson() throws IOException {
Config config = Config.fromYAML(new ClassPathResource("application-single.yml").getInputStream());
RedissonClient redisson = Redisson.create(config);
return redisson;
}
注意:用ClassPathResource方式加载配置文件
public AjaxResult selectUserForLockById(@PathVariable Long id) {
String key = "user_" + id;
RLock readLock = redissonService.getReadLock(key);
readLock.lock();//加锁
//readLock.lock(10, TimeUnit.SECONDS);可以设置锁的过期时间,如果不设置redisson默认30秒
SysUser user = new SysUser();
try {
logger.info("加锁成功,开始执行查询业务...");
user = userService.getById(id);
} catch (Exception e) {
e.printStackTrace();
} finally {
logger.info("释放锁....");
readLock.unlock();
}
return AjaxResult.ok().data("user", user);
}
public AjaxResult selectUserForSemaphoreById(@PathVariable Long id) {
String key = "user_semaphore_" + id;
RSemaphore semaphore = redissonService.getSemaphore(key);
//设置初始信号量数量
semaphore.trySetPermits(5);
SysUser user = new SysUser();
try {
boolean flag = semaphore.tryAcquire();
if (flag) {
logger.info("获取信号量成功,开始执行任务......");
user = userService.getById(id);
logger.info("业务执行完成,释放信号量......");
semaphore.release();
} else {
logger.info("获取信号量失败,直接返回消息......");
user = null;
}
} catch (Exception e) {
e.printStackTrace();
}
return AjaxResult.ok().data("user", user);
}
Redisson还提供了公平锁、联锁、红锁、闭锁等实现,本文不做一一介绍了,更多详情参看Redisson官网