Redis 分布式锁|从青铜到钻石的五种演进方案
一、本地锁的问题首先我们来回顾下本地锁的问题目前题目微服务被拆分成了四个微服务。前端请求进来时会被转发到不同的微服务。假如前端接收了 10 W 个请求每个微服务接收 2.5 W 个请求假如缓存失效了每个微服务在访问数据库时加锁通过锁synchronzied或lock来锁住自己的线程资源从而防止缓存击穿。这是一种本地加锁的方式在分布式情况下会带来数据不一致的问题比如服务 A 获取数据后更新缓存 key 100服务 B 不受服务 A 的锁限制并发去更新缓存 key 99最后的结果可能是 99 或 100但这是一种未知的状态与期望结果不一致。流程图如下所示二、什么是分布式锁基于上面本地锁的问题我们需要一种支持分布式集群环境下的锁查询 DB 时只有一个线程能访问其他线程都需要等待第一个线程释放锁资源后才能继续执行。生活中的案例可以把锁看成房门外的一把锁所有并发线程比作人他们都想进入房间房间内只能有一个人进入。当有人进入后将门反锁其他人必须等待直到进去的人出来。我们来看下分布式锁的基本原理如下图所示我们来分析下上图的分布式锁1.前端将 10W 的高并发请求转发给四个题目微服务。2.每个微服务处理 2.5 W 个请求。3.每个处理请求的线程在执行业务之前需要先抢占锁。可以理解为“占坑”。4.获取到锁的线程在执行完业务后释放锁。可以理解为“释放坑位”。5.未获取到的线程需要等待锁释放。6.释放锁后其他线程抢占锁。7.重复执行步骤 4、5、6。大白话解释所有请求的线程都去同一个地方“占坑”如果有坑位就执行业务逻辑没有坑位就需要其他线程释放“坑位”。这个坑位是所有线程可见的可以把这个坑位放到 Redis 缓存或者数据库这篇讲的就是如何用 Redis 做“分布式坑位”。三、Redis 的 SETNXRedis 作为一个公共可访问的地方正好可以作为“占坑”的地方。用 Redis 实现分布式锁的几种方案我们都是用 SETNX 命令设置 key 等于某 value。只是高阶方案传的参数个数不一样以及考虑了异常情况。我们来看下这个命令SETNX是set If not exist的简写。意思就是当 key 不存在时设置 key 的值存在时什么都不做。在 Redis 命令行中是这样执行的set key value NX我们可以进到 redis 容器中来试下SETNX命令。先进入容器docker exec -it 容器 id redis-cli然后执行 SETNX 命令将wukong这个 key 对应的 value 设置成1111。set wukong 1111 NX返回OK表示设置成功。重复执行该命令返回nil表示设置失败。四、青铜方案我们先用 Redis 的 SETNX 命令来实现最简单的分布式锁。3.1 青铜原理我们来看下流程图多个并发线程都去 Redis 中申请锁也就是执行 setnx 命令假设线程 A 执行成功说明当前线程 A 获得了。其他线程执行 setnx 命令都会是失败的所以需要等待线程 A 释放锁。线程 A 执行完自己的业务后删除锁。其他线程继续抢占锁也就是执行 setnx 命令。因为线程 A 已经删除了锁所以又有其他线程可以抢占到锁了。代码示例如下Java 中 setnx 命令对应的代码为setIfAbsent。setIfAbsent 方法的第一个参数代表 key第二个参数代表值。// 1.先抢占锁 Boolean lock redisTemplate.opsForValue().setIfAbsent(lock, 123); if(lock) { // 2.抢占成功执行业务 ListTypeEntity typeEntityListFromDb getDataFromDB(); // 3.解锁 redisTemplate.delete(lock); return typeEntityListFromDb; } else { // 4.休眠一段时间 sleep(100); // 5.抢占失败等待锁释放 return getTypeEntityListByRedisDistributedLock(); }一个小问题那为什么需要休眠一段时间因为该程序存在递归调用可能会导致栈空间溢出。3.2 青铜方案的缺陷青铜之所以叫青铜是因为它是最初级的肯定会带来很多问题。设想一种家庭场景晚上小空一个人开锁进入了房间打开了电灯然后突然断电了小空想开门出去但是找不到门锁位置那小明就进不去了外面的人也进不来。从技术的角度看setnx 占锁成功业务代码出现异常或者服务器宕机没有执行删除锁的逻辑就造成了死锁。那如何规避这个风险呢设置锁的自动过期时间过一段时间后自动删除锁这样其他线程就能获取到锁了。四、白银方案4.1 生活中的例子上面提到的青铜方案会有死锁问题那我们就用上面的规避风险的方案来设计下也就是我们的白银方案。还是生活中的例子小空开锁成功后给这款智能锁设置了一个沙漏倒计时⏳沙漏完后门锁自动打开。即使房间突然断电过一段时间后锁会自动打开其他人就可以进来了。4.2 技术原理图和青铜方案不同的地方在于在占锁成功后设置锁的过期时间这两步是分步执行的。如下图所示4.3 示例代码清理 redis key 的代码如下// 在 10s 以后自动清理 lock redisTemplate.expire(lock, 10, TimeUnit.SECONDS);完整代码如下// 1.先抢占锁 Boolean lock redisTemplate.opsForValue().setIfAbsent(lock, 123); if(lock) { // 2.在 10s 以后自动清理 lock redisTemplate.expire(lock, 10, TimeUnit.SECONDS); // 3.抢占成功执行业务 ListTypeEntity typeEntityListFromDb getDataFromDB(); // 4.解锁 redisTemplate.delete(lock); return typeEntityListFromDb; }4.4 白银方案的缺陷白银方案看似解决了线程异常或服务器宕机造成的锁未释放的问题但还是存在其他问题因为占锁和设置过期时间是分两步执行的所以如果在这两步之间发生了异常则锁的过期时间根本就没有设置成功。所以和青铜方案有一样的问题锁永远不能过期。五、黄金方案5.1 原子指令上面的白银方案中占锁和设置锁过期时间是分步两步执行的这个时候我们可以联想到什么事务的原子性Atom。原子性多条命令要么都成功执行要么都不执行。将两步放在一步中执行占锁设置锁过期时间。Redis 正好支持这种操作# 设置某个 key 的值并设置多少毫秒或秒 过期。 set key value PX 多少毫秒 NX 或 set key value EX 多少秒 NX然后可以通过如下命令查看 key 的变化ttl key下面演示下如何设置 key 并设置过期时间。注意执行命令之前需要先删除 key可以通过客户端或命令删除。# 设置 keywukongvalue1111过期时间5000ms set wukong 1111 PX 5000 NX # 查看 key 的状态 ttl wukong执行结果如下图所示每运行一次 ttl 命令就可以看到 wukong 的过期时间就会减少。最后会变为 -2已过期。5.2 技术原理图黄金方案和白银方案的不同之处获取锁的时候也需要设置锁的过期时间这是一个原子操作要么都成功执行要么都不执行。如下图所示5.3 示例代码设置lock的值等于123过期时间为 10 秒。如果10秒 以后lock 还存在则清理 lock。setIfAbsent(lock, 123, 10, TimeUnit.SECONDS);5.4 黄金方案的缺陷我们还是举生活中的例子来看下黄金方案的缺陷。5.4.1 用户 A 抢占锁用户 A 先抢占到了锁并设置了这个锁 10 秒以后自动开锁锁的编号为123。10 秒以后A 还在执行任务此时锁被自动打开了。5.4.2 用户 B 抢占锁用户 B 看到房间的锁打开了于是抢占到了锁设置锁的编号为123并设置了过期时间10 秒。因房间内只允许一个用户执行任务所以用户 A 和 用户 B 执行任务产生了冲突。用户 A 在15 s后完成了任务此时 用户 B 还在执行任务。用户 A 主动打开了编号为123的锁。用户 B 还在执行任务发现锁已经被打开了。用户 B 非常生气我还没执行完任务呢锁怎么开了5.4.3 用户 C 抢占锁用户 B 的锁被 A 主动打开后A 离开房间B 还在执行任务。用户 C 抢占到锁C 开始执行任务。因房间内只允许一个用户执行任务所以用户 B 和 用户 C 执行任务产生了冲突。从上面的案例中我们可以知道因为用户 A 处理任务所需要的时间大于锁自动清理开锁的时间所以在自动开锁后又有其他用户抢占到了锁。当用户 A 完成任务后会把其他用户抢占到的锁给主动打开。这里为什么会打开别人的锁因为锁的编号都叫做“123”用户 A 只认锁编号看见编号为“123”的锁就开结果把用户 B 的锁打开了此时用户 B 还未执行完任务当然生气了。六、铂金方案6.1 生活中的例子上面的黄金方案的缺陷也很好解决给每个锁设置不同的编号不就好了如下图所示B 抢占的锁是蓝色的和 A 抢占到绿色锁不一样。这样就不会被 A 打开了。做了个动图方便理解动图演示静态图更高清可以看看6.2 技术原理图与黄金方案的不同之处设置锁的过期时间时还需要设置唯一编号。主动删除锁的时候需要判断锁的编号是否和设置的一致如果一致则认为是自己设置的锁可以进行主动删除。6.3 代码示例// 1.生成唯一 id String uuid UUID.randomUUID().toString(); // 2. 抢占锁 Boolean lock redisTemplate.opsForValue().setIfAbsent(lock, uuid, 10, TimeUnit.SECONDS); if(lock) { System.out.println(抢占成功 uuid); // 3.抢占成功执行业务 ListTypeEntity typeEntityListFromDb getDataFromDB(); // 4.获取当前锁的值 String lockValue redisTemplate.opsForValue().get(lock); // 5.如果锁的值和设置的值相等则清理自己的锁 if(uuid.equals(lockValue)) { System.out.println(清理锁 lockValue); redisTemplate.delete(lock); } return typeEntityListFromDb; } else { System.out.println(抢占失败等待锁释放); // 4.休眠一段时间 sleep(100); // 5.抢占失败等待锁释放 return getTypeEntityListByRedisDistributedLock(); }1.生成随机唯一 id给锁加上唯一值。2.抢占锁并设置过期时间为 10 s且锁具有随机唯一 id。3.抢占成功执行业务。4.执行完业务后获取当前锁的值。5.如果锁的值和设置的值相等则清理自己的锁。6.4 铂金方案的缺陷上面的方案看似很完美但还是存在问题第 4 步和第 5 步并不是原子性的。时刻0s。线程 A 抢占到了锁。时刻9.5s。线程 A 向 Redis 查询当前 key 的值。时刻10s。锁自动过期。时刻11s。线程 B 抢占到锁。时刻12s。线程 A 在查询途中耗时长终于拿多锁的值。时刻13s。线程 A 还是拿自己设置的锁的值和返回的值进行比较值是相等的清理锁但是这个锁其实是线程 B 抢占的锁。那如何规避这个风险呢钻石方案登场。七、钻石方案上面的线程 A 查询锁和删除锁的逻辑不是原子性的所以将查询锁和删除锁这两步作为原子指令操作就可以了。7.1 技术原理图如下图所示红色圈出来的部分是钻石方案的不同之处。用脚本进行删除达到原子操作。7.2 代码示例那如何用脚本进行删除呢我们先来看一下这段 Redis 专属脚本if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end这段脚本和铂金方案的获取key删除key的方式很像。先获取 KEYS[1] 的 value判断 KEYS[1] 的 value 是否和 ARGV[1] 的值相等如果相等则删除 KEYS[1]。那么这段脚本怎么在 Java 项目中执行呢分两步先定义脚本用 redisTemplate.execute 方法执行脚本。// 脚本解锁 String script if redis.call(get,KEYS[1]) ARGV[1] then return redis.call(del,KEYS[1]) else return 0 end; redisTemplate.execute(new DefaultRedisScriptLong(script, Long.class), Arrays.asList(lock), uuid);上面的代码中KEYS[1] 对应“lock”ARGV[1] 对应“uuid”含义就是如果 lock 的 value 等于 uuid 则删除 lock。而这段 Redis 脚本是由 Redis 内嵌的 Lua 环境执行的所以又称作 Lua 脚本。那钻石方案是不是就完美了呢有没有更好的方案呢下篇我们再来介绍另外一种分布式锁的王者方案Redisson。八、总结本篇通过本地锁的问题引申出分布式锁的问题。然后介绍了五种分布式锁的方案由浅入深讲解了不同方案的改进之处。从上面几种方案的不断演进的过程中知道了系统中哪些地方可能存在异常情况以及该如何更好地进行处理。举一反三这种不断演进的思维模式也可以运用到其他技术中。下面总结下上面五种方案的缺陷和改进之处。青铜方案缺陷业务代码出现异常或者服务器宕机没有执行主动删除锁的逻辑就造成了死锁。改进设置锁的自动过期时间过一段时间后自动删除锁这样其他线程就能获取到锁了。白银方案缺陷占锁和设置锁过期时间是分步两步执行的不是原子操作。改进占锁和设置锁过期时间保证原子操作。黄金方案缺陷主动删除锁时因锁的值都是相同的将其他客户端占用的锁删除了。改进每次占用的锁随机设为较大的值主动删除锁时比较锁的值和自己设置的值是否相等。铂金方案缺陷获取锁、比较锁的值、删除锁这三步是非原子性的。中途又可能锁自动过期了又被其他客户端抢占了锁导致删锁时把其他客户端占用的锁删了。改进使用 Lua 脚本进行获取锁、比较锁、删除锁的原子操作。钻石方案缺陷非专业的分布式锁方案。改进Redission 分布式锁。

