Redisson
官方github网站:https://github.com/redisson/redisson
Redisson官网:https://redisson.org/
依赖引入
pom.xml
文件中写入
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.13.6</version>
</dependency>
配置Redisson客户端
- 使用yml配置文件
- 导入Redisson起步依赖
- 自定义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;
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
// 配置类
Config config = new Config();
// 添加redis地址,这里添加了单点的地址,也可以使用config.useClusterServers()添加集群地址
config.useSingleServer().setAddress("redis://192.168.88.66:6379")
.setPassword("123456");
// 创建客户端
return Redisson.create(config);
}
}
业务中调用
// 创建锁对象
RLock lock = redissonClient.getLock("lock:order:" + userId);
// 尝试获取锁
boolean isLock = lock.tryLock();
// 判断是否成功
if (!isLock) {
// 获取失败返回错误
}
try {
// 执行对应的业务
} finally {
// 释放锁
lock.unlock();
}
其中trylock
方法共有三个重载方法,如果使用无参的方法,则不会进行等待,锁失效时间使用默认值(应该是30s)。
trylock(long waitTime, long leaseTime, TimeUnit unit)可以去设置等待时间、锁失效时间、时间单位。
trylock(long time, TimeUnit unit) 设置等待时间,时间单位。
Redisson可重入分布式锁原理
可重入:利用hash结构记录线程id和重入次数
可重试:利用信号量和PubSub功能实现等待、唤醒,获取锁失败的重试机制
超时续约:利用watchDog,每隔一段时间(releaseTime/3),重置超时时间
获取锁
- 尝试获取锁
- 判断ttl是否为null
- 如果为null,判断leaseTime是否为-1
- leaseTime为-1,开启看门狗机制watchDog,返回true;不为-1,直接返回true,不开启看门狗。
- ttl不为null,判断剩余等待时间是否大于0,如果小于0,返回false
- 剩余等待时间大于0,则订阅并等待别的线程释放锁的信号;
- 判断等待时间是否超时,已经超时,则返回false
- 若未超时,则再去尝试获取锁,即步骤1
释放锁
- 尝试释放锁
- 判断是否成功
- 如果失败,可能已经超时释放,记录异常,并结束
- 如果释放成功,发送释放锁消息(对应获取锁的步骤4)给订阅的线程,取消watchDog。
可重入的Lua脚本
Redisson实现也是使用的Lua脚本,具体Redisson实现方法,大家可以去写一个model看下解码的Redisson源码。以下Lua脚本是和Redisson源码思路一样的,但不完全一致,可以帮助理解原理。
获取锁的Lua脚本
-- Redisson 可重入锁获取锁的原理
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断是否存在
if(redis.call('exists', key) == 0) then
-- 不存在, 获取锁
redis.call('hset', key, threadId, '1');
-- 设置有效期
redis.call('expire', key, releaseTime);
return 1; -- 返回结果
end;
-- 锁已经存在,判断threadId是否是自己
if(redis.call('hexists', key, threadId) == 1) then
-- 存在, 获取锁,重入次数+1
redis.call('hincrby', key, threadId, '1');
-- 设置有效期
redis.call('expire', key, releaseTime);
return 1; -- 返回结果
end;
return 0; -- 代码走到这里,说明获取锁的不是自己,获取锁失败
释放锁的Lua脚本
-- Redisson可重入锁的删除锁原理
local key = KEYS[1]; -- 锁的key
local threadId = ARGV[1]; -- 线程唯一标识
local releaseTime = ARGV[2]; -- 锁的自动释放时间
-- 判断当前锁是否还是被自己持有
if (redis.call('HEXISTS', key, threadId) == 0) then
return nil; -- 如果已经不是自己,则直接返回
end;
-- 是自己的锁,则重入次数-1
local count = redis.call('HINCRBY', key, threadId, -1);
-- 判断是否重入次数是否已经为0
if (count > 0) then
-- 大于0说明不能释放锁,重置有效期然后返回
redis.call('EXPIRE', key, releaseTime);
return nil;
else -- 等于0说明可以释放锁,直接删除
redis.call('DEL', key);
return nil;
end;
Redisson主从一致性
主从一致性:设置连锁multiLock,多个独立的Redis节点,必须在所有节点都获取重入锁,才算获取锁成功