开源项目前导 Redisson是什么?

首先是为什么我们需要用到Redisson

RedisTemplate-》误删锁和原子性

我们用RedisTemplate的话 用lua脚本 可以解决原子性的问题

误删锁:

1.然后通过UUID+线程ID可以解决 分布式情况下不同机器误删锁的情况

(因为不同服务 可能线程ID冲突)

2.在释放锁的时候,先判断该锁是否是自己的,也就是判断value。

        1.如果该key对应的value是自己存进去的那个value,则释放锁。

        2.否则不释放锁,防止锁误删。

但是其实 还有一些问题没有得到解决

1.重入问题:重入指获取锁的线程再次进入相同锁的代码块中,可以自动获取锁,从而防止了死锁的出现。常见的synchronized和Lock锁都是可重入的。

2.不可重试:获取锁只尝试一次就返回false,没有重试机制。而我们希望的是,获取锁失败后可以尝试再次获取锁。

3.超时释放:在加锁的时候添加了过期时间,这样有可能锁是因为超时释放,虽然使用Lua表达式解决了误删,但是代码块因为锁超时释放而没有锁住代码块,难免出现安全隐患。

4.主从一致性:Redis在集群情况下,向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。

所以需要用到Redisson了

Redisson的可重入锁设计与jdk中的Lock锁设计思路差不多。

可以参考我的文章

深入理解 JUC 的 ReentrantReadWriteLock 解锁过程-CSDN博客

深入理解 JUC 的 ReentrantLock 加锁和解锁流程-CSDN博客

jdk的Lock底层使用一个voaltile的一个state变量记录重入的状态。当没有人持有这把锁,state变量为0,当有人持有这把锁,state的变量为1,当持有这把锁的人再次持有这把锁,那么state的值就会+1。如果是对于synchronized而言,他在c语言代码中会有一个count,原理和state类似,也是重入一次就加一,释放一次就-1 ,直到减少成0 时,表示当前这把锁没有被人持有。

在Redisson中,不再用简单的key-value来实现分布式锁。而是使用key-hashMap来实现分布式锁,hashMap中也是key-value组合,key为表示哪个线程持有这把锁,value为锁的重入次数。

因此,获取锁的流程也会有所改变。

线程A进来获取锁,首先判断锁是否存在,如果不存在,那么获取锁,并添加自己的线程标识,并设置锁的有效期,随后执行业务。

在执行业务的时候再次获取该锁,首先也是判断锁是否存在,很明显,锁已经存在了,那么判断锁的标识是否是当前线程的(也就是判断这把锁是不是自己持有了),如果否,证明锁是被其他线程占有,获取锁失败。如果是,需要可重入,则重入次数+1,并重置锁的有效期,执行相应业务。

这里的业务执行完毕之后,首先判断锁是否是自己的(防止线程阻塞导致锁过期,因而释放掉其他线程的锁,也就是防止锁误删情况),如果是,那么锁的重入次数-1,接着判断锁的重入次数是否为0,如果为0,证明是已经到了最外层,就可以把锁释放掉了。
 

同时,这一整个流程需要保证原子性,因此需要用Lua脚本。

获取锁的lua脚本示例

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脚本示例

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;

鸣谢

【Redis】4.万字文章带你深入Redisson与源码解读(建议收藏)_redisson 源码精读-CSDN博客

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值