相关新闻

AutoCVE是一款基于多Agent协同的全流程自动化CVE挖掘与源码审计系统

AutoCVE是一款基于多Agent协同的全流程自动化CVE挖掘与源码审计系统

0x01 工具介绍 AutoCVE是一款基于多Agent协同的全流程自动化CVE挖掘与源码审计系统,依托智能调度与ReAct循环机制,融合多模式审计能力,可自动完成项目筛查、漏洞挖掘、验证研判与报告生成。实测表现亮眼,可在一周内挖掘30个合规高…

2026/7/8 6:46:59阅读更多 →
你的SEO排名再高也挡不住AI搜索的降维打击

你的SEO排名再高也挡不住AI搜索的降维打击

某B2B企业市场总监打开Google Search Console。过去半年,官网SEO流量稳步上涨。核心关键词"制造业MES系统"排到了搜索结果第一页。团队付出的心血有了回报。但同一周,他发现了一个让人后背发凉的趋势。越来越多用户不再点击搜索结果。他们直接…

2026/7/8 6:46:59阅读更多 →
直付通二级商户的“信任杠杆”:用好一级方的背书效应

直付通二级商户的“信任杠杆”:用好一级方的背书效应

在商业合作中,有一种无形的力量常常被低估,那就是“信任杠杆”。简单来说,就是当你与一个本身就值得信任的主体建立合作关系时,这种信任会自动辐射到你身上,让你在与第三方打交道时更容易获得认可。对于二级直付通商户…

