分布式锁
在分布式环境中,为了保证业务数据的正常访问,防止出现重复请求的问题,会使用分布式锁来阻拦后续请求。我们先写一段有问题的业务代码:
分布式锁需要解决的问题
- 互斥性
- 安全性
- 死锁
- 容错
Redis 2.6版本之前想实现原子性需要借助两个方法
- setnx key value:如果key不存在,则创建并赋值返回1,如果存在返回0
- expire key time : time秒后销毁key(当time设置为0时候会直接释放该key)
那么为什么可以用这两种方法组合实现分布式锁呢?
我们可以把key理解为一个资源
当这个setnx key value返回结果为0时候即赋值不成功,那么可以理解为此时资源在被占用状态,其他线程无法占用资源并执行得到资源后的操作.
当这个setnx key value返回结果为1时候即赋值成功,那么可以理解为此时资源不在被占用状态,那么我们此时可以占用这个资源并执行得到资源后的操作(占用这个资源即填充资源使其他线程在尝试setnx key value返回值为0).
-
expire key time销毁资源相当于我用完资源后为了让别人可以获得这个资源(setnx key value返回值为1) 而进行的释放锁的过程
Redis2.6之后实现分布式锁的方式
由于上述方法中加锁和释放锁分别是原子的,但是两个过程组合到一起就不是原子的了,因此高并发情况下,原子性得不到满足,我们采用下面的方法去实现分布式锁
set key value ex|px nx|xx
eg: set locktarget 122325 ex 10 nx
- EX 即second:设置键的过期时间为second秒
- PX 即millisecond:设置键的过期时间为millisecond 毫秒
- NX:只在键不存在时,才对键进行设置操作(等同于setnx,分布式用这个)
set locktarget 122325 ex 10 nx即只有nx判断locktarget不存在才会进行赋值12325返回OK - XX:只在键已经存在时,才对键进行设置操作
-
SET操作成功完成时,返回OK,否则返回nil
上述情况同样仍然存在一个问题,那就是如果是多线程情况下,A到了过期时间,但是此时持久A的线程还没完成任务,这时候释放了锁,被B拿走了,那么A完成任务了再执行一个del(key)。但这时候线程B还没执行完,线程A实际上删除的是线程B加的锁。
怎么办呢?我们可以让获得锁的线程开启一个守护线程(跟母线程消亡时间一致),用来给快要过期的锁“续航”。当过去了29秒,线程A还没执行完,这时候守护线程会执行expire指令,为这把锁“续命”20秒。守护线程从第29秒开始执行,每20秒执行一次。( System.currentTimeMillis()
获取两个当前时间,再取差值) ===========》也就是redis后面的红锁 看门狗,详情可以看红锁、看门狗