单机和集群下的分布式锁
Redis分布式锁实现
我们通常使用的synchronized或者Lock都是线程锁,对同一个JVM进程内的多个线程有效。因为锁的本质 是内存中存放一个标记,记录获取锁的线程是谁,这个标记对每个线程都可见。然而我们启动的多个订单服务,就是多个JVM,内存中的锁显然是不共享的,每个JVM进程都有自己的 锁,自然无法保证线程的互斥了,这个时候我们就需要使用到分布式锁了。常用的有三种解决方案:1.基于数据库实现 2.基于zookeeper的临时序列化节点实现 3.redis实现。本文我们介绍的就是redis的实现方式。
实现分布式锁要满足3点:多进程可见,互斥,可重入。
1) 多进程可见
redis本身就是基于JVM之外的,因此满足多进程可见的要求。
2) 互斥
即同一时间只能有一个进程获取锁标记,我们可以通过redis的setnx实现,只有第一次执行的才会成功并返回1,其它情况返回0。
setnx(set not exist)若不存在则设置,存在则不做操作。此命令为原子操作,避免在set操作完成后没设置超时时间就崩溃,所以要使用原子操作。
1.单机Redis的分布式锁实现
1.1 实现原理
(1)setnx key value:如果有这把锁,则不修改,此时返回0,若没有,则设为key,value,此时返回1。在为1的时候才可以继续向下执行
(2)expire:设置过期时间,以防系统业务中断造成锁永远停留在redis里造成死锁
(3)delete:执行完毕后删除
1.2 思考:
问:当锁的超时时间为10s,但是业务执行15s,此时锁已经过期
答:在设置锁之后,立即开启另一个线程(watchdog 看门狗,可以通过装饰器实现),每隔超时时间的1/3秒就给锁把超时时间续上
问:锁在程序没运行完过期,锁被其他线程夺取,然后其他线程上锁,第一个程序运行到最后又把锁删掉,以此类推(锁永久失效的问题)(比如没有看门狗机制或者看门狗机制崩溃)
答:先生成uuid,放入锁里,之后取的时候,如果value == uuid才能删除(要么你就过期,要么就只能删除和自己手上的uuid匹配的)
2.集群Redis的分布式锁实现
RedLock
为什么要使用redlock,在redlock算法中要给一半以上的客户端加锁才算加锁成功。
有什么好处呢,如果给一半以上的结点加锁后,若当中有一个节点挂掉了,其他客户端也不能给一半以上的结点加锁了,这样就不能获取锁,这样就比单机可用性高。
2.1 RedLock实现原理
(1)获取当前时间的毫秒数T1。
(2)按顺序依次向N个Redis节点执行获取锁的操作。这个获取锁的操作和上一篇中基于单Redis节点获取锁的过程相同。包括唯一UUID作为Value以及锁的过期时间(expireTime)。为了保证在某个在某个Redis节点不可用的时候算法能够继续运行,这个获取锁的操作还需要一个超时时间。它应该远小于锁的过期时间。客户端向某个Redis节点获取锁失败后,应立即尝试下一个Redis节点。这里失败包括Redis节点不可用或者该Redis节点上的锁已经被其他客户端持有。
(3)计算整个获取锁过程的总耗时。即当前时间减去第一步记录的时间。计算公司为T2=now()- T1。如果客户端从大多数Redis节点(>N/2 +1)成功获取到锁。并且获取锁总共消耗的时间小于锁的过期时间(即T2<expireTime)。则认为客户端获取锁成功,否则,认为获取锁失败
(4)如果获取锁成功,需要重新计算锁的过期时间。它等于最初锁的有效时间减去第三步计算出来获取锁消耗的时间,即expireTime - T2
(5)如果最终获取锁失败,那么客户端立即向所有Redis节点发起释放锁的操作。(单机下释放锁的逻辑一样)
2.2 思考
问:如果redis主从架构,主节点加完锁后还没同步到从节点之前就挂了,那么选举的从节点没有该锁
答:虽然说RedLock算法可以解决单点Redis分布式锁的安全性问题,但如果集群中有节点发生崩溃重启,还是会锁的安全性有影响的。具体出现问题的场景如下:
假设一共有5个Redis节点:A, B, C, D, E。设想发生了如下的事件序列:
# 客户端1成功锁住了A, B, C,获取锁成功(但D和E没有锁住)
# 节点C崩溃重启了,但客户端1在C上加的锁没有持久化下来,丢失了
# 节点C重启后,客户端2锁住了C, D, E,获取锁成功
这样,客户端1和客户端2同时获得了锁(针对同一资源)。针对这样场景,解决方式也很简单,也就是让Redis崩溃后延迟重启,并且这个延迟时间大于锁的过期时间就好。这样等节点重启后,所有节点上的锁都已经失效了。(C挂掉后延迟重启,延迟时间大于锁的过期时间,释放锁后客户端重新竞争ABDE,C重新上线后为闲置状态,同时加入客户端的竞争中)
问:集群模式下,redlock算法,如果两个客户端,都在争取一半以上的锁资源,造成竞争循环,怎么办
答:设置竞争超时次数和重新竞争的频率(不同的频率)