2026/7/8 6:46:59阅读更多 →
KMS智能激活脚本:5分钟解决Windows和Office永久激活问题

KMS智能激活脚本:5分钟解决Windows和Office永久激活问题

KMS智能激活脚本:5分钟解决Windows和Office永久激活问题 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统激活弹窗而烦恼吗?KMS_VL_ALL_AIO智能激活脚本…

2026/7/8 7:42:18阅读更多 →
【Springboot毕设全套源码+文档】基于springboot高校教室设备故障报修信息管理系统的设计与实现(丰富项目+远程调试+讲解+定制)

【Springboot毕设全套源码+文档】基于springboot高校教室设备故障报修信息管理系统的设计与实现(丰富项目+远程调试+讲解+定制)

博主介绍:✌️码农一枚 ,专注于大学生项目实战开发、讲解和毕业🚢文撰写修改等。全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战 ✌️技术范围:&am…

2026/7/8 7:42:18阅读更多 →
KMS_VL_ALL_AIO:智能激活解决方案全面指南

KMS_VL_ALL_AIO:智能激活解决方案全面指南

KMS_VL_ALL_AIO:智能激活解决方案全面指南 【免费下载链接】KMS_VL_ALL_AIO Smart Activation Script 项目地址: https://gitcode.com/gh_mirrors/km/KMS_VL_ALL_AIO 还在为Windows系统激活弹窗而烦恼吗?KMS_VL_ALL_AIO智能激活脚本为您提供了一站…

