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

news2025/3/19 23:18:51
在使用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.coloradmin.cn/o/2318023.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

C++和标准库速成(八)——指针、动态数组、const、constexpr和consteval

目录 1. 指针和动态数组1.1 栈和自由存储区1.2 使用指针1.3 动态分配的数组1.4 空指针常量 2. const2.1 const修饰类型2.2 const与指针2.3 使用const保护参数2.4 const方法(建议) 3. constexpr4. consteval参考 1. 指针和动态数组 动态内存允许所创建的程序具有在编…

超声重建,3D重建 超声三维重建,三维可视化平台 UR 3D Reconstruction

1. 超声波3D重建技术的实现方法与算法 技术概述 3D超声重建是一种基于2D超声图像生成3D体积数据的技术,广泛应用于医学影像领域。通过重建和可视化三维结构,3D超声能够显著提高诊断精度和效率,同时减少医生的脑力负担。本技术文档将详细阐述…

[HelloCTF]PHPinclude-labs超详细WP-Level 6Level 7Level 8Level 9-php://协议

由于Level 6-9 关的原理都是通用的, 这里就拿第6关举例, 其他的关卡同理 源码分析 定位到代码 isset($_GET[wrappers]) ? include("php://".$_GET[wrappers]) : ; 与前几关发生变化的就是 php:// 解题分析 这一关要求我们使用 php协议 php:// 协议 php://filte…

【Linux】Bash是什么?怎么使用?

李升伟 整理 什么是 Bash? Bash(Bourne Again Shell)是一种 命令行解释器(Shell),广泛用于 Unix 和 Linux 操作系统。它是 Bourne Shell(sh) 的增强版,提供了更多的功能…

如何创建并保存HTML文件?零基础入门教程

原文:如何创建并保存HTML文件?零基础入门教程 | w3cschool笔记 本文将以Windows系统为例,教你用最简单的记事本创建并保存第一个HTML网页。 📝 第一步:准备工具 文本编辑器:使用系统自带的记事本&#xff…

React19源码系列之FiberRoot节点和Fiber节点

在上一篇文章,看了createRoot函数的大致流程。 createContainer函数创建并返回了FiberRoot 。FiberRoot是由createFiberRoot函数创建, createFiberRoot函数还将 FiberRoot和 根Fiber 通过current属性建立起了联系。将FiberRoot作为参数传给 ReactDOMRoo…

TCP协议的多线程应用、多线程下的网络编程

DAY13.2 Java核心基础 多线程下的网络编程 基于单点连接的方式,一个服务端对应一个客户端,实际运行环境中是一个服务端需要对应多个客户端 创建ServerSocketNable类,多线程接收socket对象 public class ServerSocketNable implements Run…

华为中小型企业项目案例

实验目的(1) 熟悉华为交换机和路由器的应用场景 (2) 掌握华为交换机和路由器的配置方法 实验拓扑实验拓扑如图所示。 华为中小型企业项目案例拓扑图 实验配置市场部和技术部的配置创建VLANLSW1的配置 [LSW1]vlan batch 10 20 [LSW1]q…

LabVIEW VI Scripting随机数波形图自动生成

通过LabVIEW VI Scripting 技术,实现从零开始编程化创建并运行一个随机数波形监测VI。核心功能包括自动化生成VI框架、添加控件与函数、配置数据流逻辑及界面布局优化,适用于批量生成测试工具、教学模板开发或复杂系统的模块化构建。通过脚本化操作&…

MATLAB 控制系统设计与仿真 - 26

状态空间控制系统概述 状态空间描述 现代控制理论是建立在状态空间基础上的控制系统分析和设计理论,它用状态变量来刻画系统的内部特征,用‘一节微分方程组’来描述系统的动态特性。系统的状态空间模型描述了系统输入/输出与内部状态之间的关系&#x…

Python----计算机视觉处理(Opencv:图像镜像旋转)

一、图像镜像旋转 图像的旋转是围绕一个特定点进行的,而图像的镜像旋转则是围绕坐标轴进行的。图像镜像旋转,也可 以叫做图像翻转,分为水平翻转、垂直翻转、水平垂直翻转三种。 通俗的理解为,当以图片的中垂线为x轴和y轴时&#x…

C++从入门到入土(八)——多态的原理

目录 前言 多态的原理 动态绑定与静态绑定 虚函数表 小结 前言 在前面的文章中,我们介绍了C三大特性之一的多态,我们主要介绍了多态的构成条件,但是对于多态的原理我们探讨的是不够深入的,下面这这一篇文章,我们将…

PyCharm安装redis,python安装redis,PyCharm使用失败问题

报错信息 Usage: D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip install [options] -r [package-index-options] … D:\wb2\wbrj_pys\venv\Scripts\python.exe -m pip instal…

保姆级离线TiDB V8+解释

以前学习的时候还是3版本,如今已经是8版本了 https://cn.pingcap.com/product-community/?_gl1ujh2l9_gcl_auMTI3MTI3NTM3NC4xNzM5MjU3ODE2_gaMTYwNzE2NTI4OC4xNzMzOTA1MjUz_ga_3JVXJ41175MTc0MTk1NTc1OC4xMS4xLjE3NDE5NTU3NjIuNTYuMC41NDk4MTMxNTM._ga_CPG2VW1Y4…

PyTorch 深度学习实战(17):Asynchronous Advantage Actor-Critic (A3C) 算法与并行训练

在上一篇文章中,我们深入探讨了 Soft Actor-Critic (SAC) 算法及其在平衡探索与利用方面的优势。本文将介绍强化学习领域的重要里程碑——Asynchronous Advantage Actor-Critic (A3C) 算法,并展示如何利用 PyTorch 实现并行化训练来加速学习过程。 一、A…

Docker换源加速(更换镜像源)详细教程(2025.3最新可用镜像,全网最详细)

文章目录 前言可用镜像源汇总换源方法1-临时换源换源方法2-永久换源(推荐)常见问题及对应解决方案1.换源后,可以成功pull,但是search会出错 补充1.如何测试镜像源是否可用2.Docker内的Linux换源教程 换源速通版(可以直…

SpringData Redis:RedisTemplate配置与数据操作

文章目录 引言一、Redis概述与环境准备二、RedisTemplate基础配置三、连接属性配置四、操作String类型数据五、操作Hash类型数据六、操作List类型数据七、操作Set类型数据八、操作ZSet类型数据九、事务与管道操作总结 引言 Redis作为高性能的NoSQL数据库,在分布式系…

Qt按钮控件常用的API

1.创建按钮 QPushButton *btnnew QPushButton; 以顶层方式弹出窗口控件 代码: #include "widget.h" #include "ui_widget.h" #include"QPushButton"Widget::Widget(QWidget *parent): QWidget(parent), ui(new Ui::Widget) {ui-&…

如何检查CMS建站系统的插件是否安全?

检查好CMS建站系统的插件安全是确保网站安全的重要环节,对于常见的安全检查,大家可以利用以下几种有效的方法和工具,来帮你评估插件的安全性。 1. 检查插件来源和开发者信誉 选择可信来源:仅从官方插件库或可信的第三方开发者处…

【Matlab GUI】封装matlab GUI为exe文件

注:封装后的exe还是需要有matlab环境才能运行 (1)安装MCRinstaller.exe文件,在matlab安装目录下的toolbox/compiler/deploy/win64文件夹里 (2)安装完MCRinstaller.exe,字命令窗口输入&#x…