文章目录
前言
本文主要描述redisson分布式锁的使用
1.Redisson 简介
Redisson是架设在Redis基础上的一个Java驻内存数据网格(In-Memory Data Grid)。
Redisson在基于NIO的Netty框架上,充分的利用了Redis键值数据库提供的一系列优势,在Java实用工具包中常用接口的基础上,为使用者提供了一系列具有分布式特性的常用工具类。使得原本作为协调单机多线程并发程序的工具包获得了协调分布式多机多线程并发系统的能力,大大降低了设计和研发大规模分布式系统的难度。同时结合各富特色的分布式服务,更进一步简化了分布式环境中程序相互之间的协作。
最熟为人知的是它的分布式锁机制,它有比redis本身更多更强劲的功能,可以自行百度
参考来源
分布式锁原理 : 发送一段lua脚本(保证原子性)用以加锁,其本质是抢占key,谁先生成key谁就有锁
以后再补一篇原理
2.集成方式
redisson 集成springBoot并不是特别友好,因为它没有像redis这样写自动配置类,故不能仅写配置就能用
引入pom坐标
<!--分布式锁-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
2.1 自定义配置属性+手动注入
要点:
- 自己写配置属性
- 自己写
@bean
注入 redissonClient
配置类
更多配置项 见官网
import com.xkj.ecommerce.utils.RedissonLockUtil;
import lombok.Data;
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.redisson.config.SentinelServersConfig;
import org.redisson.config.SingleServerConfig;
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Arrays;
/**
* @createDate 2021/1/18
*/
@Configuration
@ConfigurationProperties(prefix = "redisson")
@Data
@AutoConfigureOrder
public class RedissonConfig {
private int timeout = 3000;
private String address;
private String password;
private int database = 0;
private int connectionPoolSize = 64;
private int connectionMinimumIdleSize = 10;
private int slaveConnectionPoolSize = 250;
private int masterConnectionPoolSize = 250;
private String[] sentinelAddresses;
private String masterName;
public void setSentinelAddresses(String sentinelAddresses) {
this.sentinelAddresses = Arrays.stream(sentinelAddresses.split(",")).map(s -> "redis://" + s).toArray(String[]::new);
}
public void setAddress(String address) {
this.address = "redis://" + address;
}
/**
* 哨兵模式自动装配
*/
@Bean
@ConditionalOnMissingBean(RedissonClient.class)
@ConditionalOnProperty(name = "redisson.masterName")
public RedissonLockUtil redissonSentinel() {
Config config = new Config();
SentinelServersConfig serverConfig = config.useSentinelServers().addSentinelAddress(this.getSentinelAddresses())
.setMasterName(this.getMasterName())
.setTimeout(this.getTimeout())
.setMasterConnectionPoolSize(this.getMasterConnectionPoolSize())
.setSlaveConnectionPoolSize(this.getSlaveConnectionPoolSize());
if (StringUtils.isNotBlank(this.getPassword())) {
serverConfig.setPassword(this.getPassword());
}
RedissonClient redissonClient = Redisson.create(config);
RedissonLockUtil locker = new RedissonLockUtil();
locker.setRedissonClient(redissonClient);
return locker;
}
/**
* 单机模式自动装配
*/
@Bean
@ConditionalOnProperty(name = "redisson.address")
public RedissonLockUtil redissonSingle() {
Config config = new Config();
SingleServerConfig serverConfig = config.useSingleServer()
.setAddress(this.getAddress())
.setTimeout(this.getTimeout())
.setConnectionPoolSize(this.getConnectionPoolSize())
.setConnectionMinimumIdleSize(this.getConnectionMinimumIdleSize());
if (StringUtils.isNotBlank(this.getPassword())) {
serverConfig.setPassword(this.getPassword());
}
RedissonClient redissonClient = Redisson.create(config);
RedissonLockUtil locker = new RedissonLockUtil();
locker.setRedissonClient(redissonClient);
return locker;
}
}
application.properties
redisson.timeout=3000
....
# 哨兵配置(用英文逗号隔开)
# redisson.sentinelAddresses=192.168.2.100:3408,192.168.2.101:3408
# 单机配置
redisson.address=192.168.2.100:3408
2.2 使用Yaml()方式批量读取配置
要点:
- 使用redisson官方提供的工具类,读取指定配置项
配置类
import org.redisson.Redisson;
import org.redisson.api.RedissonClient;
import org.redisson.config.Config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.io.IOException;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redisson() throws IOException {
// 本例子使用的是yaml格式的配置文件,读取使用Config.fromYAML,如果是Json文件,则使用Config.fromJSON
Config config = Config.fromYAML(RedissonConfig.class.getClassLoader().getResource("redisson-config.yml"));
return Redisson.create(config);
}
}
自行注入到工具类中,参考
RedissonClient redissonClient = Redisson.create(config); RedissonLockUtil locker = new RedissonLockUtil(); locker.setRedissonClient(redissonClient);
redisson-config.yml
#Redisson配置
singleServerConfig:
address: "redis://127.0.0.1:6379"
password: null
clientName: null
database: 7 #选择使用哪个数据库0~15
idleConnectionTimeout: 10000
pingTimeout: 1000
connectTimeout: 10000
timeout: 3000
retryAttempts: 3
retryInterval: 1500
reconnectionTimeout: 3000
failedAttempts: 3
subscriptionsPerConnection: 5
subscriptionConnectionMinimumIdleSize: 1
subscriptionConnectionPoolSize: 50
connectionMinimumIdleSize: 32
connectionPoolSize: 64
dnsMonitoringInterval: 5000
#dnsMonitoring: false
threads: 0
nettyThreads: 0
codec:
class: "org.redisson.codec.JsonJacksonCodec"
transportMode: "NIO"
2.3 使用SpringBoot自动配置类
引入springboot-redisson坐标
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.redisson/redisson-spring-boot-starter -->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.12.0</version>
</dependency>
与Yaml()方式 类似,应该属于进阶版
application.yml
# 公共的spring配置
spring.redis.database=
spring.redis.host=
spring.redis.port=
spring.redis.password=
spring.redis.ssl=
spring.redis.timeout=
spring.redis.cluster.nodes=
spring.redis.sentinel.master=
spring.redis.sentinel.nodes=
# Redisson 的特殊配置
# 可以从本地的类路径下读取配置文件
spring.redis.redisson.config=classpath:redisson-config.yml
然后就可以直接使用
@Autowired
RedissonClient redissonClient;
自行注入到工具类中,参考
RedissonClient redissonClient = Redisson.create(config); RedissonLockUtil locker = new RedissonLockUtil(); locker.setRedissonClient(redissonClient);
3. 工具类
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import java.util.concurrent.TimeUnit;
public class RedissonLockUtil {
private static RedissonClient redissonClient;
public void setRedissonClient(RedissonClient redissonCli) {
redissonClient = redissonCli;
}
public static RLock lock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock();
return lock;
}
public static RLock lock(String lockKey, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(leaseTime, TimeUnit.SECONDS);
return lock;
}
public static RLock lock(String lockKey, TimeUnit unit, int timeout) {
RLock lock = redissonClient.getLock(lockKey);
lock.lock(timeout, unit);
return lock;
}
/**
* 尝试获得锁,自定义 时间单位;等待时长;加锁时长
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, unit);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获得锁,自定义 等待时长;加锁时长 (默认为秒)
*/
public static boolean tryLock(String lockKey, int waitTime, int leaseTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, leaseTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获得锁,自定义 等待时长 (加锁时长不限制;默认为秒)
*/
public static boolean tryLock(String lockKey, int waitTime) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, TimeUnit.SECONDS);
} catch (InterruptedException e) {
return false;
}
}
/**
* 尝试获得锁,自定义 等待时长 (加锁时长不限制)
*/
public static boolean tryLock(String lockKey, int waitTime, TimeUnit unit) {
RLock lock = redissonClient.getLock(lockKey);
try {
return lock.tryLock(waitTime, unit);
} catch (InterruptedException e) {
return false;
}
}
public static void unlock(String lockKey) {
RLock lock = redissonClient.getLock(lockKey);
lock.unlock();
}
public static void unlock(RLock lock) {
lock.unlock();
}
}
用的时候就可以直接 RedissonLockUtil.lock("test")
, 就不用注解注入啦(超方便的说)
4. 总结
按理来说,第三种集成方式最方便,但是我没用,因为我没用😭
(据说redisson和redisson-spring-boot-starter用的不是同一套体系,如果考虑引包的兼容性,建议用第三种方式)
第二种和第三种方式我没有测试
redisson是很好用的一个东西,还是要学会使用
5. 扩展
- 学习redisson还有其他很方便的功能(自动续锁/超时释放锁等)
- redisson与单体锁的区别
- 在redis集群部署时redisson出现的问题,以及解决方案(红锁)
- redis和zookeeper实现分布式锁的对比