算法需要具备的三大特性

● 安全属性:独享(相互排斥)。在任意一个时刻,只有一个客户端持有锁。
● 活性A:无死锁,即便持有锁的客户端崩溃,锁仍然可以被获取。
● 活性B:容错,只要大部分Redis节点都活着,客户端就可以获取和释放锁.

单节点情况

客户端A向服务端请求获取锁:

set key_ rand_A NX PX 300000

# key_ 代表锁名称,rand_A是A客户端随机生成的密钥,NX代表只有锁不存在的时候,才能获取(设置),PX是锁的自动过期时间(过期锁自动释放机制)

客户端A设置key_成功后,其他客户端将无法设置成功(因为有NX保护)

客户端A释放锁:

if redis.call("get",KEYS[1]) == ARGV[1] then          
    return redis.call("del",KEYS[1])

# 判断释放锁的客户端的密钥是否跟"首次获取锁的客户端的密钥"一致,如果一致才能确定释放锁操作的客户端是A,才能释放成功

多节点情况

问题:

1、在主从模式下:客户端A从master节点获取锁,master将锁同步到slave时:master宕机,slave还没获得从master同步的锁;此时slave升级为master,客户端B从新master中获取了锁

2、网络延迟长:客户端A获取锁,因网络延时,客户端A长时间阻塞 ,锁自动过期,这时客户端B获取锁(B判断锁过期),与此同时客户端A的网络恢复正常(A仍在获取内容),这就导致两个客户端可同时访问共享资源。

RedLock 算法

1、客户端A首先记下开始前的当前时间戳 M

2、按顺序依次向N个Redis节点获取锁(跟单节点一样,通过SETNX方式获取)

SET K a_random NX PX 300000 (10ms)     # 节点A   

SET K b_random NX PX 300000 (10ms)     # 节点B

SET K c_random NX PX 300000 (10ms)     # 节点C 

SET K d_random NX PX 300000 (10ms)     # 节点D

SET K e_random NX PX 300000 (10ms)     # 节点E

3、计算获取锁总消耗时间 P = 50s(获取锁成功后的时间戳 - M)

4、判断当超过一半(>= (N / 2) + 1)的Redis节点获取锁(设置K)成功,并且总消耗时间P小于锁的有效时间(300000),当前客户端才算获取锁成功。

若获取锁成功:锁的有效时间应该重新计算 = 最初锁的有效时间(300000) - 获取锁总消耗时间 P 

若获取锁失败:那么客户端应该立即向所有Redis节点发起释放锁的操作

RedLock 算法问题

1、持久化问题:

假设一共有5个Redis节点:A, B, C, D, E:
客户端1成功锁住了A, B, C,获取锁成功,但D和E没有锁住。
节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了。
节点C重启后,客户端2锁住了C, D, E,获取锁成功。
这样,客户端1和客户端2同时获得了锁(针对同一资源)。

2、客户端长时间阻塞,导致获得的锁释放,访问的共享资源不受保护的问题。

3、Redlock算法对时钟依赖性太强, 若某个节点中发生时间跳跃(系统时间戳不正确),也可能会引此而引发锁安全性问题。