【redis进阶】分布式锁
目录
一、什么是分布式锁
二、分布式锁的基础实现
三、引入过期时间
四、引入校验 id
五、引入lua
六、引入 watch dog (看门狗)
七、引入 Redlock 算法
八、其他功能
redis学习🥳
一、什么是分布式锁
在一个分布式的系统中,也会涉及到多个节点访问同一个公共资源的情况. 此时就需要通过 锁 来做互
斥控制,避免出现类似于 "线程安全" 的问题.
而 java 的 synchronized 或者 C++ 的 std::mutex,这样的锁都是只能在当前进程中生效,在分布式
系统中,是有很多进程的(每个服务器,都是独立的进程)因此,之前的锁,就难以对现在分布式系
统中的多个进程之间产生制约。分布式系统中,多个进程之间的执行顺序也是不确定的 =>随机性,此
时就需要使用到 “分布式锁”.
✍ 本质上就是使用一个公共的服务器,来记录 加锁状态.
这个公共的服务器可以是 Redis,也可以是其他组件(比如 MySQL 或者 ZooKeeper 等),还可以是我们自己写的一个服务.
二、分布式锁的基础实现
思路非常简单. 本质上就是通过一个键值对来标识锁的状态.
举个例子: 考虑买票的场景,现在存在多个服务器节点,每个车次的票数都是固定的.
现在车站提供了若干个车次,都可能需要处理这个买票的逻辑: 先查询指定车次的余票,如果余票 > 0,
则设置余票值 -= 1.

显然上述的场景是存在 "线程安全" 问题的,需要使用锁来控制.
否则就可能出现 "超卖" 的情况.
此时如何进行加锁呢?我们可以在上述架构中引入一个 Redis 作为分布式锁的管理器.

