redis一般锁和分布式锁的原理简析

redis分布式锁的实现方式

redis中存在一个指令,setnx,即set if not exist,如果此时key不存在则返回1,并对key赋值;如果key已经在redis中存在,则直接返回0,并且啥也不干。

一般锁原理

p1(线程1) setnx lock value返回1说明key不存在,获取锁成功
p2(线程2)也执行 setnx lock value返回0,获取锁失败,所以p2需要不断轮询setnx直到获取1
正常情况下p1 执行完了,del lock,即释放锁,然后p2获得锁

死锁问题与解决

上面场景考虑一种情况,如果进程p1获得锁后,断开了与 Redis 的连接(可能是p1挂掉,或者网络中断),如果没有有效的释放锁的机制,那么其p2会处于一直等待的状态,即出现“死锁”。

解决死锁方案一
上面在使用 SETNX 获得锁时,我们将键 lock 的值设置为锁的有效时间(即current+locktime),进程获得锁后,其他进程还会不断的检测锁是否已超时,如果超时,那么等待的进程也将有机会获得锁。

p1设置 setnx  lock current+locktime
因为 p1挂掉了,p1不会自动删除lock
p2和p3通过循环get获取value值,并和当前current值比较,lock过期状态可能被p2和p3同时获取到,所以p2,p3可能先后执行删除lock并设置新的锁 问题:p2和P3同时获得了锁
解决办法
p2 p3在获取到lockfool的过期状态时并不立刻设置新的锁,而是采用getSet,并比较get到的时间是否小于当前时间,如果是那么设置新的锁,否则说明别的线程已经设置新的锁,只能再次等待。
问题:假设p2先getSet,p3再getSet,那么p2的锁时间会被篡改。这个其实可以忍受
https://blog.csdn.net/lihao21/article/details/49104695

参考官网:https://redis.io/commands/setnx
带有java实现代码:https://juejin.im/post/5b737b9b518825613d3894f4  实现和代码

分布式与集群场景下的问题

上面方案看似比较完美了,但是在redis为集群和分布式场景下可能有问题

机器宕机问题:1master和1slave,lock对应的key刚刚写入master还没有同步给slave,master宕机,slave切换为master,因为key未同步,所以另一线程依旧可以获取锁。
分布式问题:redis有多个实例,并且不是在同一个集群中,那么如果请求一次有返回1就表示成功肯定不行,因为假设5个master,那么每个master都可以提供一个锁,显然是不对的哈

集群Redis的分布式锁

在Redis的分布式环境中,Redis 的作者提供了RedLock 的算法来实现一个分布式锁。
4.1 加锁
RedLock算法加锁步骤如下

  1. 获取当前Unix时间,以毫秒为单位。
  2. 依次尝试从N个实例,使用相同的key和随机值获取锁。在步骤2,当向Redis设置锁时,客户端应该设置一个网络连接和响应超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在5-50毫秒之间。这样可以避免服务器端Redis已经挂掉的情况下,客户端还在死死地等待响应结果。如果服务器端没有在规定时间内响应,客户端应该尽快尝试另外一个Redis实例。
  3. 客户端使用当前时间减去开始获取锁时间(步骤1记录的时间)就得到获取锁使用的时间。当且仅当从大多数(这里是3个节点)的Redis节点都取到锁,并且使用的时间小于锁失效时间时,锁才算获取成功。
  4. 如果取到了锁,key的真正有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
  5. 如果因为某些原因,获取锁失败(没有在至少N/2+1个Redis实例取到锁或者取锁时间已经超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功)。

4.2 解锁
向所有的Redis实例发送释放锁命令即可,不用关心之前有没有从Redis实例成功获取到锁.

借鉴链接:https://juejin.im/post/5b737b9b518825613d3894f4

你可能感兴趣的:(redis)