RedLock红锁算法
# 单点Redis会出现的问题
我们之前的例子都是在Redis单点模式下进行的,但是当Redis部署集群的时候,就会出现一些问题。
一般来说Redis集群中,一台主机,多台从机,还有哨兵机制。从机同步主机数据需要一定的时间。
问题场景:
10010服务对主机进行set操作进行获取锁,然后主机存入锁,随后就是同步过程。主机通过IO操作将数据写入RDB或者AOF日志中,从机需要通过网络操作拉取主机的日志,这个过程是需要耗费一定时间的。
然后因为某些原因,Redis的这台主机挂掉了,通过哨兵Sentinel机制,其中一台从机升级为主机。这时旧的主机还没有将数据同步到日志中去,然后另外一个10011服务又发送了一条请求过来获取锁,因为新的主机没有进行同步,所以还没有锁,这就导致了又获取了一把锁。
这样一来,两个请求就并发了,锁机制失效。
# RedLock解决Redis集群锁失效问题
使用Redis官方提供的RedLock红锁算法
官方文档:RedLock官方文档 (opens new window)
中文翻译:
在算法的分布式版本中,我们假设有N个Redis服务器。这些节点是完全独立的,因此我们不使用复制或任何其他隐式协调系统。**前几节已经描述了如何在单个实例中安全地获取和释放锁,在分布式锁算法中,将使用相同的方法在单个实例中获取和释放锁。**将N设置为5是一个合理的值,因此需要在不同的计算机或虚拟机上运行5个Redis主服务器,确保它们以独立的方式发生故障。
为了获取锁,客户端执行以下操作:
- 客户端以毫秒为单位获取当前时间的时间戳,作为起始时间。
- 客户端尝试在所有N个实例中顺序使用相同的键名、相同的随机值来获取锁定。每个实例尝试获取锁都需要时间,客户端应该设置一个远小于总锁定时间的超时时间。例如,如果自动释放时间为10秒,则尝试获取锁的超时时间可能在5到50毫秒之间。这样可以防止客户端长时间与处于故障状态的Redis节点进行通信:如果某个实例不可用,尽快尝试与下一个实例进行通信。
- 客户端获取当前时间 减去在步骤1中获得的起始时间,来计算获取锁所花费的时间。当且仅当客户端能够在大多数实例(至少3个)中获取锁时,并且获取锁所花费的总时间小于锁有效时间,则认为已获取锁。
- 如果获取了锁,则将锁有效时间减去 获取锁所花费的时间,如步骤3中所计算。
- 如果客户端由于某种原因(无法锁定N / 2 + 1个实例或有效时间为负)而未能获得该锁,它将尝试解锁所有实例(即使没有锁定成功的实例)。
每台计算机都有一个本地时钟,我们通常可以依靠不同的计算机来产生很小的时钟漂移。只有在拥有锁的客户端将在锁有效时间内(如步骤3中获得的)减去一段时间(仅几毫秒)的情况下终止工作,才能保证这一点。以补偿进程之间的时钟漂移
当客户端无法获取锁时,它应该在随机延迟后重试,以避免同时获取同一资源的多个客户端之间不同步(这可能会导致脑裂的情况:没人胜)。同样,客户端在大多数Redis实例中尝试获取锁的速度越快,出现裂脑情况(以及需要重试)的窗口就越小,因此理想情况下,客户端应尝试将SET命令发送到N个实例同时使用多路复用。
值得强调的是,对于未能获得大多数锁的客户端,尽快释放(部分)获得的锁有多么重要,这样就不必等待锁定期满才能再次获得锁(但是,如果发生了网络分区,并且客户端不再能够与Redis实例进行通信,则在等待密钥到期时需要付出可用性损失)。