2026/7/8 7:42:18阅读更多 →
KLayout完整指南:如何免费掌握专业版图设计与验证工具

KLayout完整指南:如何免费掌握专业版图设计与验证工具

KLayout完整指南:如何免费掌握专业版图设计与验证工具 【免费下载链接】klayout KLayout Main Sources 项目地址: https://gitcode.com/gh_mirrors/kl/klayout KLayout是一款功能强大的开源版图设计软件,专门为半导体芯片设计和集成电路布局验证而…

2026/7/8 7:42:18阅读更多 →
羽毛球馆场地好

羽毛球馆场地好

引言如今,羽毛球作为一项集个人技巧与团队合作为一体的体育运动,在许多人中饱受欢迎。其身影不仅出现在正式赛事的赛场上,也成为了日常健身娱乐的重要选择之一。位于无锡新吴区的【星方羽毛球馆】迎接着越来越多的爱好者和专业选手&#xff0…

2026/7/8 7:42:18阅读更多 →
上海四驱升降桌哪款承重又大又不晃

上海四驱升降桌哪款承重又大又不晃

上海四驱升降桌:大承重不晃动的秘诀 如果你正为选择一张既承重大又稳定不晃的升降桌而烦恼,上海的一家品牌或许能提供一种可靠方案。它是上海棹浦智能技术有限公司旗下的读立升降桌(VIVISTAND),近十年来专注解决久坐办…

