【Redis笔记】使用Redisson实现可重入锁

Redisson

官方github网站:https://github.com/redisson/redisson
Redisson官网:https://redisson.org/

依赖引入

pom.xml文件中写入

<dependency>
	<groupId>org.redissongroupId>
	<artifactId>redissonartifactId>
	<version>3.13.6version>
dependency>

配置Redisson客户端

  1. 使用yml配置文件
  2. 导入Redisson起步依赖
  3. 自定义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),重置超时时间

获取锁

  1. 尝试获取锁
  2. 判断ttl是否为null
  • 如果为null,判断leaseTime是否为-1
  • leaseTime为-1,开启看门狗机制watchDog,返回true;不为-1,直接返回true,不开启看门狗。
  1. ttl不为null,判断剩余等待时间是否大于0,如果小于0,返回false
  2. 剩余等待时间大于0,则订阅并等待别的线程释放锁的信号;
  3. 判断等待时间是否超时,已经超时,则返回false
  4. 若未超时,则再去尝试获取锁,即步骤1

释放锁

  1. 尝试释放锁
  2. 判断是否成功
  3. 如果失败,可能已经超时释放,记录异常,并结束
  4. 如果释放成功,发送释放锁消息(对应获取锁的步骤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节点,必须在所有节点都获取重入锁,才算获取锁成功

你可能感兴趣的:(Redis,redis,笔记,数据库)