深入学习 Redis - 分布式锁底层实现原理,以及实际应用

news2025/3/2 1:31:45

目录

一、Redis 分布式锁

1.1、什么是分布式锁

1.2、分布式锁的基础实现

1.2.1、引入场景

1.2.2、基础实现思想

1.2.3、引入 setnx

1.3、引入过期时间

1.4、引入校验 id

1.5、引入 lua 脚本

1.5.1、引入 lua 脚本的原因

1.5.2、lua 脚本介绍

1.6、过期时间续约问题(看门狗 Watch Dog)

1.7、引入 redlock 算法

1.8、分布式锁扩展


一、Redis 分布式锁


1.1、什么是分布式锁

锁就是用来解决线程安全的,分布式锁又是什么呢?

之前所学过的 synchronized 本质上都是只能在一个进程内部生效的,而在分布式系统中,是有很多进程的(每个服务器都是一个独立的进程),多个进程之间的执行顺序也是不确定的(随机的 ),之前的锁,就很应对分布式系统中多个进程之间产生的制约.

因此,就需要引入 “分布式锁” 来解决上述问题.

分布式锁本质上就是一个公共的服务器,用来记录锁的状态。

Ps:这个公共的服务器可以是Redis, 也可以是其他组件(比如 MySQL 或者 ZooKeeper 等), 还可以是我们自己写的⼀个服务

1.2、分布式锁的基础实现

1.2.1、引入场景

想象这样一个场景:买车票.

实现思路:先查询剩余票数,如果剩余票数 > 0,则设置剩余票数 -= 1.

在没有引入分布式锁之前,就有可能出现以下买票场景:

客户端1 先执行查询余票,发现剩余 1 张,在即将执行 剩余票数 -= 1 过程之前,客户端2 也执行了查询余票,发现也是剩余 1 张,客户端2  也会执行 剩余票数 -= 1. 的过程.

这里就出现了 “超卖” 的场景!!!1 张票,卖给了两个人.

1.2.2、基础实现思想

分布式锁的实现思路很简单,本质上是使用一个/一组 单独的服务器程序,通过用一个键值对来标识锁的状态,来给其他服务器提供 “加锁” 这样的服务.

对于上述场景,进行买票操作过程中,就需要先加锁.

具体的,往 redis 上设置一个特殊的 key - value,接着完成买票操作,再把这个 key - value 删除掉.  如果在 客户端1 买票的期间,客户端2 也想去买票,就也会尝试设置 key - value,如果发现  key - value 已经存在,就分为 “加锁失败”(是放弃还是阻塞,就要看具体的实现策略了).

这样就保证了 第一个服务器执行 “查询 -> 更新” 过程中,第二个服务器不会执行 “查询” ,也就解决了上述 “超卖” 问题.

Ps:买票的场景使用 mysql 的事务,也可以批量执行 查询 + 修改 操作. 但是分布式系统中,要访问的共享资源不一定是 mysql ....... 也可能是其他存储介质,没有事务. 也可能是执行一段特定的操作,是通过统一的服务器完成指定动作.

1.2.3、引入 setnx

针对于刚刚买票的场景:“key 不存在就设置成功,不存在就设置失败”. 使用 setnx 就可以达到 “加锁” 的效果. 针对解锁,就可以使用 del 命令来完成.

如果某个服务器,加锁成功了(setnx 成功),执行后续逻辑中,还没来得及执行 “解锁” 程序就崩溃了,怎么办?

以前在一个进程中,为了保证解锁的操作能执行到,可以把解锁的操作放到 finally 中,但是这种做法,只是针对进程内的锁有效,针对分布式锁,无效!

比如,服务器直接掉电,进程直接异常终止,这就会导致 redis 上设置的 key 无人删除,也就导致其他服务器无法获取到锁了.

1.3、引入过期时间

针对上述 “没来得及解锁,服务器宕机的情况”,我们可以给 key 设置一个过期时间.

通过 set ex nx 这样的命令完成设置,一旦时间到了,key 就会自动被删除掉.

比如,设置 key 的过期时间,为 1000ms,那么即使出现极端情况,某个服务器挂了,没有真正释放锁,这个锁最多保持 1000ms,也就自动释放了.

 可以通过先 setnx ,再使用 expire 的方式设置过期时间么?

不可以!!!务必要使用 set ex nx 的方式来设置!

