Redisson 实现 redis 分布式锁 及Redisson 源码

redis分布式锁加锁原则及注意事项:

加锁原则: 互斥性。在任意时刻,只有一个客户端能持有锁。

不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。

具有容错性。只要大部分的Redis节点正常运行,客户端就可以加锁和解锁。

解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能把别人加的锁给解了。

主要参数: key:唯一值 加锁主键

value:key 对应的值 也要保证唯一 在解锁的时间使用 保证谁加的锁 只能谁来解

失效时间(指key): 防止死锁

超时补偿:执行业务时间大于初始化超时时间时增加超时时间 如果没有补时存在的问题:线程A 加锁过超时时间 业务并没有执行完 此时若没有超时补时 线程B就可以获取当前

key 对应的锁 ,这时就会导致同一时间多个线程持有锁操作业务, 另一个隐患是如果解锁的时候不做value唯一值的判断,可能B线程加的锁被A线程释放

Redisson 加锁执行流程图

加锁lua脚本执行说明


<T> RFuture<T> tryLockInnerAsync(long leaseTime, TimeUnit unit, long threadId, RedisStrictCommand<T> command) {
    internalLockLeaseTime = unit.toMillis(leaseTime);

    return commandExecutor.evalWriteAsync(getName(), LongCodec.INSTANCE, command,
              "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]);",
                Collections.<Object>singletonList(getName()), internalLockLeaseTime, getLockName(threadId));
}
复制代码

加锁lua 脚本执行流程说明

如果缓存中的key不存在,则执行 hset 命令(hset key UUID+threadId 1) if (redis.call('exists', KEYS[1]) == 0) then redis.call('hset', KEYS[1], ARGV[2], 1);

然后通过 pexpire 命令设置锁的过期时间(即锁的租约时间) redis.call('pexpire', KEYS[1], ARGV[1]);

返回空值 nil ,表示获取锁成功 return nil; end;

如果key已经存在,并且value也匹配,表示是当前线程持有的锁,则执行 hincrby 命令,重入次数加(支持可重入),并且设置失效时间 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;

如果key已经存在,但是value不匹配,说明锁已经被其他线程持有,通过 pttl 命令获取锁的剩余存活时间并返回,至此获取锁失败 return redis.call('pttl', KEYS[1]);

Collections.singletonList(this.getName()), new Object[]{this.internalLockLeaseTime, this.getLockName(threadId)});

参数备注:

end 代表一种情况的结束

KEYS[1] this.getName() key 值

ARGV[1] this.internalLockLeaseTime

ARGV[2] this.getLockName(threadId)

Redisson 解锁执行逻辑

    return commandExecutor.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;",
            Arrays.<Object>asList(getName(), getChannelName()), LockPubSub.UNLOCK_MESSAGE, internalLockLeaseTime, getLockName(threadId));

}
复制代码

解锁lua脚本执行说明

如果缓存中的key不存在,并发布解锁消息,返回1 if (redis.call('exists', KEYS[1]) == 0) then redis.call('publish', KEYS[2], ARGV[1]); return 1; end;

如果分布式锁存在,但是value不匹配,表示锁已经被其他线程占用,无权释放锁,那么直接返回空值(解铃还须系铃人) if (redis.call('hexists', KEYS[1], ARGV[3]) == 0) then return nil;end;

如果value匹配,则就是当前线程占有分布式锁,那么将重入次数减1 local counter = redis.call('hincrby', KEYS[1], ARGV[3], -1); 重入次数减1后的值如果大于0,表示分布式锁有重入过,那么只能更新失效时间,还不能删除 if (counter > 0) then redis.call('pexpire', KEYS[1], ARGV[2]); return 0; 重入次数减1后的值如果为0,这时就可以删除这个KEY,并发布解锁消息,返回1 else redis.call('del', KEYS[1]); redis.call('publish', KEYS[2], ARGV[1]); return 1; end; return nil;

类比加锁参数 Arrays.asList(this.getName(), this.getChannelName()), new Object[]{LockPubSub.unlockMessage, this.internalLockLeaseTime, this.getLockName(threadId)});

发布订阅

超时补时


作者:春夏秋冬又一春酱
链接:https://juejin.cn/post/7057419255557390349
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值