当前位置:

首页 > 编程开发 > Redis中的分布式锁如何实现

Redis中的分布式锁如何实现

Redis Assistant
Redis Assistant

Redis Assistant 是一款为Redis设计的高效可视化管理与监控工具,提供多种操作模式,高效处理服务器数据。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。

立即下载
¥19
Windows/macOS/Linux 2026-08-17
正版软件 Windows macOS软件 Linux 数据库管理工具
本文目录

    什么是分布式锁说到Redis,我们第一想到的功能就是可以缓存数据,除此之外,Redis因为单进程、性能高的特点,它还经常被用于做分布式锁。锁我们都知道,在程序中的作用就是同步工具,保证共享资源在同一时刻只能被一个线程访问,Java中的锁我们都很熟悉了,像synchronized、Lock都是我们经常使用的,但是Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁。分布式锁,顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源,一般来说

    Redis中的分布式锁如何实现

    什么是分布式锁

    说到Redis,我们第一想到的功能就是可以缓存数据,除此之外,Redis因为单进程、性能高的特点,它还经常被用于做分布式锁。

    锁我们都知道,在程序中的作用就是同步工具,保证共享资源在同一时刻只能被一个线程访问,Java中的锁我们都很熟悉了,像synchronized 、Lock都是我们经常使用的,但是Java的锁只能保证单机的时候有效,分布式集群环境就无能为力了,这个时候我们就需要用到分布式锁。

    分布式锁,顾名思义,就是分布式项目开发中用到的锁,可以用来控制分布式系统之间同步访问共享资源,一般来说,分布式锁需要满足的特性有这么几点:

    1、互斥性:在任何时刻,对于同一条数据,只有一台应用可以获取到分布式锁;

    2、高可用性:在分布式场景下,一小部分服务器宕机不影响正常使用,这种情况就需要将提供分布式锁的服务以集群的方式部署;

    3、防止锁超时:如果客户端没有主动释放锁,服务器会在一段时间之后自动释放锁,防止客户端宕机或者网络不可达时产生死锁;

    4、独占性:加锁解锁必须由同一台服务器进行,也就是锁的持有者才可以释放锁,不能出现你加的锁,别人给你解锁了;

    业界里可以实现分布式锁效果的工具很多,但操作无非这么几个:加锁、解锁、防止锁超时。

    既然本文说的是Redis分布式锁,那我们理所当然就以Redis的知识点来延伸。

    实现锁的命令

    先介绍下Redis的几个命令,

    1、SETNX,用法是SETNX key value

    SETNX是『 SET if Not eXists』(如果不存在,则 SET)的简写,设置成功就返回1,否则返回0。

    Redis中的分布式锁如何实现

    setnx用法

    可以看出,当把key为lock的值设置为"Java"后,再设置成别的值就会失败,看上去很简单,也好像独占了锁,但有个致命的问题,就是key没有过期时间,这样一来,除非手动删除key或者获取锁后设置过期时间,不然其他线程永远拿不到锁。

    既然这样,我们给key加个过期时间总可以吧,直接让线程获取锁的时候执行两步操作:

    `SETNX Key 1`
    `EXPIRE Key Seconds`

    这个方案也有问题,因为获取锁和设置过期时间分成两步了,不是原子性操作,有可能获取锁成功但设置时间失败,那样不就白干了吗。

    不过也不用急,这种事情Redis官方早为我们考虑到了,所以就引出了下面这个命令

    2、SETEX,用法SETEX key seconds value

    将值 value 关联到 key ,并将 key 的生存时间设为 seconds (以秒为单位)。如果 key 已经存在,SETEX 命令将覆写旧值。

    这个命令类似于以下两个命令:

    `SET key value`
    `EXPIRE key seconds  # 设置生存时间`

    这两步动作是原子性的,会在同一时间完成。

    Redis中的分布式锁如何实现

    setex用法

    3、PSETEX ,用法PSETEX key milliseconds value

    这个命令和SETEX命令相似,但它以毫秒为单位设置 key 的生存时间,而不是像SETEX命令那样,以秒为单位。

    不过,从Redis 2.6.12 版本开始,SET命令可以通过参数来实现和SETNX、SETEX、PSETEX 三个命令相同的效果。

    就比如这条命令

    `SET key value NX EX seconds`

    加上NX、EX参数后,效果就相当于SETEX,这也是Redis获取锁写法里面最常见的。

    怎么释放锁

    释放锁的命令就简单了,直接删除key就行,但我们前面说了,因为分布式锁必须由锁的持有者自己释放,所以我们必须先确保当前释放锁的线程是持有者,没问题了再删除,这样一来,就变成两个步骤了,似乎又违背了原子性了,怎么办呢?

    不慌,我们可以用lua脚本把两步操作做拼装,就好像这样:

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

    KEYS[1]是当前key的名称,ARGV[1]可以是当前线程的ID(或者其他不固定的值,能识别所属线程即可),这样就可以防止持有过期锁的线程,或者其他线程误删现有锁的情况出现。

    代码实现

    知道了原理后,我们就可以手写代码来实现Redis分布式锁的功能了,因为本文的目的主要是为了讲解原理,不是为了教大家怎么写分布式锁,所以我就用伪代码实现了。

    首先是redis锁的工具类,包含了加锁和解锁的基础方法:

    `public class RedisLockUtil {`
     `private String LOCK_KEY = "redis_lock";`
     `// key的持有时间,5ms`
     `private long EXPIRE_TIME = 5;`
     `// 等待超时时间,1s`
     `private long TIME_OUT = 1000;`
     `// redis命令参数,相当于nx和px的命令合集`
     `private SetParams params = SetParams.setParams().nx().px(EXPIRE_TIME);`
     `// redis连接池,连的是本地的redis客户端`
     `JedisPool jedisPool = new JedisPool("127.0.0.1", 6379);`
     `/**`
     `* 加锁`
     `*`
     `* @param id`
     `*            线程的id,或者其他可识别当前线程且不重复的字段`
     `* @return`
     `*/`
     `public boolean lock(String id) {`
     `Long start = System.currentTimeMillis();`
     `Jedis jedis = jedisPool.getResource();`
     `try {`
     `for (;;) {`
     `// SET命令返回OK ,则证明获取锁成功`
     `String lock = jedis.set(LOCK_KEY, id, params);`
     `if ("OK".equals(lock)) {`
     `return true;`
     `}`
     `// 否则循环等待,在TIME_OUT时间内仍未获取到锁,则获取失败`
     `long l = System.currentTimeMillis() - start;`
     `if (l >= TIME_OUT) {`
     `return false;`
     `}`
     `try {`
     `// 休眠一会,不然反复执行循环会一直失败`
     `Thread.sleep(100);`
     `} catch (InterruptedException e) {`
     `e.printStackTrace();`
     `}`
     `}`
     `} finally {`
     `jedis.close();`
     `}`
     `}`
     `/**`
     `* 解锁`
     `*`
     `* @param id`
     `*            线程的id,或者其他可识别当前线程且不重复的字段`
     `* @return`
     `*/`
     `public boolean unlock(String id) {`
     `Jedis jedis = jedisPool.getResource();`
     `// 删除key的lua脚本`
     `String script = "if redis.call('get',KEYS[1]) == ARGV[1] then" + "   return redis.call('del',KEYS[1]) " + "else"`
     `+ "   return 0 " + "end";`
     `try {`
     `String result =`
     `jedis.eval(script, Collections.singletonList(LOCK_KEY), Collections.singletonList(id)).toString();`
     `return "1".equals(result);`
     `} finally {`
     `jedis.close();`
     `}`
     `}`
    `}`

    具体的代码作用注释已经写得很清楚了,然后我们就可以写一个demo类来测试一下效果:

    `public class RedisLockTest {`
     `private static RedisLockUtil demo = new RedisLockUtil();`
     `private static Integer NUM = 101;`
     `public static void main(String[] args) {`
     `for (int i = 0; i < 100; i++) {`
     `new Thread(() -> {`
     `String id = Thread.currentThread().getId() + "";`
     `boolean isLock = demo.lock(id);`
     `try {`
     `// 拿到锁的话,就对共享参数减一`
     `if (isLock) {`
     `NUM--;`
     `System.out.println(NUM);`
     `}`
     `} finally {`
     `// 释放锁一定要注意放在finally`
     `demo.unlock(id);`
     `}`
     `}).start();`
     `}`
     `}`
    `}`

    我们创建100个线程来模拟并发的情况,执行后的结果是这样的:

    Redis中的分布式锁如何实现

    代码执行结果

    可以看出,锁的效果达到了,线程安全是可以保证的。

    当然,上面的代码只是简单的实现了效果,功能肯定是不完整的,一个健全的分布式锁要考虑的方面还有很多,实际设计起来不是那么容易的。

    我们的目的只是为了学习和了解原理,手写一个工业级的分布式锁工具不现实,也没必要,类似的开源工具一大堆(Redisson),原理都差不多,而且早已经过业界同行的检验,直接拿来用就行。

    虽然功能是实现了,但其实从设计上来说,这样的分布式锁存在着很大的缺陷,这也是本篇文章想重点探讨的内容。

    分布式锁的缺陷

    一、客户端长时间阻塞导致锁失效问题

    客户端1得到了锁,因为网络问题或者GC等原因导致长时间阻塞,然后业务程序还没执行完锁就过期了,这时候客户端2也能正常拿到锁,可能会导致线程安全的问题。

    Redis中的分布式锁如何实现

    客户端长时间阻塞

    那么该如何防止这样的异常呢?我们先不说解决方案,介绍完其他的缺陷后再来讨论。

    二、redis服务器时钟漂移问题

    如果redis服务器的机器时钟发生了向前跳跃,就会导致这个key过早超时失效,比如说客户端1拿到锁后,key的过期时间是12:02分,但redis服务器本身的时钟比客户端快了2分钟,导致key在12:00的时候就失效了,这时候,如果客户端1还没有释放锁的话,就可能导致多个客户端同时持有同一把锁的问题。

    三、单点实例安全问题

    如果redis是单master模式的,当这台机宕机的时候,那么所有的客户端都获取不到锁了,为了提高可用性,可能就会给这个master加一个slave,但是因为redis的主从同步是异步进行的,可能会出现客户端1设置完锁后,master挂掉,slave提升为master,因为异步复制的特性,客户端1设置的锁丢失了,这时候客户端2设置锁也能够成功,导致客户端1和客户端2同时拥有锁。

    为了解决Redis单点问题,redis的作者提出了RedLock算法。

    RedLock算法

    该算法的实现前提在于Redis必须是多节点部署的,可以有效防止单点故障,具体的实现思路是这样的:

    1、获取当前时间戳(ms);

    2、先设定key的有效时长(TTL),超出这个时间就会自动释放,然后client(客户端)尝试使用相同的key和value对所有redis实例进行设置,每次链接redis实例时设置一个比TTL短很多的超时时间,这是为了不要过长时间等待已经关闭的redis服务。并且试着获取下一个redis实例。

    比如:TTL(也就是过期时间)为5s,那获取锁的超时时间就可以设置成50ms,所以如果50ms内无法获取锁,就放弃获取这个锁,从而尝试获取下个锁;

    3、client通过获取所有能获取的锁后的时间减去第一步的时间,还有redis服务器的时钟漂移误差,然后这个时间差要小于TTL时间并且成功设置锁的实例数>= N/2 + 1(N为Redis实例的数量),那么加锁成功

    比如TTL是5s,连接redis获取所有锁用了2s,然后再减去时钟漂移(假设误差是1s左右),那么锁的真正有效时长就只有2s了;

    4、如果客户端由于某些原因获取锁失败,便会开始解锁所有redis实例。

    根据这样的算法,我们假设有5个Redis实例的话,那么client只要获取其中3台以上的锁就算是成功了,用流程图演示大概就像这样:

    Redis中的分布式锁如何实现

    key有效时长

    好了,算法也介绍完了,从设计上看,毫无疑问,RedLock算法的思想主要是为了有效防止Redis单点故障的问题,而且在设计TTL的时候也考虑到了服务器时钟漂移的误差,让分布式锁的安全性提高了不少。

    但事实真的是这样吗?反正我个人的话感觉效果一般般,

    首先第一点,我们可以看到,在RedLock算法中,锁的有效时间会减去连接Redis实例的时长,如果这个过程因为网络问题导致耗时太长的话,那么最终留给锁的有效时长就会大大减少,客户端访问共享资源的时间很短,很可能程序处理的过程中锁就到期了。而且,锁的有效时间还需要减去服务器的时钟漂移,但是应该减多少合适呢,要是这个值设置不好,很容易出现问题。

    然后第二点,这样的算法虽然考虑到用多节点来防止Redis单点故障的问题,但但如果有节点发生崩溃重启的话,还是有可能出现多个客户端同时获取锁的情况。

    假设一共有5个Redis节点:A、B、C、D、E,客户端1和2分别加锁

    • 客户端1成功锁住了A,B,C,获取锁成功(但D和E没有锁住)。

    • 节点C的master挂了,然后锁还没同步到slave,slave升级为master后丢失了客户端1加的锁。

    • 客户端2这个时候获取锁,锁住了C,D,E,获取锁成功。

    这样,客户端1和客户端2就同时拿到了锁,程序安全的隐患依然存在。除此之外,如果这些节点里面某个节点发生了时间漂移的话,也有可能导致锁的安全问题。

    所以说,虽然通过多实例的部署提高了可用性和可靠性,但RedLock并没有完全解决Redis单点故障存在的隐患,也没有解决时钟漂移以及客户端长时间阻塞而导致的锁超时失效存在的问题,锁的安全性隐患依然存在。

    本文内容来源于网友投稿,如有侵权请联系删除。
    作者最新文章
    编程开发
    相关文章 更多
    PHP递归性能优化技巧与迭代替代方案
    PHP递归性能优化技巧与迭代替代方案

    解析PHP递归函数在树形数据处理中的性能瓶颈,提供预加载数据消除I/O、使用显式栈替代深层递归的实战方案,帮助开发者在代码可读性与执行效率间做出合理取舍。

    Java测试中怎么使用Mockito模拟依赖对象
    Java测试中怎么使用Mockito模拟依赖对象

    详细讲解在Java单元测试中如何使用Mockito模拟依赖对象,包括引入依赖、创建Mock、打桩返回值、行为验证以及Mock与Spy的核心差异和常见陷阱排查。

    链表删除节点的时间复杂度是多少及其详细分析
    链表删除节点的时间复杂度是多少及其详细分析

    详细分析链表删除节点的时间复杂度,深入探讨单链表与双向链表在不同已知前提下的查找与删除开销,并结合完整代码与清晰图解进行对比总结。

    codex如何配置模型参数及文件设置教程
    codex如何配置模型参数及文件设置教程

    想知道如何让AI写出的代码更贴合你的习惯?本文手把手教你在VS Code中调整Codex相关模型参数,通过修改配置文件优化温度值和令牌限制,解决代码建议不准确或响应慢的问题。

    Claude Code AI编程工具实力揭秘与编程助手实测
    Claude Code AI编程工具实力揭秘与编程助手实测

    通过实测展示Claude Code在终端中如何理解自然语言指令、自动修改代码文件并处理复杂编程任务,帮助开发者评估其实际辅助能力。

    winforms教程自学入门与基础开发步骤详解
    winforms教程自学入门与基础开发步骤详解

    本教程详细讲解如何使用Visual Studio创建WinForms项目,通过添加按钮和标签控件并编写点击事件代码,实现一个基础的计数器功能,适合C#初学者快速上手Windows窗体应用开发。

    Cursor自动补全设置教程教你快速开启代码补全功能
    Cursor自动补全设置教程教你快速开启代码补全功能

    详解Cursor编辑器中自动补全功能的开启与优化设置,涵盖Tab触发机制、上下文窗口调整及模型切换,帮助开发者解决补全延迟、干扰大等问题,提升编码流畅度。

    pandas的数据格式怎么转换和设置方法教程
    pandas的数据格式怎么转换和设置方法教程

    详解Pandas中数据格式转换的核心方法,包括astype强制转换、to_numeric容错处理及日期解析技巧,解决常见类型错误并提升数据处理效率。

    VS Code中文设置方法 简体语言包安装与切换教程
    VS Code中文设置方法 简体语言包安装与切换教程

    详细介绍在Visual Studio Code中安装Chinese (Simplified)语言包的方法,包括通过扩展市场搜索、安装及自动重启切换至简体中文界面的完整步骤,帮助开发者快速将编辑器本地化。

    cursor安装过程无法更改安装位置的解决方法
    cursor安装过程无法更改安装位置的解决方法

    针对Cursor安装包默认锁定C盘且无路径选择界面的问题,提供通过手动移动文件并创建目录联结(Symbolic Link)的解决方案,实现将软件安装在其他磁盘分区。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    WINDOWS 更多
    3dmax(3ds max)
    3dmax(3ds max)
    Windows

    Autodesk 3ds Max 是一款专业的三维建模、动画与渲染软件,广泛应用于建筑可视化、游戏开发、影视动画、广告设计和产品展示等领域。

    photoshop
    photoshop
    Windows、macOS 、 iPad

    Photoshop 2026 是 Adobe 推出的专业图像处理与视觉设计软件,支持 Windows、macOS 和 iPad 等平台,广泛应用于摄影修图、电商设计、平面海报、数字绘画及视觉合成等创作场景。

    Blender
    Blender
    Windows、macOS 和 Linux

    Blender 是一款免费开源、跨平台的专业 3D 创作软件,集建模、动画、渲染、视频编辑与视觉合成等功能于一体,广泛应用于影视动画、游戏设计和建筑可视化等领域。软件支持 Cycles 物理渲染器与 Eevee 实时渲染引擎,并提供多边形建模、骨骼绑定、物理模拟等专业工具。Blender 兼容 Windows、macOS 和 Linux 系统,安装包轻巧、运行流畅,依托活跃的全球开发者社区持续更新,是从初学者到专业创作者都值得选择的正版 3D 创作工具。

    即将离开本站
    您即将前往第三方网站,请确认是否继续?