redis 上多个命令之间,无法保证原子性,即使使用 事务,也不能保证这两个操作都能成功(redis 的事务只能保证不被 “插队”,不能保证操作成功). 此时就有可能出现 setnx 成功,expire 失败的场景.

1.4、引入校验 id

所谓锁,就是 redis 上的普通键值对.

所谓加锁,就是给 redis 上设置一个 key - value.

所谓解锁,就是把 redis 上这个 key - value 删除掉.

是否可能出现 服务器1  执行了加锁,服务器2 执行了解锁?

正常来说,肯定不是故意的,但是代码总会有 bug,不小心执行了解锁操作,就让这锁形同虚设,带来严重后果(比如 超卖).

为了解决上述问题,就需要引入一点校验机制.

具体的,如下步骤:

1. 给服务器编号,让每个服务器都有一个自己的身份标识.

2. 进行加锁的时候,设置 key - value, key 就表示要针对哪个资源加锁,value 就表示服务器的编号.

后续在解锁的时候,就可以进行校验了.  解锁的时候,先查询一下这个锁对应的服务器编号,然后判定这个编号是否就是当前执行解锁的服务器编号,如果是,才真正执行 del,如果不是,就失效.

通过上述操作,就可以有效避免 “误解锁”.

1.5、引入 lua 脚本

1.5.1、引入 lua 脚本的原因

一个服务器内部,也可能是多线程的. 此时,就可能两个线程都在执行 “解锁” 操作.

例如如下场景: 

首先我们知道,解锁的操作分为两步,先通过 GET 服务器编号进行校验,校验成功后在进行 DEL.

在 服务器1 中,线程A 执行 GET 后 线程 B 也执行 GET,然后 线程A 执行 DEL 解锁,此时 线程 B 也执行 DEL 解锁.

上述情况,看起来好像重复执行 DEL 好像问题不大?实则不然!

如果此时还有一个服务器,执行加锁,就可能出问题了.

在 线程A 执行完 DEL 之后,线程 B 执行 DEL 之前,服务器2 的 线程C 正好要执行 加锁(set ex nx),此时,由于 A 已经解锁了,C 的加锁能成功,但是紧接着,线程 B DEL 就来了,就把 服务器2 刚刚的加锁操作给解除了.

归根结底,还是因为 get 和 del 不是一条原子操作产生的问题.

使用事务,虽然可以解决上述问题(redis 事务虽然弱,但是能够避免插队),但是实践中,往往使用更好的方案 —— lua 脚本.

1.5.2、lua 脚本介绍

lua 语言特别轻量(实现一个 lua 解释器,消耗的体积非常小),可以使用 lua 编写一些逻辑,把这个脚本上传到 redis 服务器上,然后就可以让客户端来控制 redis 执行上述脚本了.

最重要的一点就是,redis 执行一个 lua 脚本,就相当于在 redis 上执行一个命令一样,是原子的. 并且 redis 官方文档中也明确说,lua 就属于是 事务 的替代方案.

例如前面的 “买票” 案例.

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

ARGV[1]:表示调用脚本给定的参数,此处要传入一个服务器的 id.

如果 id 和 get 到参数匹配,就进行删除操作.

1.6、过期时间续约问题(看门狗 Watch Dog)

加锁的时候,给 key 设定 过期时间,设置成多少合适?

  • 如果设置的时间过短,就可能在 业务逻辑 还没有执行完,就释放锁了.
  • 如果设置的时间太长,就可能导致 “锁释放的不及时” 的问题.

最好的方式就是 “动态续约”.

具体的,初始情况下,设置一个过期时间(比如 1s),在还剩 300ms 的时候(这里的时间不一定是 300ms,数据灵活调整),如果当前任务还没有执行完,就把过期时间续上 1s,等到时间快到了,任务还没执行完,就再续(无限续杯)~

上述过程中,如何知道当前任务还没有执行完,要进行续杯呢?实际上,服务器这边有一个专门的线程,负责续约这个事情,这个线程也叫做 “看门狗”(这是一个比较广义的概念,很多涉及到过期时间的操作都会引入 “看门狗” ).

这样,即使服务器中途崩溃了,没人负责续约了,锁也能在短时间内自动释放.

