一、Redisson可重入锁原理
1、可重入:利用hash结构记录线程id和重入次数。
2、可重试:利用信号量和PubSub功能实现等待、唤醒,获取
锁失败的重试机制。
3、超时续约:利用watchDog,每隔一段时间(releaseTime
/3),重置超时时间。
调用redisson.lock()方法会在redis中存储一个hash数据结构,key为锁的名称,value中的field为当前操作的线程id,value为锁重入的次数。
代码测试:
private RLock rLock=null;
@ApiOperation(value="测试分布式锁的可重入锁原理", notes="testRedisson02")
@GetMapping("/testRedisson02")
public String testRedisson02() throws InterruptedException {
//1、获取锁(可重入),并指定锁的名称
rLock=redissonClient.getLock("lock:testRedisson02");
//2、尝试获取锁,参数分别是:waitTime:获取锁的最大等待时间(期间会重试)
// leaseTime:锁自动释放时间 TimeUnit:时间单位
//tryLock(long waitTime, long leaseTime, TimeUnit unit)
boolean isLock=rLock.tryLock(1,100, TimeUnit.SECONDS);
//3、判断锁获取成功及释放
if(isLock){
try {
log.info("执行正常的业务02......");
this.testRedisson03();
}catch (Exception e){
log.info("锁获取异常02e:"+e);
}finally {
//锁未关闭,则手动释放锁
if(rLock.isLocked()){
rLock.unlock();
log.info("释放锁成功02");
}
}
}
return "true";
}
public void testRedisson03() throws InterruptedException {
//2、尝试获取锁,参数分别是:waitTime:获取锁的最大等待时间(期间会重试)
// leaseTime:锁自动释放时间 TimeUnit:时间单位
//tryLock(long waitTime, long leaseTime, TimeUnit unit)
boolean isLock=rLock.tryLock(1,100, TimeUnit.SECONDS);
//3、判断锁获取成功及释放
if(isLock){
try {
log.info("执行正常的业务03......");
}catch (Exception e){
log.info("锁获取异常03e:"+e);
}finally {
//锁未关闭,则手动释放锁
if(rLock.isLocked()){
rLock.unlock();
log.info("释放锁成功03");
}
}
}
}
输出结果:
具体分析如下:
在上面的testRedisson02方法中,当外层testRedisson02方法加锁之后,会获取当前的线程标识存入field字段,并将value+1;当内层testRedisson03方法再次加这个锁,会先判断当前线程与field中存的线程是否是一样的,如果是一样的,value+1;此时value为2。如果要解锁,先要判断锁是否是自己的(比对key和field字段),如果是,则value-1。
获取锁的Lua脚本源码:
return evalWriteAsync(getName(), LongCodec.INSTANCE, command,
"if (redis.call('exists', KEYS[1]) == 0) then " +
"redis.call('hincrby', 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]);",
释放锁的Lua脚本源码:
return evalWriteAsync(getName(), LongCodec.INSTANCE, RedisCommands.EVAL_BOOLEAN,
"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;",
二、总结
1、不可重入Redis分布式锁:
原理:利用setnx的互斥性;利用ex避免死锁;释放锁时判
断线程标识。
缺陷:不可重入、无法重试、锁超时失效。
2、可重入的Redis分布式锁:
原理:利用hash结构,记录线程标示和重入次数;利用
watchDog延续锁时间;利用信号量控制锁重试等待。
缺陷:redis宕机引起锁失效问题。--主从一致性问题
3、RedissonmultiLock:
原理:多个独立的Redis节点,必须在所有节点都获取重入
锁,才算获取锁成功。
缺陷:运维成本高、实现复杂。
更多详细资料,请关注个人微信公众号或搜索“程序猿小杨”添加。