什么是RedLock?
-
RedLock是Redis官方提供的一个分布式锁解决方案,为了解决单Redis节点的分布式锁方案可能出现的并发问题。
单Redis节点的分布式锁方案可能会出现什么样的并发问题?
首先,我们是如何为资源加锁的?
- 我们可以直接基于 redis 的 setNX (SET if Not eXists)命令,实现一个简单的锁:
- 获取锁:
SET resource_name my_random_value NX PX 30000
- 释放锁(必须使用Lua脚本保证原子性):
if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end
- 获取锁:
单节点的Redis分布式锁,当Redis节点发生故障时会出现什么情况?
-
Redis是 master-slave的架构,发生故障的时候切换到slave就好,但是Redis的复制却是异步的
-
场景:
- 客户端A在master上拿到了锁。
- 在master将数据同步到slave上之前,master宕机。
- 客户端B就从slave上又一次拿到了锁。
-
由于Master的宕机,造成了同时多人持有锁。
- 我们可以直接基于 redis 的 setNX (SET if Not eXists)命令,实现一个简单的锁:
RedLock的实现
核心思想: 同时使用多个Redis Master来冗余,且这些节点都是完全的独立的,也不需要对这些节点之间的数据进行同步。
实现方法:
假设我们有N个Redis节点,N应该是一个大于2的奇数
获取锁
- 取得当前时间
- 依次获取N个节点的Redis锁
- 再次获取当前时间,计算获取锁的时间
- 如果获取到的锁的数量大于 (N/2 + 1)个,且获取锁的时间小于锁的有效时间(lock validity time)就认为获取到了一个有效的锁。
锁自动释放时间 = 最初的锁释放时间 - 之前获取锁所消耗的时间。
如果获取锁的数量小于 (N/2 + 1),或者在锁的有效时间(lock validity time)内没有获取到足够的说,就认为获取锁失败。这个时候需要向所有节点发送释放锁的消息。 - 获取锁成功,访问资源
释放锁
- 向所有的Redis节点发起释放的操作,无论之前是否获取锁成功。
需要注意的问题
-
重试获取锁的间隔时间应当是一个随机范围而非一个固定时间。这样可以防止,多客户端同时一起向Redis集群发送获取锁的操作,避免同时竞争。同时获取相同数量锁的情况。
-
如果某master节点故障之后,回复的时间间隔应当大于锁的有效时间(延迟重启)。
假设有A,B,C三个Redis节点。
- 客户端foo获取到了A、B两个锁。
- 这个时候B宕机,所有内存的数据丢失。
- B节点恢复。
- 这个时候客户端bar重新获取锁,获取到B,C两个节点。
此时又有两个客户端获取到锁了。
如果恢复的时间将大于锁的有效时间,就可以避免以上情况发生。
RedLock的不足
我们为什么需要锁?
- 提升效率,用锁来保证一个任务没有必要被执行两次。比如耗费资源很多的计算
- 保证正确性,使用锁来保证任务按照正常的步骤执行,防止两个节点同时操作一份数据,造成文件冲突,数据丢失。
第一种原因,我们对锁是有一定宽容度的,就算发生了两个节点同时工作,对系统的影响也仅仅是多付出了一些计算的成本,没什么额外的影响。这个时候 使用单点的 Redis 就能很好的解决问题,没有必要使用RedLock,维护那么多的Redis实例,提升系统的维护成本。
第二种原因,对正确性有着严格要求的(比如订单,或者消费),就算使用了 RedLock 算法仍然不能保证锁的正确性。
为什么第二种原因 RedLock 不能保证正确性?
-
避免宕机造成的死锁而设置锁的过期时间 可能导致的问题
在RedLock获取锁的步骤中,如果 客户端1 在第4步获取锁成功之后发生了阻塞(如系统站厅,网络延迟等),阻塞时间超过了锁的过期时间,这时 客户端2 可以获取到锁,如果在客户端2获取锁成功之后,客户端1从阻塞中恢复过来开始访问资源,就会发生两个实体对同一资源进行操作的并发问题。 这种情况是无法避免的,即使客户端1在对资源进行操作之前再获取一次锁的状态, 假如阻塞发生在获取锁的状态之后,也会造成同样的并发问题。
具有自动释放锁功能的分布式锁都没办法解决这个问题。
-
RedLock建立在了 Time 是可信的模型上, 系统时钟不同步可能会出现问题:
假设我们有 A、B、C、D、E 五个Redis节点
- 客户端1 从 A、B、C、D、E五个节点中,获取了 A、B、C三个节点获取到锁,我们认为他持有了锁。
- 节点B的时间走的比A、C快, 这时B会先于A、C释放锁
- 客户端2 可以从 B、D、E三个节点获取到锁。
在整个分布式系统就造成 两个 客户端 同时持有锁了。
系统时钟不同步的问题可以通过运维来保证。
- 需要将阶跃的时间更新到服务器的时候,应当采取小步快跑的方式。多次修改,每次更新时间尽量小,这样将大的时间误差分散到一个个很长的时间范围内。 这种方法与大批量修改数据库数据时,采取分段修改的方式来尽可能减少大范围的行锁导致系统性能降低 都是相同的思想。
总结:
- 在系统设计中不存在完美的解决方案,就像分布式系统中的CAP定理:在一个分布式系统中,Consistency(一致性)、 Availability(可用性)、Partition tolerance(分区容错性),三者不可兼得。
我们可以努力让用户感觉不到这种残缺,但是却无法消除这种残缺。
在实际场景中需要思考的是:牺牲的一致性能获得多大的可用性。所以应该深入了解其中的原理,根据场景的侧重点来选择合适的解决方案。
网友评论