2026/7/8 7:37:18阅读更多 →
从GitHub安全案例解析常见漏洞与防护实践

从GitHub安全案例解析常见漏洞与防护实践

1. 项目概述:从GitHub Trending看安全实战 最近在GitHub Trending上看到一个项目,叫 skills4/skills ,它因为一些安全漏洞案例被大家讨论。这其实是一个挺典型的场景:一个旨在展示或教授某种技能的仓库,本身却成了安…

2026/7/8 5:12:14阅读更多 →
MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

MLT 2026启示:因果推理与概率建模驱动下一代LLM应用

# MLT 2026启示:因果推理与概率建模驱动下一代LLM应用## 一、背景与挑战:从“黑箱预测”到“可信推理”2026年6月,第7届机器学习与趋势国际会议(MLT 2026)将在悉尼召开。会议议程中,“因果与可解释机器学习…

2026/7/8 7:00:12阅读更多 →
通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

通达OA SQL注入漏洞深度剖析:从手工注入到自动化利用与防御

1. 项目概述与漏洞背景最近在梳理一些历史OA系统的安全风险时,通达OA v11.6版本中的一个老漏洞又进入了我的视线。这个漏洞位于/general/bi_design/appcenter/report_bi.func.php文件中,是一个典型的SQL注入点。虽然这个漏洞的利用方式看起来并不复杂&am…

2026/7/8 2:26:06阅读更多 →
作为一个给团队打绩效的人,我想说几句

作为一个给团队打绩效的人,我想说几句

我每半年都会给团队成员打绩效,也会参与和 CTO 的绩效校准,所以从管理者的视角,说说这件事 首先,我先把结论告诉你:接受结果,但一定要把原因问清楚。 因为当绩效公布到你这里的时候,结果基本已…

2026/7/8 0:01:17阅读更多 →
A股股指期货:全维度解析(多表格结构化完整版)

A股股指期货:全维度解析(多表格结构化完整版)

一、基础定义与核心本质股指期货全称股票价格指数期货,是中国金融期货交易所(中金所)上市的标准化金融期货合约,交易标的为 A 股大盘指数,约定未来特定时间按约定价格现金交割指数涨跌差价,不交割一篮子股票…

2026/7/8 0:01:17阅读更多 →
iOS越狱新手指南:从困惑到掌控,3天解锁iPhone无限潜能的真实故事

iOS越狱新手指南:从困惑到掌控,3天解锁iPhone无限潜能的真实故事

iOS越狱新手指南:从困惑到掌控,3天解锁iPhone无限潜能的真实故事 【免费下载链接】Jailbreak iOS 26.4 - 26, 17 - 17.7.5 & iOS 18 - 18.7.3 Jailbreak Tools, Cydia/Sileo/Zebra Tweaks & Jailbreak News Updates || AI Jailbreak Finder &…

2026/7/8 0:01:17阅读更多 →
YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

YOLOv8推理性能优化:从1.2FPS到35FPS的全链路加速实践

如果你在部署 YOLOv8 时,发现推理速度只有可怜的 1-2 FPS,而别人的演示视频却能跑到 30 FPS 以上,那么问题很可能不在模型本身,而在于你的整个处理链路。很多开发者拿到一个训练好的 YOLOv8 模型后,会直接使用官方示例…

2026/7/8 6:59:54阅读更多 →
Coze与Dify对比指南:低代码AI应用开发从入门到实战

Coze与Dify对比指南:低代码AI应用开发从入门到实战

1. 从零到一:为什么你需要了解 Coze 和 Dify?如果你对 AI 应用开发感兴趣,但一看到“大模型”、“智能体”、“工作流”这些词就头疼,觉得门槛太高,那这篇文章就是为你准备的。很多开发者,包括我自己&#…

2026/7/7 5:11:21阅读更多 →
AI生图工具怎么选?2026年6月版实测对比

AI生图工具怎么选?2026年6月版实测对比

做自媒体的朋友应该都有体会:配图一直是个让人头疼的问题。2026年,AI生图工具已经非常成熟了,但工具太多反而不知道怎么选。以下是截至2026年6月我对主流AI生图工具的实测对比。Midjourney V8.1:速度之王2026年6月11日&#xff0c…

2026/7/7 5:11:21阅读更多 →