Redisson实现Redis分布式锁的底层原理
在Spring中,使用Redis实现分布式锁的方式是通过Redisson框架。它提供了一种基于Java的简单易用的分布式锁解决方案,并且支持自动续期功能。
Redisson具有自动续期锁的功能,称为“看门狗”。该功能使用Redis的发布订阅机制来实现。当获取到锁之后,Redisson会启动一个看门狗线程,定时检查当前节点是否仍然持有该锁。如果持有,则会对锁进行延期操作;否则,将释放锁。这种方式的优点是可以保证在锁的过期时间内,锁始终处于被占用状态。
在Spring Boot中,我们可以很方便地集成Redisson框架,使用它提供的分布式锁和自动续期功能。
在pom.xml文件中添加Redisson依赖:
本文章中使用的是以下方式引入,该方式对低版本的springboot项目兼容性更好
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>${redisson.version}</version>
</dependency>
或
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>${redisson.version}</version>
</dependency>
然后在application.yml配置文件中增加Redisson的相关配置:
spring:
redis:
host: localhost
port: 6379
password: 123456
database: 0
配置文件
@Configuration
public class RedissionConfig {
@Value("${spring.redis.host}")
private String redisHost;
@Value("${spring.redis.port}")
private String port;
@Value("${spring.redis.password}")
private String password;
@Value("${spring.redis.database}")
private int database;
@Bean
public RedissonClient getRedisson() {
Config config = new Config();
config.useSingleServer().
setAddress(redisHost + ":" + port).
setPassword(password).
setConnectionPoolSize(16).
setDatabase(database);
config.setCodec(new JsonJacksonCodec());
return Redisson.create(config);
}
}
(可选)如需设置锁的公共前缀可再次封装Redisson对象。例如:
// 创建一个MyRedisson对象
public class MyRedisson extends Redisson {
private String KeyPrefix;
protected MyRedisson(Config config) {
super(config);
this.KeyPrefix = "";
}
protected MyRedisson(Config config, String KeyPrefix) {
super(config);
this.KeyPrefix = KeyPrefix;
}
public static RedissonClient create(Config config) {
return new MyRedisson(config);
}
public static RedissonClient create(Config config, String KeyPrefix) {
return new MyRedisson(config, KeyPrefix);
}
public RLock getLock(String name) {
return super.getLock(KeyPrefix + name);
}
}
然后将配置文件中的Redisson.create(config)替换成MyRedisson.create(config, "你期望的前缀(如redisson-lock:)")
最后,在Java代码中使用Redisson的RLock对象来使用分布式锁。例如:
@Service
public class UserService {
@Autowired
private RedissonClient redissonClient;
public void doSomething() {
RLock lock = redissonClient.getLock("some_key");
try {
if (lock.tryLock(10, TimeUnit.SECONDS)) {
try {
// 执行业务逻辑
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
// 处理获取锁超时异常
}
}
}
在上述代码中,我们获取了一个名为“some_key”的RLock对象,并使用tryLock方法尝试获取锁。如果获取成功,则执行业务逻辑,并在释放锁之前保持锁的状态。在RLock对象内部,Redisson会创建一个看门狗线程,并定期检查锁的状态,自动续期锁的过期时间。
释放锁核心逻辑
protected RFuture<Boolean> unlockInnerAsync(long threadId) {
/**
* Redisson解锁:通过LUA脚本释放锁,保证多个命令之间的原子性
*/
return evalWriteAsync(getRawName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then " +
"return nil;" +
"end; " +
"local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); " +
"if (counter > 0) then " +
"redis.call('pexpire', KEYS[1], ARGV[2]); " +
"return 0; " +
"else " +
"redis.call('del', KEYS[1]); " +
"redis.call('publish', KEYS[2], ARGV[1]); " +
"return 1; " +
"end; " +
"return nil;",
Arrays.asList(getRawName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));
}
Redisson可重入锁的释放还是通过LUA脚本实现,保证多个命令之间的原子性。
基本流程:
1、通过hexists指令判断锁是不是自己的,如果不是自己的锁,说明非法释放锁,返回nil,
2、如果是自己的锁,通过hincrby将锁的重入次数减1;
3、判断减1后的数是否大于0,如果减1后的数大于0,说明还没有完全释放锁,则重置锁的过期时间,并返回0;
4、如果减1后的数已经等于0,说明已经完全释放锁,则通过del指令释放锁,并通过publish发布一条消息,告诉其它订阅了这把锁的线程,我已经释放锁了,你们可以过来获取了;释放锁成功,返回1
5、其它情况,返回nil;
主动释放锁这块考虑的不仅仅是对 key 进行处理,因为可能存在重入锁,所以会先对 redis key 对应的 hash value 进行递减,相当于减去重入次数。
redisson-spring-boot-starter方式引入需注意的问题(通过查询资料获知,未经过验证)
将 Redisson 与 Spring Boot 库集成。取决于Spring Data Redis模块,支持 Spring Boot 1.3.x - 2.4.x
这句话是官方说的,不过现在的2.5.x也是支持的,只需要注意springboot最低版本不要低于1.3.x即可。
redisson-spring-data与Spring Boot version的版本对应关系
maven依赖
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.1</version>
</dependency>
点击redisson-spring-boot-starter进去
org.redisson
redisson-spring-data-26
${project.version}
依赖的springboot版本为2.6
若与项目中版本不匹配
可如此处理
<!--redisson-->
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-boot-starter</artifactId>
<version>3.17.1</version>
<exclusions>
<exclusion>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-26</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson-spring-data-21</artifactId>
<version>3.17.1</version>
</dependency>
参考文档:https://blog.csdn.net/liuerchong/article/details/124942903