分布式锁方案
-
redis锁
和Memcached的方式类似,利用Redis的setnx命令。此命令同样是原子性操作,只有在key不存在的情况下,才能set成功。 -
Zookeeper分布式锁
利用Zookeeper的顺序临时节点,来实现分布式锁和等待队列。Zookeeper设计的初衷,就是为了实现分布式锁服务的。
Redis分布式锁
单节点redis锁
redis使用setnx命令实现分布式锁,只有在key不存在的时候才会设置值,表示锁获取成功。
超时时间设置
如果当前获取锁的进程挂掉了,由于当前进程没有释放锁,就会造成死锁。所以要给锁加一个过期时间。如果锁超过了过期时间还没被释放,则redis会强制删除该key,避免了死锁情况发生。
释放锁前先检查
给锁设置过期时间可以避免死锁发生,但是过期时间的设置可能破坏锁的互斥性。假如进程A获取了锁,设置过期时间是1s,由于某些原因到了过期时间A还没有处理完(数据库网络延迟等等),这时锁就被自动释放了,其他进程可以获取锁,破坏了锁的互斥性。(可以使用锁续时,延长进程A锁的过期时间)
锁被其他进程释放
上面的情况也存在进程A释放进程B锁的情况,因为key都是一样的,解决办法可以给不同进程设置不用的标志,比如userid,保证锁不互相干扰。同时可以结合幂等性保证,保证同一个userid只被处理一次。
Redis宕机造成的问题
Redis宕机后内存中的锁不复存在,所以任何进程都可以获取这个锁。(使用Redis的持久化机制并不能解决这个问题,因为有可能出现进程A的锁没有被持久化就宕机的情况),这些问题本质上还是单点的问题,想要解决这些问题,就要使用多个Redis,其中某一个宕机后不影响其他redis服务器锁的正确性。(使用Redis集群并没有解决这个问题,因为Redis集群也存在主从复制)
RedLock
RedLock是多个Redis实例的分布式锁实现,多个Redis避免了单点故障带来的不可靠性。和redis集群不同的是,RedLock是依据法团准则写入锁,写和读达到(N/2 +1),也就是一半以上的redis后才算成功(这个和Poxas有点类似,https://www.jianshu.com/p/42c524920b05)。
假如有五个redis服务器,RedLock获取步骤如下:
- 进程A依次向每个Redis服务器获取锁(setnx),至少获取一半以上的才算成功
- 如果进程A设置成功,进程B是没有办法获取一半的锁,所以进程B设置失败,直到进程A释放锁
延时重启
假如有R1、R2、R3、R4、R5五个Redis服务器,进程A获取了R1、R2、R3三把锁,此时R1宕机了如何处理?显然不能把R1立即重启,因为立即重启会使进程B立即获取R1、R4、R5,的锁,破坏了锁的互斥性。需要找合适的时机让R1恢复,选的时机是统计当前时刻所有机器上锁的最大生存时间,这样就可以保证R1恢复访问后,锁已经失效了。(后面会接触一个watchdog,还没有了解加了watchdog以后,R1是如何监控MAX TTL)。
RedLock锁的过期时间
不能使用最大时间戳来当作过期时间戳,使用理论时间戳(即开始设置时,本地机器的时间戳+TTL)是最保险的方案,因为他肯定是最小的。
假如有R1、R2、R3、R4、R5五个Redis服务器,进程A想设置一个10s的锁,当前时间戳是12300,R1的过期时间是12310,R2过期时间是
12312,R2的过期时间是12312。如果使用最大的过期时间来当做锁的过期时间(12312),当时间戳是12311的时候,程序里认为锁还没有过期,但是R1已经释放,进程B又可以获取锁了,破坏了锁的互斥性。
watchdog
当进程还没有完成任务,锁就达到了过期时间(被释放)。Java redisson的解决办法是使用watchdog续租机制,当锁的有效期是10s,第9s进程还没有完成逻辑,watchdog会自动续约锁的过期时间。(是一个守护进程,如果进程A宕机,看门狗也就没有了,所以不会出现锁资源不释放)。
可能出现的问题
RedLock是依赖服务器时钟的,如果服务器时间跳变,可能意味着某一个锁立马过期了。其他进程同样可以获取锁。当然,这种问题发生的概率可能是足够低的,能不能承受这样的情况带来的损失决定着是否采用Redis来实现分布式锁。
网友评论