Redis中的Red Lock/Redis锁
1.2RedLock
-
注意
- 红锁的服务器是只存锁信息的和存业务信息的redis服务器是分开的
-
步骤:
- 首先集群部署Redis,官方推荐至少5个实例,不需要主从和哨兵,每个服务器是独立的,不存在信息同步。五个服务器不需要任何交互。
- 客户端对五个实例依次申请锁,如果最终申请成功的数量超过半数(>=3),则表明红锁申请成功,反之失败。
- 若一台实例宕机,但还剩下4个,还是可以超过半数,是没有问题的。而且没有主从机制,不会存在同步丢锁的问题。
-
具体步骤:
- 客户端获取当前时间(t1)
- 客户端按照顺序依次对N个Redis节点利用set命令进行加锁操作,对每个节点加锁对会设置超时时间(远小于锁的总过期时间)(防止单点卡死),如果当前节点请求超时,立马向下一个节点申请锁。
- 当客户端成功从半数的Redis节点获取到了锁,这个时候获取当前时间t2,然后计算加锁过程的总耗时t(t2 - t1)。如果t<锁的过期时间,这个时候就可以加锁成功,反之失败
- 加锁成功则执行业务逻辑,加锁失败则依次向全部节点释放解锁的流程
-
GC为什么无法解决
(1) GC 的工作机制
- GC 的运行和结束时间取决于以下因素:
- 垃圾回收算法:不同的 GC 算法(如 G1、CMS、ZGC)在内存回收策略上差异很大,有些是并发的,有些需要 Stop The World。
- 堆内存大小:堆越大,垃圾回收所需的时间可能越长。
- 内存碎片化:如果内存中的对象分布很分散,需要更长的时间来整理。
- 系统负载:GC 的优先级可能受到操作系统调度或 CPU 负载的影响。
(2) STW 的不可控性
- 在 GC 过程中,JVM 暂停所有应用线程(STW),而垃圾回收线程独占资源。应用程序在这段时间内被“冻结”,无法执行任何逻辑。
- STW 的持续时间是动态的,依赖于当前内存状态、存活对象数量等。开发者无权干预 STW 的时长,也无法预测它具体会持续多久。
(3) 无法与 GC 直接通信
- JVM 的垃圾回收是一个内部过程,程序无法主动与垃圾回收器通信。虽然可以通过 JVM 提供的工具(如
GarbageCollectorMXBean
)监控 GC 事件,但这些监控都是被动的,并不能提前知道或控制 GC 的结束时间。
(4) 并发 GC 也无法完全避免停顿
- 即使是低停顿的垃圾回收算法(如 G1 GC、ZGC、Shenandoah),也需要在某些阶段执行短暂的 STW 操作。例如,根扫描、内存压缩等操作无法完全并发化。
- GC 的运行和结束时间取决于以下因素:
-
思考:解锁失败,是否要一直请求?
1. 客户端执行加锁操作。
2. 如果加锁失败,主动解锁成功的节点:
-
** 快速解锁**:立即发送解锁请求,对失败的节点启动 异步重试机制。
-
锁状态检测:定期检查锁状态,确保所有节点上的锁都被清理。
-
如果卡死的节点恢复,异步重试或状态检测会自动清理锁信息。
-
如果超出指定时间(如 5 秒)未能完全解锁,锁依赖 TTL 自动过期。
-
-
总结
Redlock 的整体方案和流程
前提条件 1. 不需要部署从库和哨兵实例,只部署多个独立的主库 Redis。 2. 至少需要 5 个 Redis 实例(官方推荐),以保证多数节点可用。 整体流程 1. 客户端获取当前时间戳 time1
。2. 客户端依次向 5 个 Redis 实例发起加锁请求,每个请求设置较短超时时间(远小于锁的有效时间)。 3. 如果某节点加锁失败(如锁被占用或网络超时),立刻向下一个节点发起请求。 4. 如果 大于等于 3 个节点 加锁成功,再获取当前时间戳 time2
,计算耗时time2-time1
。- 如果耗时 <锁过期时间
,加锁成功;否则加锁失败,向所有实例释放锁。5. 如果加锁成功,操作共享资源;如果失败,则清理所有节点的锁状态。 Redlock 的设计重点
重点 解释 1. 多个独立的 Redis 实例 通过部署多个独立 Redis 实例(无主从、无集群),确保分布式环境的容错能力。 2. 必须保证大多数节点加锁成功 只要大多数节点(如 5 个节点中至少 3 个)正常加锁成功,即使部分节点宕机,锁服务仍然可用。 3. 加锁耗时必须小于锁的 TTL 如果耗时超过锁的 TTL,部分锁可能已失效,此时锁已不具备互斥性,因此直接认定加锁失败。 4. 释放锁需向所有节点发起释放请求 通过释放所有节点的锁,清理可能遗留的锁状态,避免后续客户端误以为锁仍然存在。 Redlock 的优点和局限性
优点 局限性 1. 容错性高:只要大多数节点可用,整个锁服务仍然可以提供服务。 1. 锁的正确性质疑:当 Redis 节点时钟发生漂移或客户端进程暂停时,可能导致多个客户端错误地同时持有锁。 2. 实现简单:无需复杂的主从配置和集群部署,仅通过多个独立节点实现高可用锁。 2. 锁失效问题:在极端情况下(如 GC 停顿、网络延迟),锁可能失效,导致并发问题。 3. 性能高:相比 Zookeeper 等分布式协调服务,Redis 的响应时间更短,吞吐量更高。 3. 时钟依赖问题:Redlock 的正确性依赖于 Redis 节点时钟的相对准确性(不能有大跳跃或大误差)。 对 Redlock 的质疑
问题 解释 1. 分布式锁的目的是什么? 分布式锁的目的是互斥,防止多个进程同时操作资源。Redlock 在某些异常场景下无法完全保证正确性(如锁失效)。 2. GC 和网络延迟的问题 在客户端获取锁后,如果发生 GC 或网络延迟,锁可能过期,其他客户端获取锁,导致多个客户端并发操作同一资源。 3. 时钟问题 如果 Redis 节点的时钟跳跃或漂移(如 NTP 同步问题),可能导致锁过期时间计算错误,从而引发锁的竞争冲突。 分布式锁的场景对比
场景 问题 GC(Stop-The-World) 客户端持有锁后,因 GC 停顿导致无法续约,锁到期释放给其他客户端,而 GC 恢复后原客户端仍然继续操作资源,导致冲突。 网络延迟 如果客户端加锁请求因网络延迟被误判为失败,可能导致重复加锁;或续约延迟,导致锁过期释放。 时钟漂移或跳跃 Redis 节点的时钟漂移或跳跃,可能导致锁被误判为过期,从而发生并发问题。 fencing token 机制
机制介绍 1. 在客户端获取锁时,由锁服务生成一个 递增的 token(如版本号)。 2. 客户端在操作共享资源时,携带这个 token,资源服务根据 token 判断请求的优先级和合法性。 3. 如果有多个客户端同时操作资源,资源服务可以根据 token 的递增性拒绝旧请求,从而保证操作顺序性。 优点 局限性 1. 操作顺序性:通过递增 token,可以严格保证操作的顺序性(如较旧的请求被拒绝)。 1. 依赖资源服务支持:共享资源必须支持基于 token 的验证(如数据库的事务隔离、文件系统的锁等)。 2. 适用于异步环境:无论网络延迟或进程暂停,递增 token 都能保证资源的正确性。 2. 实现复杂:需要为资源服务添加逻辑,支持 token 的验证和处理。 基于 Zookeeper 的分布式锁
优点 缺点 1. 无需考虑锁的 TTL:Zookeeper 使用临时节点,锁会随着会话的断开自动释放。 1. 性能不如 Redis:Zookeeper 的吞吐量较低,适合高可靠性需求,但不适合极高并发场景。 2. Watch 机制:客户端可以 Watch 锁节点状态,实现分布式锁的等待队列。 2. 部署成本高:Zookeeper 的运维复杂度和硬件需求较高。 3. 连接断开时自动释放锁:避免客户端因崩溃或卡顿导致的锁遗留问题。 3. 网络问题影响锁状态:如果客户端与 Zookeeper 长时间失联,锁会被释放。 总结
分布式锁选型建议 1. 轻量高性能场景:如果性能是主要考虑因素,可以选择 Redis 分布式锁(Redlock)。 2. 高可靠性需求:如果需要更高的可靠性和锁的正确性,可以选择 Zookeeper 或基于 Etcd 的分布式锁方案。 3. 极端场景下的兜底方案:对于极端情况下的锁失效问题,可以结合 fencing token 或资源层的版本控制来兜底。 -
流程图
+------------------------------------+ | 客户端发起加锁请求 (time1 记录时间) | +------------------------------------+ | v +------------------------------------------------+ | 向 Redis 实例依次发送加锁请求,设置超时时间 (远小于 TTL) | +------------------------------------------------+ | v +--------------------------------------+ | 大于等于多数实例加锁成功?(如 3/5 成功) | +--------------------------------------+ | 是 | 否 v v
±----------------------+ ±------------------------------+
| 获取当前时间 time2,计算 | | 向所有实例发起释放锁的请求,清理锁状态 |
| time2 - time1 是否 < TTL | ±------------------------------+
±----------------------+
| 是
v
±--------------------------+
| 加锁成功,操作共享资源 |
±--------------------------+