当前位置: 首页 > article >正文

Redis中的Red Lock/Redis锁

1.2RedLock
  • 注意

    1. 红锁的服务器是只存锁信息的和存业务信息的redis服务器是分开的
  • 步骤:

    1. 首先集群部署Redis,官方推荐至少5个实例,不需要主从和哨兵,每个服务器是独立的,不存在信息同步。五个服务器不需要任何交互。
    2. 客户端对五个实例依次申请锁,如果最终申请成功的数量超过半数(>=3),则表明红锁申请成功,反之失败。
    3. 若一台实例宕机,但还剩下4个,还是可以超过半数,是没有问题的。而且没有主从机制,不会存在同步丢锁的问题。
  • 具体步骤:

    1. 客户端获取当前时间(t1)
    2. 客户端按照顺序依次对N个Redis节点利用set命令进行加锁操作,对每个节点加锁对会设置超时时间(远小于锁的总过期时间)(防止单点卡死),如果当前节点请求超时,立马向下一个节点申请锁。
    3. 当客户端成功从半数的Redis节点获取到了锁,这个时候获取当前时间t2,然后计算加锁过程的总耗时t(t2 - t1)。如果t<锁的过期时间,这个时候就可以加锁成功,反之失败
    4. 加锁成功则执行业务逻辑,加锁失败则依次向全部节点释放解锁的流程
  • GC为什么无法解决

    (1) GC 的工作机制

    • GC 的运行和结束时间取决于以下因素:
      1. 垃圾回收算法:不同的 GC 算法(如 G1、CMS、ZGC)在内存回收策略上差异很大,有些是并发的,有些需要 Stop The World。
      2. 堆内存大小:堆越大,垃圾回收所需的时间可能越长。
      3. 内存碎片化:如果内存中的对象分布很分散,需要更长的时间来整理。
      4. 系统负载:GC 的优先级可能受到操作系统调度或 CPU 负载的影响。

    (2) STW 的不可控性

    • 在 GC 过程中,JVM 暂停所有应用线程(STW),而垃圾回收线程独占资源。应用程序在这段时间内被“冻结”,无法执行任何逻辑。
    • STW 的持续时间是动态的,依赖于当前内存状态、存活对象数量等。开发者无权干预 STW 的时长,也无法预测它具体会持续多久。

    (3) 无法与 GC 直接通信

    • JVM 的垃圾回收是一个内部过程,程序无法主动与垃圾回收器通信。虽然可以通过 JVM 提供的工具(如 GarbageCollectorMXBean)监控 GC 事件,但这些监控都是被动的,并不能提前知道或控制 GC 的结束时间。

    (4) 并发 GC 也无法完全避免停顿

    • 即使是低停顿的垃圾回收算法(如 G1 GC、ZGC、Shenandoah),也需要在某些阶段执行短暂的 STW 操作。例如,根扫描、内存压缩等操作无法完全并发化。
  • 思考:解锁失败,是否要一直请求?

    ​ 1. 客户端执行加锁操作。

    ​ 2. 如果加锁失败,主动解锁成功的节点:

    • ** 快速解锁**:立即发送解锁请求,对失败的节点启动 异步重试机制

    • 锁状态检测:定期检查锁状态,确保所有节点上的锁都被清理。

    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
    ±--------------------------+
    | 加锁成功,操作共享资源 |
    ±--------------------------+


http://www.kler.cn/a/470364.html

相关文章:

  • 【计算机操作系统:三、操作系统的用户接口】
  • 【雷达】雷达的分类
  • python制作翻译软件
  • 三维卷积( 3D CNN)
  • Git 常用命令及其使用场景
  • Scala_【5】函数式编程
  • K8s集群中,Worker 节点误执行了 kubeadm init
  • ip属地不是唯一的吗怎么改
  • Vue笔记-001-声明式渲染
  • macOS 安装 python3.11
  • Java基础知识-面试题
  • 智能服装推荐系统 协同过滤余弦函数推荐服装 Springboot Vue Element-UI前后端分离
  • react vscode prettier 格式化代码
  • 数据结构 - blocks
  • freertos官网源码烧录stlink注意配置
  • javascript e.preventDefault() 的作用和用法
  • Unity3D仿星露谷物语开发18之添加道具到库存栏
  • 按键精灵ios脚本教程:用函数来实现将图片保存到相册
  • Elasticsearch VS Easysearch 性能测试
  • Golang学习笔记_18——接口
  • 海外云服务器能用来做什么?
  • python 如何调整word 文档页眉页脚
  • 移动电商的崛起与革新:以开源AI智能名片2+1链动模式S2B2C商城小程序为例的深度剖析
  • 培训机构Day23
  • 在 Vue 中使用 @ngageoint/geopackage 实现 GeoPackage 数据处理与可视化
  • 常见转义字符