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

分布式锁: 并发时,redis如何避免删别人的锁

在使用Redis实现分布式锁的时候,如何避免在并发情况下误删别人的锁。

首先,分布式锁的基本概念:是多个客户端在访问共享资源时,通过某种机制来确保同一时间只有一个客户端能持有锁。
Redis通常用SET命令加上NX选项来创建锁,同时设置过期时间,避免锁被长时间占用无法释放。
比如用`SET lock_key unique_value NX PX 30000`这样的命令,
其中unique_value是每个客户端唯一的标识,这样在释放锁的时候可以验证是否是自己持有的锁,防止误删。

那问题来了,假设客户端A获取了锁,然后执行了一些操作,这时候如果操作时间超过了锁的过期时间,
锁会自动释放。此时客户端B可能获取到锁,然后客户端A操作完成,尝试释放锁,
这时候如果没有检查锁的值,直接删除的话,就会把客户端B的锁删掉。这显然是不对的。

所以关键点在于释放锁的时候要确保只有锁的持有者才能删除它。那具体怎么实现呢?
应该是这样的:在删除锁之前,先获取锁的值,判断是否与自己设置的unique_value一致,如果一致才删除。
但是这里有个问题,获取值和删除这两个操作不是原子性的,可能存在竞态条件。
比如客户端A检查值的时候发现是自己的,然后准备删除,这时候锁过期了,客户端B获取了锁,
然后客户端A才执行删除操作,这时候又会误删客户端B的锁。

那如何解决这个问题呢?应该让检查和删除这两个操作原子化执行。
Redis有没有这样的机制呢?对的,可以用Lua脚本,因为Lua脚本在Redis中是原子执行的,不会被其他命令打断。
所以,我们可以写一个Lua脚本,里面先检查锁的值是否匹配,如果匹配则删除,否则不操作。这样就能保证操作的原子性了。

比如,Lua脚本可以这样写:

```lua

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

```

这里的KEYS[1]是锁的键名,ARGV[1]是客户端生成的唯一值。这样执行的时候,
判断和删除就变成一个原子操作,不会被打断,避免了误删的情况。

那除了使用Lua脚本之外,还有没有其他的方法?比如使用Redis的事务(MULTI/EXEC),
不过事务中的命令虽然会按顺序执行,但是其中的命令可能会被其他客户端的命令插入,
所以并不能保证原子性。而Lua脚本是确保整个脚本在同一个原子操作中执行的,所以更可靠。

另外,关于unique_value的生成,必须确保每个客户端每次获取锁时都是唯一的,比如可以使用UUID,
或者结合客户端ID和时间戳等信息生成。这样即使不同客户端的操作,他们的unique_value也不会冲突。

还有,锁的过期时间设置也很重要。如果设置的太短,可能导致业务逻辑还没执行完,锁就被释放了,进而被其他客户端获取,导致并发问题。
如果设置的太长,当客户端崩溃时,锁不能及时释放,影响系统可用性。
所以需要根据业务执行时间合理设置过期时间,或者使用自动续期的机制,
比如Redisson的看门狗机制,定期检查锁是否还在持有,如果是的话就延长过期时间。

总结一下,避免删除别人的锁的关键步骤是:

1. 在加锁时设置唯一的标识(unique_value)。
2. 在解锁时通过Lua脚本原子性地验证标识并删除锁。
3. 确保锁的过期时间合理,或者Redisson的看门狗机制实现自动续期。

这样就能有效避免在并发场景下误删其他客户端的锁了。不过需要注意,
Redis的分布式锁在极端情况下(比如主从切换时)可能还存在问题,
这时候可能需要更复杂的算法比如RedLock,
但大多数情况下,单实例Redis加上正确的解锁方式已经足够应对了。

在Redis中实现分布式锁时,为避免并发场景下误删其他客户端的锁,需遵循以下步骤:

核心解决方案:

  1. 唯一标识锁持有者
    每个客户端在获取锁时,需生成一个全局唯一值(如UUID、客户端ID+时间戳),作为锁的值。例如:

    SET lock_key unique_value NX PX 30000  
    # unique_value为唯一标识,NX表示仅当键不存在时设置,PX设置过期时间
  2. 原子化验证并删除锁
    释放锁时,需确保验证锁归属删除锁是原子操作。通过Lua脚本实现:

    if redis.call("get", KEYS[1]) == ARGV[1] then
        return redis.call("del", KEYS[1])
    else
        return 0
    end

    该脚本先检查锁的值是否匹配当前客户端的唯一标识,若匹配则删除,否则不操作。

  3. 合理设置锁超时时间

    • 根据业务逻辑的最大执行时间,设置足够的锁超时时间(如PX 30000表示30秒)。

    • 可选:实现锁自动续(如Redisson的看门狗机制),避免业务未完成锁提前过期。

