Redisson主要解决一下问题
- 重入问题:重入问题是指 获得锁的线程可以再次进入到相同的锁的代码块中,可重入锁的意义在于防止死锁,比如HashTable这样的代码中,他的方法都是使用synchronized修饰的,假如他在一个方法内,调用另一个方法,那么此时如果是不可重入的,不就死锁了吗?所以可重入锁他的主要意义是防止死锁,我们的synchronized和Lock锁都是可重入的。
- 不可重试:是指目前的分布式只能尝试一次,我们认为合理的情况是:当线程在获得锁失败后,他应该能再次尝试获得锁。
- 超时释放:我们在加锁时增加了过期时间,这样的我们可以防止死锁,但是如果卡顿的时间超长,虽然我们采用了lua表达式防止删锁的时候,误删别人的锁,但是毕竟没有锁住,有安全隐患
- 主从一致性:如果Redis提供了主从集群,当我们向集群写数据时,主机需要异步的将数据同步给从机,而万一在同步过去之前,主机宕机了,就会出现死锁问题。
一、分析可重入
1.原理
在Lock锁中,他是借助于底层的一个voaltile的一个state变量来记录重入的状态的,比如当前没有人持有这把锁,那么state=0,假如有人持有这把锁,那么state=1,如果持有这把锁的人再次持有这把锁,那么state就会+1 ,如果是对于synchronized而言,他在c语言代码中会有一个count,原理和state类似,也是重入一次就加一,释放一次就-1 ,直到减少成0 时,表示当前这把锁没有被人持有。
在redission中,也支持可重入锁
在分布式锁中,他采用hash结构用来存储锁,其中大key表示表示这把锁是否存在,用小key表示当前这把锁被哪个线程持有,所以接下来我们一起分析一下当前的这个lua表达式
2、获取锁源码
我们点进去Redisson获取锁的源码
可以发现如下代码
也就是下面的代码
-- 判断是否存在
if ((redis.call('exists', KEYS[1]) == 0)-- 存在,判断是否是自己的锁 or (redis.call('hexists', KEYS[1], ARGV[2]) == 1)) then-- 不存在,获取锁或者是自己的锁,次数加1redis.call('hincrby', KEYS[1], ARGV[2], 1);-- 设置过期时间redis.call('pexpire', KEYS[1], ARGV[1]);-- 返回nilreturn nil;
end ;
-- 返回剩余过期时间
return redis.call('pttl', KEYS[1]);
1.这个地方一共有3个参数3
KEYS[1] : 锁名称
ARGV[1]: 锁失效时间
ARGV[2]: id + ":" + threadId; 锁的小key
exists: 判断数据是否存在 name:是lock是否存在,如果==0,就表示当前这把锁不存在
redis.call('hincrby', KEYS[1], ARGV[2], 1);
直接获取锁,并且计数器置1
如果当前这把锁存在,则第一个条件不满足,再判断
redis.call('hexists', KEYS[1], ARGV[2]) == 1
此时需要通过大key+小key判断当前这把锁是否是属于自己的,如果是自己的,则进行
redis.call('hincrby', KEYS[1], ARGV[2], 1)
将当前这个锁的value进行+1 ,redis.call('pexpire', KEYS[1], ARGV[1]); 然后再对其设置过期时间,如果以上两个条件都不满足,则表示当前这把锁抢锁失败,最后返回pttl,即为当前这把锁的失效时间
3、释放锁源码
-- 判断锁是否存在
if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) thenreturn nil;
end;-- 存在,计数器-1
local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1);-- 如果计数器大于0,则设置过期时间
if (counter > 0) thenredis.call('pexpire', KEYS[1], ARGV[2]);return 0;
else-- 否,则释放锁redis.call('del', KEYS[1]);-- 同时发布一个消息到指定channel,通知锁已经释放redis.call('publish', KEYS[2], ARGV[1]);return 1;
end;return nil;
首先判断锁是否存在,存在则计数器-1,如果计数器大于0,则刷新过期时间,否则释放锁,同时发布消息,通知已释放锁
4、流程图
整个流程也就是这个流程图