目录
为什么要有分布式锁
实现原理
死锁的情况以及死锁怎么办
分两步步设置key和过期时间的问题
业务执行时间超过锁过期时间
描述
解决方案
释放别人的锁
不好评估过期时间
一个线程抢到锁,其他线程在做什么
集群redis中可能会出现的问题
Redlock
前提
操作步骤
分布式锁分为两大类
我们在单机时,有单机锁去解决线程操作共享资源安全的问题。安全问题意思就是线程操作结果和你预期结果不符合。
我们现在是分布式系统,会有多个进程操作一个共享资源的情况,所以也会出现安全问题。这个时候我们就需要一个外部系统去实现互斥的能力。所以引入了分布式锁。这次我们主要讲redis实现分布式锁原理以及注意事项。
我们利用redis的setnx指令去实现互斥能力。
这个指令意思是set if not exits。如果key不存在,才去设置,如果存在则不设置。
案例:test为key,第一次设置成功,第二次不成功。
我们用del命令去释放锁。
案例: 释放锁(删除key)之后,setnx命令成功。
类比到分布式锁:客户端A抢占test这个锁,抢占成功。客户端B,抢占test这个锁则失败。如果客户端A执行完之后要及时释放锁。
死锁情况:如果客户端A抢占成功后执行逻辑出现异常(或是A服务挂了),没执行del操作,那锁就一直被占有,无法释放。其他客户端将无法拿到这个锁(无法setnx成功)。
解决办法:把锁设置过期时间。既然客户端A无法释放,我们就设定时间让他释放,到达时间key直接过期。
实例:设置key为test————设置过期时间10s————再次设置key为test
身为大聪明的你一眼就看出这是两步操作(1、设置key。2、设置过期时间)。有没有一种可能,我是说有没有一种可能设置完key之后,正在设置过期时间时程序挂了或是种种原因没执行。他们并不是原子操作,所以我们需要把设置key和过期时间变为原子操作。
案例:把设置key和过期时间变为原子操作。
如果A抢到了锁并且设置过期时间为10s。但是A业务执行了20s。
也就是说:
A执行完之前B拿到锁安全问题:锁过期了但是A仍在执行,这时B抢锁成功去执行自己的逻辑。这样仍会产生安全问题。
A执行完之后释放别人的锁:A本身有释放锁的操作,会错误的把B的锁给释放掉。
我们在设置key的时候value设置为一个唯一值(如UUID或是其他)
在释放锁之前判断value是否是自己设置的之后再删除。如:
if(redis.get("lock") == $uuid){ redis.del("lock") }
可是这样仍是两步操作:对比——删除。如果我们对比之后准备删除时B抢占到了锁,仍会出现误删情况。
所以我们仍然要一个原子操作。
这个时候我们就需要lua脚本了。让redis来执行,因为redis是单线程所以,知道脚本执行后才执行其他操作。
身为大聪明的你可能也想到了设置一个守护线程,监控业务运行和过期时间,如果快要过期了业务还在运行,则对之前设定的过期时间进行续签。这个守护线程专业术语叫做“看门狗”。
Java 技术栈中已经有一个库把这些工作都封装好了:Redisson。
线程A拿到锁后,线程B就一直通过io用setnx自旋拿锁。redisson实现自旋锁
如果有很多线程,就会造成很多线程自旋。
这个时候就要用到redis的订阅发布、阻塞队列。
(有待确定,有知道的小伙伴请补充)
主从复制redis中
如果A去master加锁成功之后还没同步到slave,锁就消失了。新的master中就没有加锁了。
那如何去解决呢?
这时候就要引入我们常听的(或者压根没听过)Redis 作者提出一种解决方案—— Redlock红锁
大概是这个样子去拿锁。
总结:
如果没有一半以上的则随机重试(会出现重试,直到出现返回一半以上的锁)
该算法对系统时钟做出了危险的假设(假设多个节点机器时钟都是一致的),如果不满足这些假设,锁就会失效。
1、类cas自旋,轮询询问。代表技术:redis、mysql
2、event事件通知。代表技术:zookeeper、etcd