完整流程示例:

  1. 加锁
    客户端A执行:

    SET my_lock 8a2e1a7c-5b04-4f8a-9e3a-ca5d8e8e7a6f NX PX 10000

    成功获取锁,有效期10秒。

  2. 执行业务逻辑
    客户端A处理共享资源操作。

  3. 解锁
    客户端A执行Lua脚本:

    EVAL "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end" 1 my_lock 8a2e1a7c-5b04-4f8a-9e3a-ca5d8e8e7a6f

    仅当锁值匹配时,原子化删除锁。

注意事项:

  • 避免非原子操作
    直接使用GETDEL的非原子操作可能导致竞态条件,必须通过Lua脚本保证原子性。

  • 锁超时与续期
    若业务执行时间不稳定,建议实现锁自动续期(如每隔一段时间重置过期时间),防止锁提前释放。

  • 集群环境风险
    Redis主从架构下,主节点宕机可能导致锁丢失。若需强一致,可考虑RedLock算法(多实例部署),但会增加复杂度。

    RedLock算法(适用于Redis Cluster

    对于需要更高可靠性的场景,可以使用RedLock算法。RedLock算法要求在多个独立的Redis节点上尝试获取锁,并且大多数节点成功获取锁才算成功。释放锁时,需要在所有节点上释放。这增加了系统的复杂性和对网络延迟的容忍度,但提供了更高的可靠性。

总结:

通过唯一标识+原子化操作,Redis分布式锁可有效避免误删问题。正确实现后,能在多数场景下保障并发安全。

Redis分布式锁:面试官必问!
🔐为什么需要分布式锁?
🚫线程锁(synchronized/ReentrantLock)无法跨JVM进程
🚫进程锁(文件锁)在跨服务器时失效
🚫分布式系统需要跨机器通信的锁机制

🚀Redis分布式锁三大核心:
1️⃣ setnx+expire原子操作(Redis2.8+支持set带NX+EX参数)
2️⃣ 存储客户端唯一ID在value中,防止误删他人锁
3️⃣ 使用Lua脚本确保解锁操作的原子性

⚠️注意事项:单节点宕机导致锁失效?
RedLock算法五步解决方案:
1️⃣ 获取当前毫秒级时间戳
2️⃣ 向5个独立Redis实例顺序加锁
3️⃣ 计算获取锁的总耗时(必须小于锁的超时时间)
4️⃣ 过半节点(≥3)成功且耗时有效才算成功
5️⃣ 自动延长持有时间,需要运行守护线程

💥隐藏的雷区:
❌时钟跳跃可能导致锁提前失效
❌GC停顿可能导致锁超时失效
❌网络延迟可能导致双重判空

建议记住这个万能公式:唯一ID+自动续期+半数机制+时钟同步=高可用分布式锁!

锁提前失效-时钟跳跃

时钟跳跃可能导致锁提前失效‌,这是因为系统时钟的跳跃会导致时间戳的计算出现误差,进而影响锁的有效期计算。具体来说,时钟跳跃是指实际时间与系统记录的时间之间存在较大的差异,这种差异可能导致锁的结束时间(endTime)与开始时间(beginTime)之间的差值过大,从而使得锁在未到期时就被认为是过期,导致锁提前失效‌。

时钟跳跃的定义和原因

时钟跳跃是指系统时钟实际时间之间的差异较大,通常是由于系统时间的手动调整或网络时间协议(NTP)同步失败等原因造成的。这种时间差异会导致系统内部的时间计算出现误差,进而影响依赖于时间戳的操作,如锁的管理‌。

时钟跳跃对锁机制的影响

在分布式系统中,锁的管理通常依赖于精确的时间同步。如果系统时钟发生跳跃,锁的有效期计算可能会出现误差,导致锁在未到期时就被释放或认为已过期。这可能会导致资源竞争和访问冲突,影响系统的稳定性和可靠性‌。

防止时钟跳跃影响的措施

为了防止时钟跳跃对锁机制的影响,可以采取以下措施:

  1. 使用时间同步协议‌:通过NTP或其他时间同步协议,将各个节点的系统时钟同步到一个统一的时间源,减小时钟跳跃的可能性‌。
  2. 引入时钟漂移校正算法‌:在节点之间进行心跳同步时,引入动态调整心跳时间戳的算法,以校正时钟漂移‌。
  3. 使用分布式一致性算法‌:如Paxos、Raft等算法,通过选举机制和消息传递实现节点之间的协调和同步,保证数据一致性和时钟同步‌。
  4. 高可用架构和容灾方案:采用主备模式、多活模式、负载均衡等技术手段,确保系统的可用性和容错性‌。

锁提前失效-GC停顿

GC停顿可能导致锁超时失效‌,主要是因为垃圾回收(GC)过程中会暂停应用程序的执行,这会导致持有锁的线程无法继续执行,从而可能导致锁超时失效。

原因分析

  1. GC停顿‌:在垃圾回收过程中,JVM会暂停所有应用线程,进行内存清理。如果GC停顿时间过长,持有锁的线程可能会在锁过期前无法完成操作,导致锁被其他线程获取。
  2. ‌锁超时‌:许多分布式锁实现都设置了超时机制,以防止死锁或长时间占用资源。如果GC停顿时间超过锁的超时时间,锁会自动释放,导致其他线程可以获取锁,从而影响锁的稳定性和可靠性。

解决方案

  1. ‌优化GC设置‌:通过调整JVM的垃圾回收策略和参数,减少GC停顿时间。例如,使用G1垃圾回收器,它提供了更好的停顿时间控制。可以通过设置-XX:MaxGCPauseMillis来指定最大停顿时间目标。
  2. 调整锁的超时时间‌:根据应用的实际情况,适当增加锁的超时时间,以应对可能的GC停顿。但需要注意,过长的超时时间可能会影响系统的整体性能和响应速度。
  3. 监控和调试:使用性能监控工具(如JProfiler, YourKit, JVisualVM等)来分析对象的创建速率和内存使用情况,优化代码以减少对象创建和内存使用,从而降低GC频率和停顿时间。

通过以上措施,可以有效减少GC停顿对分布式锁的影响,确保系统的稳定性和可靠性。

锁提前失效-网络延迟

网络延迟可能导致DCL锁超时失效‌。在网络延迟的情况下,客户端请求获取逻辑锁时,请求可能会延迟到达服务器。如果在这期间其他客户端已经成功获取了锁,延迟的客户端由于不知道锁已经被占用,仍然在等待服务器的响应,这可能导致多个客户端同时获取锁的错觉。当客户端成功获取逻辑锁后,如果在持有锁的期间对共享资源进行操作时,由于网络延迟,服务器可能无法及时收到客户端释放锁的请求,导致锁被长时间占用,其他客户端无法获取锁,从而降低系统的并发性能‌。

解决方法

  1. 优化网络环境‌:可以通过升级网络设备、增加网络带宽等方式来减少网络延迟。例如,在企业内部的局域网中,将老旧的网络交换机更换为高性能的交换机,或者从较低带宽的网络接入方式升级‌。
  2. 使用网络优化工具‌:尝试使用网络优化工具,如“雷神加速器”或“UU加速器”,在打开Deadlock之前,先启动加速器并选择相应的区服进行优化,这样可以显著提升网络环境,解决因网络问题导致的游戏连接不畅‌。
  3. 检查路由器设置‌:定期重启路由器,确保没有错误的配置或限制。此外,尝试更换DNS服务器或使用有线连接,以进一步提高网络稳定性‌。

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

相关文章:

  • 如何用DeepSeek进行项目管理?AI重构项目全生命周期的实践指南
  • C51 Proteus仿真实验17:数码管显示4×4键盘矩阵按键
  • 力扣No.376.摆动序列
  • 【从零开始学习计算机科学】设计模式(一)设计模式概述
  • 蓝桥杯嵌入式赛道复习笔记2(按键控制LED灯,双击按键,单击按键,长按按键)
  • Mysql篇——SQL优化
  • Excel进阶篇:数据透视表详解 数据透视表进阶 切片器 配色
  • 如何使用HACS一键集成米家与果家设备到HomeAssistant玩转智能家居
  • 《我的Python觉醒之路》之转型Python(十五)——控制流
  • 智能化营销:唤醒沉睡客户,驱动企业利润增长
  • C++Qt开发流程图效果,包括保存、加载功能
  • 使用redis客户端中对于json数据格式的存储和读取
  • DR-CAN 卡尔曼滤波笔记
  • leetcode每日一题:使字符串平衡的最小交换次数
  • 【软件工程】06_软件设计
  • Carto 无尽旅图 for Mac v1.0.7.6 (51528)冒险解谜游戏 支持M、Intel芯片
  • 微软 AI 发布 LongRoPE2:近乎无损地将大型语言模型上下文窗口扩展至 128K 标记,保持 97% 短上下文准确性
  • 14.使用各种读写包操作 Excel 文件:辅助模块
  • APB-清华联合腾讯等机构推出的分布式长上下文推理框架
  • 拦截网页中的 Fetch 和 XMLHttpRequest 请求方式方法