Redisson的应用
Redisson 简介:Redisson 是一个在 Redis 的基础上实现的 Java 驻内存数据网格。它不仅提供了一系列的分布式的 Java 常用对象,还提供了许多分布式服务,其中就包含了各种分布式锁的实现。
使用步骤
添加依赖:
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.24.2</version>
</dependency>
配置客户端:
@Configuration
public class RedissonConfig {
@Bean
public RedissonClient redissonClient() {
Config config = new Config();
config.useSingleServer()
.setAddress("redis://localhost:6379")
.setPassword("123456");
return Redisson.create(config);
}
}
一般的使用步骤:
@Resource
private RedissionClient redissonClient;
@Test
void testRedisson() throws Exception{
//获取锁(可重入),指定锁的名称
RLock lock = redissonClient.getLock("anyLock");
//尝试获取锁,参数分别是:获取锁的最大等待时间(期间会重试),锁自动释放时间,时间单位
boolean isLock = lock.tryLock(1,10,TimeUnit.SECONDS);
//判断获取锁成功
if(isLock){
try{
System.out.println("执行业务");
}finally{
//释放锁
lock.unlock();
}
}
}
修改原来的代码:
// SimpleRedisLock lock = new SimpleRedisLock("order:" + userId, stringRedisTemplate);
RLock lock = redissonClient.getLock("lock:order:" + userId);
boolean isLock = lock.tryLock();
分布式锁 - Redisson 可重入锁原理
在 Lock 锁中,通常借助于底层的一个volatile
的state
变量来记录重入的状态。比如当前没有人持有这把锁,那么state = 0
;假如有人持有这把锁,那么state = 1
;如果持有这把锁的人再次持有这把锁,那么state
就会 +1。在 Java 的内建锁(synchronized
)中,重入锁的状态通常由一个count
变量来表示,每次锁被获得时,count
会增加 1,每次锁被释放时,count
会减少 1。当count
减少到 0 时,表示没有线程持有锁。这样,同一个线程可以多次获得锁,而不 会导致死锁。
在 Redisson 中,可重入锁是通过在 Redis 上使用特定的数据结构和 Lua 脚本来实现的。Lua 脚本如下:
local lockExists = redis.call("exists", KEYS[1]) -- 检查锁是否存在
if lockExists == 0 then
redis.call("hset", KEYS[1], ARGV[1], 1) -- 锁不存在,创建锁,并将拥有者设置为1
redis.call("pexpire", KEYS[1], ARGV[2]) -- 设置锁的过期时间
return 1 -- 返回1表示成功获取锁
end
local lockOwner = redis.call("hget", KEYS[1], ARGV[1]) -- 获取锁的拥有者
if lockOwner == false then
redis.call("hset", KEYS[1], ARGV[1], 1) -- 拥有者不存在,创建锁,并将拥有者设置为1
redis.call("pexpire", KEYS[1], ARGV[2]) -- 设置锁的过期时间
return 1 -- 返回1表示成功获取锁
end
if lockOwner == ARGV[1] then
local counter = redis.call("hincrby", KEYS[1], ARGV[1], 1) -- 拥有者是当前线程,增加拥有次数
redis.call("pexpire", KEYS[1], ARGV[2]) -- 更新锁的过期时间
return counter -- 返回拥有次数
end
return 0 -- 返回0表示获取锁失败
这个 Lua 脚本的作用是:
- 首先检查大键表示的锁是否存在。如果锁不存在,则创建锁, 并将小键表示的拥有者设置为 1,并为锁设置过期时间。返回 1 表示成功获取锁。
- 如果锁存在,再检查小键表示的锁的拥有者是不是当前线程(线程标识通过
ARGV[1]
传递)。如果是当前线程,增加拥有次数,并更新锁的过期时间。返回拥有次数。 - 如果锁已经被其他线程拥有,返回 0 表示获取锁失败。
Redission 锁重试机制
Redisson 提供了重试机制来处理获取分布式锁时的竞争条件。这个机制可以用于在获取锁失败时,尝试多次获取锁,以减少竞争。
Redission 的锁重试机制是通过org.redisson.RedissonRedLock
类中的tryLockInner
方法实现的。这个方法是 RedLock 分布式锁的内部实现,它通过 Lua 脚本与 Redis 服务器交互以获取锁。
在分析源码时,可以按照以下步骤来理解重试机制:
- 查找
tryLockInner
方法:在 Redisson 的源码中,可以找到RedissonRedLock
类,然后查找tryLockInner
方法。这个方法是用来尝试获取锁的核心逻辑。 - Lua 脚本:在
tryLockInner
方法中,使用 Lua 脚本来与 Redis 服务器进行交互。这个 Lua 脚本实现了锁的获取逻辑,包括了锁的存在检查、锁的创建、锁的续约等等。 - 重试机制:在获取锁的过程中,可以看到有关重试的逻辑。这包括了重试次数和重试间隔的 控制,通常使用
retryAttempts
和retryInterval
参数来配置。 - 锁的状态:在锁的重试机制中,可能需要跟踪锁的状态,以确定是否已经成功获取锁。可以看到一些变量或标志来表示锁的状态。
- 超时处理:还会有关于等待锁的超时时间处理,如果等待超过一定时间仍然无法获取锁,会抛出异常。
- 与 Redis 交互:Redisson 通过 Redisson 客户端与 Redis 服务器进行交互,可以看到一些 Redis 命令的调用,如
set
、eval
等,用来实现锁的获取和续约。
看门狗机制
在分布式系统和分布式锁中,"看门狗"(也称为 "锁的续租" 或 "锁的续约" 机制)是一种用于确保锁的有效性和持续性的机制。这个机制的目的是防止因为锁持有者在持有锁期间发生故障或长时间处理任务而导致锁无法释放的情况。
看门狗机制的基本工作方式如下:
- 当一个线程成功获取锁时,它会同时启动一个定时器或计时器,设置一个锁的持续时间。这个持续时间通常是一个较短的时间段,比如锁的过期时间的一半。
- 在锁的持续时间内,锁的拥有者需要周期性地 “喂狗”,也就是不断重置或续租锁的持续时间。这通常是通过向锁存储中更新锁的时间戳或其他信息来实现的。
- 如果锁的拥有者在持续时间内没有续租锁,比如因为线程崩溃或异常退出,那么锁将自动过期,其他线程将有机会尝试获取锁。
看门狗机制的好处在于它能够防止锁被永久地占用。即使锁的拥有者在某些情况下无法释放锁,也会在锁的持续时间过期后,使其他线程有机会获取锁,避免了死锁或长时间阻塞的情况。
Redisson 等一些分布式锁实现库使用了看门狗机制来支持锁的续租。通过定期续租锁,可以确保锁不会因为拥有者的故障而永久丧失。如果锁的拥有者能够定期执行续租操作,锁可以一直保持有效。如果锁的拥有者无法续租,锁将在过期时间后自动释放,从而确保其他线程有机会获取锁。
MutiLock 原理
为了提高 Redis 的可用性,我们会搭建集群或者主从结构。以主从为例,当我们写命令时,写在主机上,主机会将数据同步给从机。但是假设在主机还没有来得及把数据写入到从机时,主机宕机,哨兵会发现主机宕机,并选举一个 slave 变成 master,而此时新的 master 中实际上并没有锁信息,此时锁信息就已经丢掉了。
为了解决这个问题,Redisson 提出了 MutiLock 锁。使用这把锁时,我们不使用主从结构,每个节点的地位都是一样的。这把锁加锁的逻辑需要写入到每一个主从节点上,只有所有的服务器都写入成功,才算是加锁成功。假设现在某个节点挂了,那么在获取锁的时候,只要有一个节点拿不到,都不能算是加锁成功,这样就保证了加锁的可靠性。