Redission 提供了一个 RLock 锁,这个 RLock 继承自 juc 的 Lock 接口。提供了超时、中断、尝试等操作。是互斥锁、可重入锁。
基本原理:
RLock 底层是使用的 Redis 中的 Hash 数据结构。
其中 Hash 的 key 是锁的名字
其中 Hash 的 Field 是客户端 id
其中 Hash 的 Value 是线程重入次数
客户端 id:
分为两个部分:
● 每次在实例化 Redission 对象的时候会生成一个 id,是 UUID.randomUUID()
● 当前线程 id
为了实现加锁的原子性,Redission 使用 Lua 脚本的形式进行加锁。
加锁的可重入锁逻辑:
if (redis.call(‘exists’, KEYS[1]) == 0) then
redis.call(‘hset’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
if (redis.call(‘hexists’, KEYS[1], ARGV[2]) == 1) then
redis.call(‘hincrby’, KEYS[1], ARGV[2], 1);
redis.call(‘pexpire’, KEYS[1], ARGV[1]);
return nil;
end;
return redis.call(‘pttl’, KEYS[1]);
其中KEYS[1]是Hash的key也就是RLock的名字,ARGV[2]是客户端id,ARGV[1]是Hash的生存时间TTL。 首先判断KEYS[1]对应的Hash是否存在,如果不存在直接创建一个新的Hash,对应的filed值为1也就是说第一次重入次数为1,并且设置超时时间,最终返回null; 如果Hash已经存在且filed为ARGV[2]的项也存在,说明是该线程递归进入该锁,需要对该filed增加一次重入次数,并且更新超时时间,最终返回null; 如果当前锁已经存在,且不是当前线程持有的,就会返回当前锁的TTL。
锁续期逻辑
如果锁设置了持有锁的超时时间,在超时后会进行锁的释放,如果获取锁的时候不指定持有锁的时间,那么默认获取锁30s后超时。为了防止任务没有执行完就释放锁,Redisson使用一个守护线程(看门狗任务)定时刷新(超时时间的 1/3, 默认是10s,也就是每10s续约30s,直到线程自己释放)这个锁超时时间进行续约,也就是只要这个锁被获取了,则力保这个锁一直不超时,除非获取锁的线程主动释放。由于获取到锁和这个续命任务的守护线程是在同一个线程的,当获取锁的线程挂掉了,意味着刷新任务的线程也会停止执行,就不会再刷新锁的超时时间。
解锁逻辑:
if (redis.call(‘exists’, KEYS[1]) == 0) then
redis.call(‘publish’, KEYS[2], ARGV[1]);
return 1;
end;
if (redis.call(‘hexists’, KEYS[1], ARGV[3]) == 0) then
return nil;
end;
local counter = redis.call(‘hincrby’, KEYS[1], ARGV[3], -1);
if (counter > 0) then
redis.call(‘pexpire’, KEYS[1], ARGV[2]);
return 0;
else
redis.call(‘del’, KEYS[1]);
redis.call(‘publish’, KEYS[2], ARGV[1]);
return 1;
end;
return nil;
首先判断锁是否是存在的,如果不存在直接返回nil; 如果该线程持有锁,则对当前的重入值-1,如果计算完后大于0,重新设置超时持有实践返回0; 如果算完不大于0,删除这个Hash,并且进行广播,通知watch dog停止进行刷新,并且 返回1.
缺点
- Redisson没有办法解决节点宕机问题,不能达到ZK的一致性;
- Redison的机制比较复杂,如果对其底层实现不是很熟悉会出现很多预期外的
Redisson红锁实现
Redisson锁并没有解决主从节点切换可能导致重复加锁的问题,即某个客户端在Master节点加锁,此时主节点宕机,由于主从之间异步复制,从节点没有来得及复制,此时选举出新的Master后并没有之前的锁,另外一个客户端要对同一个锁进行操作时可以直接加锁,那么有两个客户端同时持有一把锁,这样锁住的共享资源会被重复读取,造成混乱。
Redisson提供了RedissonRedLock锁实现了RedLock,需要同时使用多个独立的Redis实例分别进行加锁,只有超过一半的锁加锁成功,则认为是成功加锁。