这就好比,吃自助餐,老板都是鼓励大家,每次少拿点,少量多次~  怕的就是你一次拿太多,吃不完,大部分都剩下了. 如果每次少拿点,即使吃不下了,浪费的也不多了.

1.7、引入 redlock 算法

使用 redis 作为分布式锁,redis 本身有没有可能挂了呢?

是很有可能的!

实际工作中的 redis 都是以集群的方式部署的(至少是主从,不会是单机),那么就有可能出现以下大冤种的情况:

服务器1 向 master 节点进行加锁操作. 这个写⼊ key 的过程刚刚完成, master 挂了; slave 节点升级成了新的 master 节点. 但是由于刚才写⼊的这个 key 尚未来得及同步给 slave(主节点和从节点之间的数据同步,是存在延迟的), 此时 就相当于服务器1 的加锁操作形同虚设了, 服务器2 仍然可以进行加锁.

为了解决以上问题,就提出了 redlock 算法(redis 作者给出的方案)

  1. 此处加锁,就是按照一定的顺序,针对这组 redis 都进行加锁操作. 
  2. 如果某个节点加不上锁,没关系,可能是 redis 挂了,继续给下一个节点加锁即可.
  3. 如果写入 key 成功的节点个数超过总数的一半,就视为 加锁成功.
  4. 同理,进行解锁的时候,也会把上述节点都解锁一遍.

1.8、分布式锁扩展

上面介绍的只是简单的 “互斥锁”.

锁这里还涉及到一些其他情况:

1.  读写锁.

2.  公平锁.

3.  可重入锁

........

基于 redis 也可以实现上述锁的特性,这里大家下来可以自己尝试实现以下~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1026625.html

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

相关文章

OpenMMLab MMYOLO目标检测应用示例与常见问题(三)

基于MMYOLO的电离图实时目标检测基准 数据集 数字电离图是获取实时电离层信息的最重要方式。电离层结构检测对于准确提取电离层关键参数具有重要的研究意义。 本研究利用中国科学院在海南、武汉和怀来获得的4311张不同季节的电离图建立数据集。使用labelme手动注释包括 Laye…

实现高并发内存池(C++)

什么是内存池 池化技术 所谓“池化技术”,就是程序先向系统申请过量的资源,然后自己管理以备不时之需。之所以要申请过量的资源,是因为每次申请该资源都有较大的开销,不如提前申请好,这样使用时就会变得非常快捷&…

2023年华数杯数学建模C题母亲身心健康对婴儿成长的影响解题全过程文档及程序

2023年华数杯全国大学生数学建模 C题 母亲身心健康对婴儿成长的影响 原题再现: 母亲是婴儿生命中最重要的人之一,她不仅为婴儿提供营养物质和身体保护,还为婴儿提供情感支持和安全感。母亲心理健康状态的不良状况,如抑郁、焦虑、…

同为科技(TOWE)专业防雷滤波桌面PDU超级插座

2023年第19届杭州亚运会开幕在即,相较于以往,本届杭州亚运会的一大看点就是电竞项目将首次以正式比赛项目的身份亮相亚运赛场,让更多受众能够领略电竞的魅力。当前社会,电竞作为一种新兴的娱乐、社交方式,让很多年轻人…

Feign实战-Springboot集成OpenFeign Demo以及参数详解

最近整理一下微服务的文章,先拿一直用的OpenFeign开刀 思考:微服务之间如何方便优雅的实现服务间的远程调用 一、说说openFeign是什么吧? 说到这个,那不得不先说说RPC 1.什么是RPC RPC 全称是 Remote Procedure Call &#x…

程序员还在为兼职接单发愁,快看看这几个宝藏平台吧!!!

在当今经济发展压力巨大的背景下,总有人还在与生活顽强对抗,拿什么和生活对抗嘞,那当然只有马内咯,尤其是对于程序员而言,现在内卷严重,如果能做一点线上兼职搞点马内,那岂不美哉,如…

计算机毕业设计 基于SpringBoot餐厅点餐系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

Android ANR问题触发机制

1 Anr类型 ​ Anr一般有四种类型。 1.1 input dispatching timeout ​ 主要时按键或触摸屏事件在5s内没有响应。这个时间在ActivityManagerService中定义。 C:\Users\wangjie\AppData\Local\Android\Sdk\sources\android-32\com\android\server\am\ActivityManagerService.…

嵌入式学习 - 用电控制电

