• Redis 分布式锁


    欢迎大家到我的博客浏览。Redis分布式锁 | YinKai's Blog

    分布式锁

    1、什么是分布式锁?

    在分布式场景下的锁,比如在多台不同机器上的进程,去竞争同一项资源,就是分布式锁。

    2、分布式锁有哪些特性?
    • 互斥性:只能让一个竞争者持有锁

    • 安全性:避免锁因为异常永远不被释放,当一个竞争者在持有锁期间,由于意外崩溃而导致未能主动解锁,其持有的锁也能够被兜底释放,并保证后续其它竞争者也能加锁

    • 对称性:同一个锁,加锁和解锁必须是一个竞争者,不能把其他竞争者持有的锁给释放了

    • 可靠性:需要有一定程度的异常处理能力、容灾能力。

    3、分布式锁的实现方式
    最简化版本

    直接用 Redis 的 setnx 命令,语法:setnx key value,如果 key 不存在,则会将 key 设置为 value,并返回 1;如果 key 存在,不会有任务影响,返回 0。

    通过 setnx 加锁,其他服务无法加锁,进而阻塞;用完之后,通过 delete 解锁,其他服务再去竞争锁。

    支持过期时间

    最简化版本有一个问题:如果获取锁的服务挂掉了,那么锁就一直得不到释放,就会导致其他服务无法获取到锁,影响到其他服务,所以这里需要一个过期时间来进行兜底。

    Redis 中有 expire 命令,用来设置一个 key 的过期时间。但是 setnx 和 expire 不具备原子性,如果 setnx 获取锁之后,服务挂掉,还没来得及设置过期时间,照样石沉大海。

    于是使用 set 和 expire 的原子操作:set key value nx ex seconds nx 标识 setnx 特性,ex 标识过期时间,最后一个参数就是过期时间的值。

    加上过期时间,基本上这个锁就能用了。但存在一个问题:会存在服务 A 释放掉 服务 B 的锁的可能。

    加上 owner

    在特殊的场景:服务 A 获取到了锁,由于业务流程比较长或者网络延迟、GC卡顿等原因,导致锁过期,而业务还会继续进行,这时候,业务 B 已经拿到了锁,准备去执行。这个时候服务 A 恢复过来并做完了任务,就会释放锁,而 B 还在继续,就会导致服务 A 释放掉了 服务 B 的锁。

    在真实的分布式场景中,可能存在几十个竞争者,上述情况发生的概率就会很高,导致同一份资源频繁被不同竞争者同时访问,分布式锁也就失去了意义。

    发生这个问题的关键在于:竞争者可以释放掉其他竞争者的锁。所以我们可以给出进一步的解决方案:分布式锁需要满足谁申请谁释放原则,不能释放别人的锁,也就是说,分布式锁,是要有归属的。

    引入 lua

    加入 owner 后的版本,也还有一点点小问题。完整的流程是:竞争者获取到锁执行任务,执行完毕后检查锁是不是自己的,最后释放。

    这些操作都不是原子化的,可能锁获取的时候还是自己的,删除的时候已经是别人的了。

    这里就需要引入 Lua。

    Redis + Lua,可以说是专门为解决原子问题而生的。

    到了这里,分布式锁的前三个特性已经满足:对称性、安全性、互斥性。可以是一个可以用的分布式锁了,能满足大多数场景。

    4、可靠性如何保证

    还剩下可靠性没有解决。

    针对一些异常情景,包括 Redis 挂掉、业务执行时间过长、网络波动等情况。

    容灾考虑

    前面我们谈及的内容,基本是基于单机考虑的,如果Redis挂掉了,那锁就不能获取了。这个问题该如何解决呢? ​ 一般来说,有两种方法:主从容灾和多级部署。

    主从容灾

    最简单的方式,就是为 Redis 配置从节点,当主节点挂掉了,从节点顶包。

    主从切换的话,需要人工参与,会提高人力成本。不过 Redis 已经有成熟的解决方案,也就是哨兵模式,可以灵活自动切换,不再需要人工介入。

    虽然一定程度解决了单点的容灾问题,但并不是尽善尽美的由于同步有时延,slave通过增加从节点的方式,可能会损失掉部分数据,分布式锁可能失效,这就会发生短暂的多机获取到执行权限

    有没有更可靠的办法呢?

    多机部署

    如果对一致性高一些,可以尝试多机部署。比如 Redis 的 RedLock,大概思路就是多个机器,通常是奇数,达到一半以上同意才能算加锁成功,这样可靠性会向 ETCD 靠近。

    ETCD:etcd:在前面的回答中已经介绍过,etcd是一个分布式键值存储系统,用于配置管理、服务发现和分布式协调。它是一个独立的开源项目,由CNCF维护,具有强一致性和高可用性,用于构建分布式系统的基础设施。

    现在假设有5个Redis主节点,基本保证它们不会同时宕掉,获取锁和释放锁的过程中客户端会执行以下操作:

    1. 向5个Redis申请加锁;

    2. 只要超过一半,也就是3个Redis返回成功,那么就是获取到了锁。如果超过一半失败,需要向每个Redis发送解锁命令;

    3. 由于向5个Redis发送请求,会有一定时耗,所以锁剩余持有时间,需要减去请求时间。这个可以作为判断依据,如果剩余时间已经为0,那么也是获取锁失败;

    4. 使用完成之后,向5个Redis发送解锁请求。

    这种模式的好处在于,如果挂了2台Redis,整个集群还是可用的,给了运维更多时间来修复。

    另外,多说一句,单点Redis的所有手段,这种多机模式都可以使用,比如为每个节点配置哨兵模式,由于加锁是一半以上同意就成功,那么如果单个节点进行了主从切换,单个节点数据的丢失,就不会让锁失效了。这样增强了可靠性。

    没有完全可靠的分布式锁

    由于分布式系统中的三大困境,简称 NPC。

    1. N:Network Delay (网络延迟)网络延迟导致锁过期。

    2. P:Process Pause(进程暂停)比如发生 GC,导致锁超时。

    3. C:Clock Drift(时钟漂移)

  • 相关阅读:
    Kubernetes基本概念
    支撑Java NIO 与 NodeJS的底层技术
    从零开始手写一个Transformer
    卖股票的最佳时机[哨兵机制 || 动态规划]
    在 Elasticsearch 中实现自动完成功能 1:Prefix queries
    一类恒等式的应用(范德蒙德卷积与超几何函数)
    递归算法学习——有效的数独,解数独
    ZYNQ之FPGA学习----MMCM/PLL IP核使用实验
    java.lang.Float类下compareTo()方法具有什么功能呢?
    力扣刷题学习(跟随视频学着刷)
  • 原文地址:https://blog.csdn.net/m0_62264224/article/details/133610579