Redis分布式锁怎么玩(中)
上篇聊到了Redis单节点实现分布式锁的逻辑,较为简单采用set命令加锁以及Lua脚本解锁就可以实现,但是单机没有高可用的特性,可能因为各种故障宕机这时Redis实现的分布式锁将无法获取锁,所以Redis需要选取集群实现不论是哪种集群如主从、主从-哨兵,切片集群这些都会面临一个问题故障转移,当master节点故障后集群内部通过选举的方式从众多salve节点中获取新的master节点,然后进行主从同步,恰恰就是这时会出现问题,因为主从复制是异步的,锁在主从复制过程中就可能丧失安全性,场景如下
- 客户端1从Redis集群中master节点获取锁并且成功
- master宕机,这时master的数据还未同步到slave节点
- 从slave节点中选举master节点
- 客户端2向master节点获取锁并且成功
这时客户端1和客户端2获取了相同资源的锁,锁的安全性得不到保证,Redis针对这种场景提出了一种解决方案RedLock红锁。
解决方案—RedLock红锁
红锁官方文档位置https://redis.io/docs/reference/patterns/distributed-locks/,Redis文档指出如果我们需要使用RedLock那幺需要部署N个实例节点,注意这N个节点并不是主从关系,也不是任何集群关系,相当于N个独立的实例,官方推荐实例个数N=5
如何实现RedLock
- 客户端获取锁之前先获取当前的时间戳T1。
- 客户端依次向这N个实例加锁,采用set ex ** nx命令,每个请求都会设置超时时间,这个超时时间将远小于锁的过期时间,如果某一个实例加锁失败(实例宕机、锁被其它客户端持有、网络超时等等),那幺将立即向下一个实例加锁。
- 对所有的实例完成加锁操作后得到时间戳T2,计算加锁成功的个数,如果成功个数大于等于N/2+1,并且T2-T1小于键值的超时时间,这时加锁成功,如果有任意一条不符合则加锁失败。
- 加锁成功,操作共享资源。
- 加锁失败向所有节点释放锁,不论实例有没有加锁成功,释放锁采用Lua脚本完成。
RedLock的注意点
为什幺需要给多个实例加锁
保证RedLock的高可用,部分节点宕机依然可以使用。
为什幺需要计算加锁这个过程的耗时
因为多个节点需要依次加锁,加锁过程可能遇到网络阻塞、丢包、延迟等情况,即便大多数节点加锁成功,那幺有可能最先加锁的实例锁已经过期,那幺后续操作将再无其它意义。
为什幺给所有节点释放锁
为什幺是所有这里一定需要注意,因为加锁这个过程有可能实例加锁成功,但实例将成功结果返回客户端时出现丢包,客户端认为其加锁失败了,所以在解锁时需要给所有的节点解锁,这样才能保证锁的及时释放。
那幺RedLock就能解决故障转移的问题了吗,这里看似确实解决了,但是也带来了一个新问题,实例的持久化将影响锁的安全性。
实例持久化对RedLock的影响
假设存在五个实例节点A、B、C、D、E,有如下场景
- 客户端1,依次给实例A、B、C加锁并且成功,实例D、E因为网络问题加锁失败。
- 这时节点C突然宕机,并且锁的键值并没有持久化到磁盘。
- 节点C重启,客户端2连接到实例D、E然后给C、D、E加锁成功。
这时客户端1和客户端2就拥有了同一把锁,锁又不安全了。
无论持久化是AOF还是RDB,都是存在数据丢失的风险,这个无法避免,为了应对这一问题,Redis作者antirez又提出了新概念延迟重启,也就是说在一个节点崩溃后,先不立即重启它,而是等待一段时间,这个时间间隔应该是大于键值的过期时间,这样就能避免实例宕机给RedLock的影响。
其它疑问
RedLock确实能够做到避免故障转移带来的问题,但有一些之前单实例做分布式锁中提到的问题如下
- 客户端获取锁后,执行业务时间过长导致实例上的锁过期,这时RedLock是否能处理呢?答案是否定的。
- 客户端加锁成功后,可能剩余的有效时间太短了无法完成业务,那幺是否需要选择释放锁呢,这也是一个难题。