目录 前言: 1、继电器 2、二极管 3、三极管 3.1 特殊的三极管-mos管 3.2 npn类型三极管 3.3 pnp类型三极管 3.4 三极管的放大特性 3.5 mos管和三极管的区别 前言: 计算机的工作的核心原理:用电去控制电。 所有的电子元件都有数据手册…

window隐私保护设置(win10)

window隐私保护设置(win10) 1、文件夹选项中,把隐私模块中关于快速访问的选项√去掉 2、个性化中开始模块根据需要关闭需要的选项,最后一个可以关闭任务栏应用历史记录 小尾巴~~ 只要有积累,就会有进步

Multispectral and hyperspectral image fusion in remote sensing: A survey

遥感中的多光谱和高光谱图像融合:一项调查 把融合方法分为:全色锐化、基于分解、基于机器学习;总结了常用数据集和性能评估指标;提出未解决的问题绘制指导方针。 针对这一任务,有叫多光谱与高光谱融合、高光谱超分辨率、高光谱锐化、超锐化 全色锐化相关方法 HS和MS图像…

牛客网Verilog刷题 | 快速入门-基础语法

1、VL1 四选一多路器 描述 制作一个四选一的多路选择器,要求输出定义上为线网类型 状态转换: d0 11 d1 10 d2 01 d3 00 信号示意图: 输入描述: 输入信号 d1,d2,d3,d4 sel 类型 wire 输出描述: 输出信号 mux_ou…

抖音seo优化排名源码搭建

抖音seo优化排名技术开发源码搭建: 思路:看上去比较简单,貌似使用 get、set 这两个 trap 就可以,但实际上并不是。实际上还需要实现 has, ownKeys , getOwnPropertyDescriptor 这些 trap,这样就能最大限度的限制私有属…

语义分割——灰度图像转伪彩色图像

目录 检验灰度图检验代码 灰度图转伪彩色图代码转换代码使用细则 示例转换结果总结 检验灰度图 制作语义分割数据集或用训练好模型测试图像时,得到的结果是灰度图像,如下: 检验代码 上面图像灰度值不是全是全为0,灰度范围在[0…

接口测试工具Postman使用实践

一、接口定义 软件不同部分之间的交互接口。通常就是所谓的API――应用程序编程接口,其表现的形式是源代码。 —— [ 百度百科 ] 我们常说的接口一般指两种: (1)API:应用程序编程接口。程序间的接口 (2…

Docker与Kubernetes集成以实现云原生应用程序:云原生在线教育平台

文章目录 项目概述项目架构项目步骤项目收益 🎈个人主页:程序员 小侯 🎐CSDN新晋作者 🎉欢迎 👍点赞✍评论⭐收藏 ✨收录专栏:云计算 ✨文章内容:云原生在线教育平台 🤝希望作者的文…

【Zabbix】Zabbix结合WxPusher推送告警消息

WxPusher简单介绍 什么是WxPusher WxPusher (微信推送服务)是一个使用微信公众号作为通道的,实时信息推送平台,你可以通过调用API的方式,把信息推送到微信上,无需安装额外的软件,即可做到信息实时通知。 你可以使用W…

【Spring Cloud系列】Feign详解与实战

Feign详解与实战 文章目录 Feign详解与实战一、概述二、什么是Feign三、Feign特性四、Feign简单使用3.1 Feign使用步骤3.2 Feign具体使用1. 引入依赖2. 启动类上添加注解3.编写FeignClient接口 五、使用Feign发起http请求5.1 Maven导入Feign配置,并集成Jackson5.2 F…

如何理解功率谱/能量谱密度?它与自相关函数的关系?

目录 1. 自相关函数: 2.功率谱密度 /能量谱密度 什么是 能量信号? 什么是 功率信号? 什么是 能量谱密度? 什么是 功率谱密度? 3.自相关函数 与 能量谱密度/功率谱密度 的关系? 4.周期性的功率信号的 功…

安卓ROM定制 修改必备常识-----初步了解system系统分区文件夹的基本含义 【二】

安卓修改rom 固件 修改GSI 移植rom 必备常识 lib--**so文件基本解析 一起来了解system目录相应文件的用途吧。(rom版本不同里面的app也会不一样) 简单打开img格式后缀文件 给大家说下最简单的方法提取img里面的文件,对于后缀img格式的文件可…