此时,如果 买票服务器1 尝试买票,就需要先访问 Redis,在 Redis 上设置一个键值对. 比如 key 就是车次,value 随便设置个值 (比如 1).
如果这个操作设置成功,就视为当前没有节点对该 001 车次加锁,就可以进行数据库的读写操作. 操作
完成之后,再把 Redis 上刚才的这个键值对给删除掉.
如果在买票服务器1 操作数据库的过程中,买票服务器2 也想买票,也会尝试给 Redis 上写一个键值对
key 同样是车次. 但是此时设置的时候发现该车次的 key 已经存在了,则认为已经有其他服务器正在持
有锁,此时 服务器2 就需要等待或者暂时放弃.
🎉 Redis 中提供了 setnx 操作,正好适合这个场景. 即: key 不存在就设置,存在则直接失败.
但是上述方案并不完整.
三、引入过期时间
当 服务器1 加锁之后,开始处理买票的过程中,如果 服务器1 意外宕机了,就会导致解锁操作 (删除该
key) 不能执行. 就可能引起其他服务器始终无法获取到锁的情况.
为了解决这个问题,可以在设置 key 的同时引入过期时间. 即这个锁最多持有多久,就应该被释放.
🌰 可以使用 set ex nx 的方式,在设置锁的同时把过期时间设置进去.
注意! 此处的过期时间只能使用一个命令的方式设置.
如果分开多个操作,比如 setnx 之后,再来一个单独的 expire,由于 Redis 的多个指令之间不存在关联,并且即使使用了事务也不能保证这两个操作都一定成功,因此就可能出现 setnx 成功,但是 expire 失败的情况.
此时仍然会出现无法正确释放锁的问题.
四、引入校验 id
对于 Redis 中写入的加锁键值对,其他的节点也是可以删除的.
比如 服务器1 写入一个 "001": 1 这样的键值对,服务器2 是完全可以把 "001" 给删除掉的.
当然,服务器2 不会进行这样的 "恶意删除" 操作,不过不能保证因为一些 bug 导致 服务器2 把锁误删除.
为了解决上述问题,我们可以引入一个校验 id.
比如可以把设置的键值对的 value,不再是简单的设为一个 1,而是设成服务器的编号.
形如 "001":"服务器1" .
这样就可以在删除 key (解锁)的时候,先校验当前删除 key 的服务器是否是当初加锁的服务器,如果
是,才能真正删除;不是则不能删除。逻辑用伪代码描述如下:
String key = [要加锁的资源 id];
String serverId = [服务器的编号];// 加锁, 设置过期时间为 10s
redis.set(key, serverId, "NX", "EX", "10s");// 执行各种业务逻辑, 比如修改数据库数据.
doSomeThing();// 解锁, 删除 key. 但是删除前要检验下 serverId 是否匹配.
if (redis.get(key) == serverId) {redis.del(key);
}
但是很明显,解锁逻辑是两步操作 "get" 和 "del",这样做并非是原子的.
五、引入lua
为了使解锁操作原子,可以使用 Redis 的 Lua 脚本功能.
🎁 Lua 也是一个编程语言. 读作 "撸啊". 是葡萄牙语中的 "月亮" 的意思. (出自于 Lua 官方文档https://www.lua.org/about.html)
Lua 的语法类似于 JS,是一个动态弱类型的语言. Lua 的解释器一般使用 C 语言实现. Lua 语法
简单精炼,执行速度快,解释器也比较轻量 (Lua 解释器的可执行程序体积只有 200KB 左右).
因此 Lua 经常作为其他程序内部嵌入的脚本语言. Redis 本身就支持 Lua 作为内嵌脚本.
很多程序都支持内嵌脚本,比如 MySQL 8 支持 JS 作为内嵌脚本,比如 Vim 支持 VimScript
和 Python 作为内嵌脚本.... 通过内嵌脚本来实现更复杂的功能,提供更强的扩展性.
Lua 除了和 Redis 搭伙之外,在很多场景也会作为内嵌脚本. 比如在游戏开发领域常常作为编写逻辑的语言. (比如魔兽世界、大话西游等)
使用 Lua 脚本完成上述解锁功能:
if redis.call('get',KEYS[1]) == ARGV[1] thenreturn redis.call('del',KEYS[1])
elsereturn 0
end;
上述代码可以编写成一个 .lua 后缀的文件,由 redis-cli 或者 redis-plus-plus 或者 jedis 等客户端加载,并发送给 Redis 服务器,由 Redis 服务器来执行这段逻辑.
一个 lua 脚本会被 Redis 服务器以原子的方式来执行.
redis-plus-plus 和 jedis 如何调用 lua ,咱们此处不做过多介绍. 具体 api 的写法大家可以自行研究.
六、引入 watch dog (看门狗)
上述方案仍然存在一个重要问题. 当我们设置了 key 过期时间之后 (比如 10s),仍然存在一定的可能
性,当任务还没执行完,key 就先过期了. 这就导致锁提前失效.
把这个过期时间设置的足够长,比如 30s, 是否能解决这个问题呢?很明显,设置多长时间合适,是无止境的. 即使设置再长,也不能完全保证就没有提前失效的情况.
而且如果设置的太长了,万一对应的服务器挂了,此时其他服务器也不能及时的获取到锁.
因此相比于设置一个固定的长时间,不如动态的调整时间更合适.
所谓 watch dog,本质上是加锁的服务器上的一个单独的线程,通过这个线程来对锁过期时间进行 "续
约".
注意,这个线程是业务服务器上的,不是 Redis 服务器的.
❤ 举个具体的例子:
初始情况下设置过期时间为 10s. 同时设定看门狗线程每隔 3s 检测一次.
那么当 3s 时间到的时候,看门狗就会判定当前任务是否完成.
• 如果任务已经完成,则直接通过 lua 脚本的方式,释放锁(删除 key).
• 如果任务未完成,则把过期时间重写设置为 10s. (即 "续约")
这样就不担心锁提前失效的问题了. 而且另一方面,如果该服务器挂了,看门狗线程也就随之挂了,此
时无人续约,这个 key 自然就可以迅速过期,让其他服务器能够获取到锁了.
七、引入 Redlock 算法
实践中的 Redis 一般是以集群的方式部署的 (至少是主从的形式,而不是单机). 那么就可能出现以下比
较极端的大冤种情况:
🍰 服务器1 向 master 节点进行加锁操作. 这个写入 key 的过程刚刚完成,master 挂了;slave节点升级成了新的 master 节点. 但是由于刚才写入的这个 key 尚未来得及同步给 slave 呢,此时就相当于 服务器1 的加锁操作形同虚设了,服务器2 仍然可以进行加锁 (即给新的 master 写入 key. 因为新的 master 不包含刚才的 key).
因此之前学的 哨兵(分布式场景中,涉及的数据量不大)和集群(更多解决的是存储空间不足的问
题)模式 是不可取的,为了解决这个问题,Redis 的作者提出了 Redlock 算法.
我们引入一组 Redis 节点. 其中每一组 Redis 节点都包含一个主节点和若干从节点. 并且组和组之间存
储的数据都是一致的,相互之间是"备份"关系 (而并非是数据集合的一部分,这点有别于 Redis cluster).
加锁的时候,按照一定的顺序,写多个 master 节点. 在写锁的时候需要设定操作的 "超时时间". 比如
50ms. 即如果 setnx 操作超过了 50ms 还没有成功,就视为加锁失败.

如果给某个节点加锁失败,就立即再尝试下一个节点.
当加锁成功的节点数超过总节点数的一半,才视为加锁成功.
如上图,一共五个节点,三个加锁成功,两个失败,此时视为加锁成功.
这样的话,即使有某些节点挂了,也不影响锁的正确性.
🎼 那么是否可能出现上述节点都同时遇到了 "大冤种" 情况呢?理论上这件事是可能发生的,但是概率太小了. 工程上就可以忽略不计了.
同理,释放锁的时候,也需要把所有节点都进行解锁操作. (即使是之前超时的节点,也要尝试解锁,尽
量保证逻辑严密).
简而言之,Redlock 算法的核心就是,加锁操作不能只写给一个 Redis 节点,而要写个多个!分布式
系统中任何一个节点都是不可靠的. 最终的加锁成功结论是 "少数服从多数的".
由于一个分布式系统不至于大部分节点都同时出现故障, 因此这样的可靠性要比单个节点来说靠谱不少.
八、其他功能
上述描述中我们解释了基于 Redis 的分布式锁的基本实现原理.
上述锁只是一个简单的互斥锁. 但是实际上我们在一些特定场景中,还有一些其他特殊的锁,比如:
• 可重入锁
• 公平锁(遵循先来后到原则)
• 读写锁
• ......
基于 Redis 的分布式锁,也可以实现上述特性. (当然了对应的实现逻辑也会更复杂).
此处我们不做过多讨论了.
实际开发中,我们也并不会真的自己实现一个分布式锁. 已经有很多现成的库帮我们封装好了,我们直
接使用即可.
比如 Java 中的 Redisson,C++ 中的 redis-plus-plus. 当然,有些大厂也会有自己版本的分布式锁的实现.
redis学习打卡🥳
相关文章:
【redis进阶】分布式锁
目录 一、什么是分布式锁 二、分布式锁的基础实现 三、引入过期时间 四、引入校验 id 五、引入lua 六、引入 watch dog (看门狗) 七、引入 Redlock 算法 八、其他功能 redis学习🥳 一、什么是分布式锁 在一个分布式的系统中,也会涉及到多个节点访问同一…...
园区管理系统如何提升企业核心竞争力与资产管理智能化水平
内容概要 在当今快节奏的商业环境中,园区管理系统正成为企业的重要合作伙伴,尤其在工业园、产业园、物流园、写字楼和公寓等多种类型的物业管理中。这个系统不仅仅是一个管理工具,它还是提升企业运营效率和核心竞争力的关键因素。通过智能化…...
AI大模型开发原理篇-3:词向量和词嵌入
简介 词向量是用于表示单词意义的向量, 并且还可以被认为是单词的特征向量或表示。 将单词映射到实向量的技术称为词嵌入。在实际应用中,词向量和词嵌入这两个重要的NLP术语通常可以互换使用。它们都表示将词汇表中的单词映射到固定大小的连续向量空间中…...
高精度算法:高精度减法
P2142 高精度减法 - 洛谷 | 计算机科学教育新生态 我们两个整数一定要是大数减去小数,所以这个点我们需要特判一下,那我们两个字符串表示的整型怎么判断大小呢,我们字典序比较大小和真实的数字比较大小是一样的,比如我们的‘21’…...
Java创建项目准备工作
新建项目 新建空项目 每一个空项目创建好后都要检查jdk版本 检查SDK和语言级别——Apply——OK 检查当前项目的Maven路径,如果已经配置好全局,就是正确路径不用管 修改项目字符集编码,将所有编码都调整为UTF-8 创建Spingboot工程 创建Spring…...
基于STM32的智能宠物喂食器设计
目录 引言系统设计 硬件设计软件设计 系统功能模块 定时喂食模块远程控制与视频监控模块食物存量检测与报警模块语音互动与用户交互模块数据记录与智能分析模块 控制算法 定时与手动投喂算法食物存量检测与低存量提醒算法数据记录与远程反馈算法 代码实现 喂食控制代码存量检测…...
在线课堂小程序设计与实现(LW+源码+讲解)
专注于大学生项目实战开发,讲解,毕业答疑辅导,欢迎高校老师/同行前辈交流合作✌。 技术范围:SpringBoot、Vue、SSM、HLMT、小程序、Jsp、PHP、Nodejs、Python、爬虫、数据可视化、安卓app、大数据、物联网、机器学习等设计与开发。 主要内容:…...
为AI聊天工具添加一个知识系统 之77 详细设计之18 正则表达式 之5
本文要点 昨天讨论了 本项目(AI聊天工具添加一个知识系统)中正则表达式模板的设计中可能要考虑到的一些问题(讨论到的内容比较随意,暂时无法确定 那些考虑 是否 应该是正则表达式模板设计要考虑的以及 是否完整)。今天…...
【Elasticsearch】 索引模板 ignore_missing_component_templates
解释 ignore_missing_component_templates 配置 在Elasticsearch中,ignore_missing_component_templates 是一个配置选项,用于处理索引模板中引用的组件模板可能不存在的情况。当您创建一个索引模板时,可以指定一个或多个组件模板࿰…...
Github 2025-01-29 C开源项目日报 Top10
根据Github Trendings的统计,今日(2025-01-29统计)共有10个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量C项目10C++项目1Assembly项目1Go项目1我的电视 - 安卓电视直播软件 创建周期:40 天开发语言:CStar数量:649 个Fork数量:124 次关注人数:64…...
文件上传2
BUUCTF 你传你🐎呢 先上传.htaccess 修改格式 即可上传成功 返回上传图片格式的木马 用蚁剑连接 5ecf1cca-59a1-408b-b616-090edf124db5.node5.buuoj.cn:81/upload/7d8511a847edeacb5385299396a96d91/rao.jpg 即可得到flag [GXYCTF2019]BabyUpload...
Unity敌人逻辑笔记
写ai逻辑基本上都需要状态机。因为懒得手搓状态机,所以选择直接用动画状态机当逻辑状态机用。 架构设计 因为敌人的根节点已经有一个animator控制动画,只能增加一个子节点AI,给它加一个animator指向逻辑“动画”状态机。还有一个脚本&#…...
高级编码参数
1.跳帧机制 参考资料:frameskipping-hotedgevideo 跳帧机制用于优化视频质量和编码效率。它通过选择性地跳过某些帧并使用参考帧来预测和重建视频内容,从而减少编码所需的比特率,同时保持较高的视频质量。在视频编码过程中,如果…...
DeepSeek-R1:通过强化学习激励大型语言模型(LLMs)的推理能力
摘要 我们推出了第一代推理模型:DeepSeek-R1-Zero和DeepSeek-R1。DeepSeek-R1-Zero是一个未经监督微调(SFT)作为初步步骤,而是通过大规模强化学习(RL)训练的模型,展现出卓越的推理能力。通过强…...
leetcode——合并K个有序链表(java)
给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表。 示例 1: 输入:lists [[1,4,5],[1,3,4],[2,6]] 输出:[1,1,2,3,4,4,5,6] 解释:链表数组如下&#…...
【Valgrind】安装报错: 报错有未满足的依赖关系: libc6,libc6-dbg
Valgrind 内存泄漏检测工具安装 安装 sudo apt install valgrind官方上也是如此 但是在我的系统(debian12)上却失败了: 报错有未满足的依赖关系: libc6 : 破坏: valgrind (< 1:3.19.0-1~) 但是 1:3.16.1-1 正要被安装 libc6-dbg : 依赖…...
vue3和vue2的区别有哪些差异点
Vue3 vs Vue2 主要差异对比指南 官网 1. 核心架构差异 1.1 响应式系统 Vue2:使用 Object.defineProperty 实现响应式 // Vue2 响应式实现 Object.defineProperty(obj, key, {get() {// 依赖收集return value},set(newValue) {// 触发更新value newValue} })Vue3…...
论文笔记(六十三)Understanding Diffusion Models: A Unified Perspective(六)(完结)
Understanding Diffusion Models: A Unified Perspective(六)(完结) 文章概括指导(Guidance)分类器指导无分类器引导(Classifier-Free Guidance) 总结 文章概括 引用: …...
NPM 使用介绍
NPM 使用介绍 引言 NPM(Node Package Manager)是Node.js生态系统中的一个核心工具,用于管理JavaScript项目的依赖包。无论是开发一个小型脚本还是构建大型应用程序,NPM都能极大地提高开发效率。本文将详细介绍NPM的使用方法,包括安装、配置、依赖管理、包发布等,帮助您…...
http3网站的设置(AI不会配,得人工配)
堡塔PHP项目中配置nginx1.26.0设置http3协议 # 文件所在服务器中的路径 /www/server/nginx/conf/nginx.confuser www www; worker_processes auto; error_log /www/wwwlogs/nginx_error.log crit; pid /www/server/nginx/logs/nginx.pid; worker_rlimit_nofile 512…...
Linux链表操作全解析
Linux C语言链表深度解析与实战技巧 一、链表基础概念与内核链表优势1.1 为什么使用链表?1.2 Linux 内核链表与用户态链表的区别 二、内核链表结构与宏解析常用宏/函数 三、内核链表的优点四、用户态链表示例五、双向循环链表在内核中的实现优势5.1 插入效率5.2 安全…...
(十)学生端搭建
本次旨在将之前的已完成的部分功能进行拼装到学生端,同时完善学生端的构建。本次工作主要包括: 1.学生端整体界面布局 2.模拟考场与部分个人画像流程的串联 3.整体学生端逻辑 一、学生端 在主界面可以选择自己的用户角色 选择学生则进入学生登录界面…...
Element Plus 表单(el-form)中关于正整数输入的校验规则
目录 1 单个正整数输入1.1 模板1.2 校验规则 2 两个正整数输入(联动)2.1 模板2.2 校验规则2.3 CSS 1 单个正整数输入 1.1 模板 <el-formref"formRef":model"formData":rules"formRules"label-width"150px"…...
项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
基于Springboot+Vue的办公管理系统
角色: 管理员、员工 技术: 后端: SpringBoot, Vue2, MySQL, Mybatis-Plus 前端: Vue2, Element-UI, Axios, Echarts, Vue-Router 核心功能: 该办公管理系统是一个综合性的企业内部管理平台,旨在提升企业运营效率和员工管理水…...
LRU 缓存机制详解与实现(Java版) + 力扣解决
📌 LRU 缓存机制详解与实现(Java版) 一、📖 问题背景 在日常开发中,我们经常会使用 缓存(Cache) 来提升性能。但由于内存有限,缓存不可能无限增长,于是需要策略决定&am…...
【LeetCode】算法详解#6 ---除自身以外数组的乘积
1.题目介绍 给定一个整数数组 nums,返回 数组 answer ,其中 answer[i] 等于 nums 中除 nums[i] 之外其余各元素的乘积 。 题目数据 保证 数组 nums之中任意元素的全部前缀元素和后缀的乘积都在 32 位 整数范围内。 请 不要使用除法,且在 O…...
go 里面的指针
指针 在 Go 中,指针(pointer)是一个变量的内存地址,就像 C 语言那样: a : 10 p : &a // p 是一个指向 a 的指针 fmt.Println(*p) // 输出 10,通过指针解引用• &a 表示获取变量 a 的地址 p 表示…...
基于鸿蒙(HarmonyOS5)的打车小程序
1. 开发环境准备 安装DevEco Studio (鸿蒙官方IDE)配置HarmonyOS SDK申请开发者账号和必要的API密钥 2. 项目结构设计 ├── entry │ ├── src │ │ ├── main │ │ │ ├── ets │ │ │ │ ├── pages │ │ │ │ │ ├── H…...
