【Redis】RedLock算法讲解
什么是 RedLock ? 为什么要使用 RedLock ?
RedLock(Redis Distributed Lock) 是 Redis 官方提出的一种用于在分布式系统中实现分布式锁的算法。它的设计目标是在一个由多个独立 Redis 节点(通常是主节点,而不是哨兵或集群模式)组成的环境中,提供一个比单实例 Redis 锁更安全、更可靠的锁服务。
在理解 RedLock 之前,我们先要知道一个简单的单节点 Redis 锁存在什么缺陷。
单节点 Redis 锁主要使用 SET resource_name my_random_value NX PX 30000 命令来实现,它存在一个致命缺陷 - 单点故障(Single Point of Failure)。
假设我们只有一个 Redis 主节点(Master):
- 客户端 A 在 Master 上成功获取了锁。
- Master 在将锁同步到 Slave 之前崩溃了。
- 系统进行故障转移,其中一个 Slave 被提升为新的 Master。
- 此时,新的 Master 上并没有客户端 A 持有的锁。
- 客户端 B 向新的 Master 申请同一个锁,并且成功了!
- 结果:现在客户端 A 和客户端 B 都认为自己持有锁,导致数据不一致、竞争条件等严重问题。
RedLock 就是为了解决这种在故障转移场景下的锁失效问题而诞生的。
简述一下 RedLock 算法的流程
RedLock 的核心思想是:“少数服从多数”。它不依赖单个 Redis 节点的可靠性,而是通过向多个独立的 Redis 节点申请锁,并根据成功获取锁的节点数量来判断是否真的获取到了锁。
假设我们有一个由 N 个 Redis 主节点组成的集群(通常 N 是奇数,例如 5 个),这些节点是完全独立的,不存在主从复制或其他数据协调关系。
获取锁的步骤:
- 记录开始时间: 客户端获取当前时间(以毫秒为单位)。
- 依次向所有节点申请锁: 客户端使用相同的键名和随机值,依次向 N 个 Redis 实例发送锁申请命令(SET resource_name my_random_value NX PX)。这里需要设置一个远小于锁超时时间的网络超时时间,以防止某个节点宕机时客户端被长时间阻塞。
- 计算成功获取锁的耗时: 客户端再次获取当前时间,减去步骤 1 的时间,得到获取锁过程的总耗时。只有当客户端在大多数(N/2 + 1)节点上成功获取锁,并且总耗时小于锁的有效时间(TTL)时,锁才算是获取成功。
- 大多数:例如,有 5 个节点,则需要至少 3 个节点成功。
- 总耗时小于 TTL:确保锁在获取成功后,仍然有剩余的有效期。
- **如果锁获取成功:**锁的实际有效时间 = 初始设置的有效时间 - 获取锁的总耗时。
- 如果锁获取失败(即没有获得大多数节点的锁,或者总耗时已经超过了锁的 TTL):客户端必须向所有 Redis 节点发起释放锁的请求(即使那些它认为没有成功的节点),以确保系统的清洁。
释放锁的步骤:
释放锁时,客户端需要向所有 Redis 节点发起释放操作,即执行一个 Lua 脚本,该脚本会检查锁的 value 是否匹配,只有匹配时才删除键。
关于 RedLock 算法有哪些争议?
RedLock 提出后,在分布式系统社区引发了激烈的讨论,其中最著名的是 Martin Kleppmann(《数据密集型应用系统设计》作者)与 Redis 作者 Antirez 的辩论。
主要争议点:
- 系统时钟(Clock)跳跃问题:
- 场景:如果一个 Redis 节点的系统时钟突然向前跳跃,会导致其上的锁提前过期。另一个客户端就可能获取到该锁,从而违反锁的互斥性。
- Antirez 的反驳:可以通过合理的运维禁止手动修改系统时间,并使用 ntpd 等工具来平滑校正时间,从而避免时钟跳跃。他认为这是一个可以解决的运维问题,而非算法缺陷。
- 垃圾回收(GC Pause)等进程暂停问题:
- 场景:客户端 A 获取了锁,然后发生了长时间的 GC Pause 或进程挂起。在此期间,锁因超时而被自动释放。客户端 B 获取了锁并开始修改共享资源。当客户端 A 从 GC 中恢复后,它仍然认为自己持有锁,也会去修改资源,导致冲突。
- 本质:这个问题其实任何分布式锁都无法完全避免,因为它涉及到网络、进程等不可靠环境。RedLock 并不比其它分布式锁方案(如 ZooKeeper, etcd)更差。解决这类问题通常需要 **防护令牌(fencing token)**等额外机制。
结论: 尽管有争议,但 RedLock 在大多数实际场景下仍然是一个有效且被广泛使用的方案,只要使用者了解其潜在的限制。
如何使用 RedLock?
你不需要自己实现这个算法。已经有成熟的客户端库:
- Java:Redisson 库提供了现成的
RedissonRedLock
实现。 - Python:
redis-py-cluster
或其他一些库提供了类似功能。 - Go:有
github.com/go-redsync/redsync
等库。