概述
通过redis实现分布式锁是一种比较常见的方式,分布式锁一般使用的是setnx(set if not exist)指令,只允许被一个客户端占用,用完后调用del指令释放。
问题一
如果在执行逻辑的过程中,服务器崩溃,或者发生了未知的异常,可能会导致del指令无法被执行,这样会陷入死锁。
解决方法
我们在拿到锁的时候,再给锁加上一个过期时间(expire 指令),在保证出现异常的时候自动释放。
问题二
如果在setnx 和 expire指令之间进程挂掉了,会导致expire得不到执行,造成死锁,
解决方法
需要setnx和expire这两条指令一起执行,由于expire依赖于setnx的执行结果,如果setnx没有抢到锁,expire是不能执行的,所以不能用redis的事务来解决。
最终这个问题在redis2.8版本得到解决,作者在set指令中加了扩展参数,使setnx和expire指令一起执行。
超时问题
如果在加锁和释放锁的之间的逻辑执行太长,超出了锁的超时限制,因为锁过期了,第二个线程持有这把锁,之后第一个线程执行完业务,把锁释放了。
解决方法
在set指令的value值设置一个随机数,释放锁时先匹配随机数是否一致,然后在删除key,匹配value和删除key不是一个原子操作,可以使用lua脚本来保证原子性,
if redis.call("get",KEYS[1]) == ARGV[1] then
return redis.call("del",KEYS[1])
else
return 0
end
redis的锁续期
可以通过redission来实现分布式锁,利用redisson的watchdog机制,定时给重置锁的超时时间,流程如下:
image.png
依赖的jar包
<dependency>
<groupId>org.redisson</groupId>
<artifactId>redisson</artifactId>
<version>3.12.4</version>
</dependency>
调用方法
public static void main(String[] args) {
RedissonClient redisson = Redisson.create();
RLock lock = redisson.getLock("lock");
lock.lock(1, TimeUnit.MINUTES);
}
在调用RLock 的lock方法时,可通过leaseTime属性设置锁持有的最大时间,如果超过这个设置的最大时间,会释放锁。
网友评论