[Redis][典型运用][分布式锁]详细讲解

news2024/9/30 7:06:47

目录

  • 0.什么是分布式锁
  • 1.分布式锁的基础实现
  • 2.引入过期时间
  • 3.引入校验ID
  • 4.引入Lua
  • 5.引入Watch Dog(看门狗)
  • 6.引入Redlock算法
  • 7.其他功能


0.什么是分布式锁

  • 在⼀个分布式的系统中,也会涉及到多个节点访问同⼀个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于"线程安全"的问题
  • C++的std::mutex只能在当前进程中⽣效,在分布式的这 种多个进程多个主机的场景下就⽆能为⼒了,此时就需要用到分布式锁
  • 分布式锁本质使用一个公共的服务器,来记录加锁状态
    • 这个公共的服务器可以是Redis,也可以是其他组件(MySQL、ZooKeeper等),或是自己写的服务

1.分布式锁的基础实现

  • 思路⾮常简单,本质上就是通过⼀个键值对来标识锁的状态

  • 以经典的买车票为例

    • 下图是必然有线程安全问题的,需要用锁来控制
      请添加图片描述

    • 在上述架构中,引入一个Redis,作为分布式锁的管理器

      • 此时,如果买票服务器1尝试买票,就需要先访问Redis,在Redis上设置⼀个键值对
        • 例如key就是⻋次,value随便设置个值(⽐如1)
      • 如果这个操作设置成功,就视为当前没有节点对该001⻋次加锁,就可以进⾏数据库的读写操作,操作完成之后,再把Redis上刚才的这个键值对给删除掉
      • 如果在买票服务器1操作数据库的过程中,买票服务器2也想买票,也会尝试给Redis上写⼀个键值对,key同样是⻋次,但是此时设置的时候发现该⻋次的key已经存在了,则认为已经有其他服务器正在持有锁,此时服务器2就需要等待或者暂时放弃
        请添加图片描述
  • Redis中提供了setnx操作,正好适合上述场景

  • 但是上述方案并不完整


2.引入过期时间

  • 问题情景:当服务器1加锁之后,开始处理买票的过程中,如果服务器1意外宕机了,就会导致解锁操作(删除该key)不能执⾏,就可能引起其他服务器始终⽆法获取到锁的情况
  • 为了解决这个问题,可以在设置key的同时引⼊过期时间,即这个锁最多持有多久,就应该被释放
    • 使用set ex nx的方式,在设置锁的同时把过期时间设置进去
    • 例如:设置key的过期时间为1000,那么意味着即使出现极端情况,某个服务器挂了,没有正确释放锁,这个锁最多保持1000ms,也就会自动释放了
  • 此处的过期时间只用了一个命令,分开来用set nxexpire可以吗
    • Redis上的多个命令之间,是无法保证原子性的
    • 由于Redis的多个指令之间不存在关联,并且即使使⽤了事务也不能保证这两个操作都⼀定成功,因此就可能出现setnx成功,但是expire失败的情况
    • 此时仍然会出现⽆法正确释放锁的问题
    • 相比之下,使用一条命令,是更加稳的

3.引入校验ID

  • 问题情景对于Redis中写⼊的加锁键值对,其他的节点也是可以删除的
    • 例如
      • 服务器1写⼊⼀个"001":1这样的键值对,服务器2是完全可以把"001"给删除掉的
      • 当然,服务器2不会进⾏这样的"恶意删除"操作,不过不能保证因为⼀些bug导致服务器2把锁误删除
  • 为了解决上述的问题,可以引入一个检验ID
    • 例如:可以把设置的键值对的值,不再是简单的设为⼀个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);
    }
    
  • 问题又来了,解锁逻辑是两步操作getdel,这样做并非是原子的
  • 此时就会可能这样的情况
    • 服务器2的线程C进行加锁,但是服务器1的线程B就把线程C刚刚加的锁解锁了
    • 因为线程B确实也是在服务器1中成功进行了校验,有权利解锁
      请添加图片描述

4.引入Lua

  • 为了使解锁操作原子,可以使用Redis的Lua脚本功能
  • Lua脚本:
    • 类似于JS,是一个动态弱类型的语言
    • Lua语法简单精炼,执行速度快,解释器也比较轻量(200KB左右)
    • Lua经常作为其他程序内部嵌入的脚本语言,Redis则也支持
  • 使用Lua脚本完成上述解锁功能
    if redis.call('get',KEYS[1]) == ARGV[1] then
    	return redis.call('del',KEYS[1])
    else
    	return 0
    end;
    
  • 上述代码可以编写成一个.lua后缀的文件,由redis-cli或者redis-plus-plus等客户端加载,并发送给Redis服务器,由Redis服务器来执行这段逻辑
  • 一个Lua脚本会被Redis服务器以原子的方式来执行
  • 使用事务就能解决上述问题,为何要用Lua脚本呢?
    • 首先明确,Redis事务虽然弱,但是避免插队还是能做到的
      • 所以说,Redis事务确实能解决上述问题
    • 但是实践中使用的往往是更好的方案
      • 有上等马为啥用下等马嘞:)
    • Redis官方文档:Lua属于是事物的替代方案

5.引入Watch Dog(看门狗)

  • 上述方案仍然存在一个重要问题,如果设置了key过期时间之后,仍然存在一定的可能性,当任务还没执行完,key就先过期了,这就导致锁提前释放
  • 如果把这个过期时间设置的足够长,比如30s,是否能解决这个问题?
    • 很明显,设置多少时间合适,是无止境的,即使设置再长,也不能完全保证就没有提前失效的情况
    • 而且如果设置的太长了,万一对应的服务器挂了,此时其他的服务器也不能及时获取到所
    • 因此相比于设置一个固定的定长时间,不如动态的调整时间更合适
  • Watch Dog本质上是加锁服务器上的一个单独的线程,通过这个线程来对锁过期时间进行“续约”
    • 注意:这个线程是业务服务器上的,不是Redis服务器的
  • 举例助解
    • 初始情况下设置过期时间为10s,同时设定看门狗线程每隔3s检测一次
    • 那么当3s时间到的时候,看门狗就会判定当前任务是否完成
      • 如果任务已经完成,则直接通过Lua脚本的方式,释放锁(删除key)
      • 如果任务未完成,则把过期时间重写设置为10s(即”续约”)
  • 此时就不担心锁提前失效的问题了,另一方面,如果该服务器挂了,看门狗线程也就随之挂了,此时无人续约,这个key自然就可以迅速过期,让其他服务器能够获取到锁了

6.引入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节点,而要写多个,需要冗余
    • 分布式系统中,任何一个节点都是不可靠的
    • 由于一个分布式系统不至于大部分节点都同时出现故障,因此这样的可靠性要比单个节点来说靠谱不少
    • 最终的加锁成功结论是:少数服从多数

7.其他功能

  • 上述描述中解释了基于Redis的分布式锁的基本实现原理
  • 上述锁只是一个简单的互斥锁,但是实际上在一些特定场景中,还有一些其他特殊的锁,例如:
    • 可重入锁
    • 公平锁
    • 读写锁
    • ……
  • 实际开发中,也不会真的自己实现一个分布式锁,已经有很多现成的库帮我们封装好了,直接拿来s使用即可
    • 例如:C++中的redis-plus-plus

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

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

相关文章

一拖二快充线:单接与双接的多场景应用

在当代社会,随着智能手机等电子设备的普及,充电问题成为了人们关注的焦点。一拖二快充线作为一种创新的充电解决方案,因其便捷性与高效性而受到广泛关注。本文将深入探讨一拖二快充线的定义、原理以及在单接与双接手机场景下的应用&#xff0…

数字图像处理:空间域滤波

1.数字图像处理:空间域滤波 1.1 滤波器核(相关核)与卷积 图像上的邻域计算 线性空间滤波的原理 滤波器核(相关核)是如何得到的? 空间域的卷积 卷积:滤波器核与window中的对应值相乘后所有…

touch命令:创建文件,更新时间戳

一、命令简介 ​touch​ 命令在 Linux 和其他类 Unix 系统中用于创建空白文件或者更新已存在文件的时间戳。如果指定的文件不存在,touch​ 命令会创建一个空白文件;如果文件已经存在,touch​ 命令会更新文件的访问时间和修改时间&#xff0c…

誉天Linux云计算课程学什么?为什么保障就业?

一个IT工程师相当于干了哪些职业? 其中置顶回答生动而形象地描绘道: 一个IT工程师宛如一个超级多面手,相当于——加班狂程序员测试工程师实施工程师网络工程师电工装卸工搬运工超人。 此中酸甜苦辣咸,相信很多小伙伴们都深有体会。除了典…

用开源软件制作出精美的短视频#视频编辑

从前,有一个叫做创意森林的地方,住着各种各样的编辑精灵。一天,视频编辑精灵们发现了一本神秘的论文,里面写满了如何利用前沿的AI技术来提升他们的工作效率。于是,精灵们开始学习使用LLM和LLaVA,像魔法一样…

《企业实战分享 · 开发技术栈选型》

📢 大家好,我是 【战神刘玉栋】,有10多年的研发经验,致力于前后端技术栈的知识沉淀和传播。 💗 🌻 CSDN入驻不久,希望大家多多支持,后续会继续提升文章质量,绝不滥竽充数…

02Cesium中常用的鼠标事件

文章目录 02Cesium中常用的鼠标事件1、左键单击事件2、左键双击事件3、左键按下事件4、左键弹起事件5、中键按下事件6、中键弹起事件7、鼠标移动事件8、右键单击事件9、右键按下事件10、右键弹起事件11、鼠标滚轮事件具体在代码中的应用如下所示 02Cesium中常用的鼠标事件 Ces…

windows下安装rabbitMQ并开通管理界面和允许远程访问

如题,在windows下安装一个rabbitMQ server;然后用浏览器访问其管理界面;由于rabbitMQ的默认账号guest默认只能本机访问,因此需要设置允许其他机器远程访问。这跟mysql的思路很像,默认只能本地访问,要远程访…

《深度学习》OpenCV 图像拼接 拼接原理、参数解析、案例实现

目录 一、图像拼接 1、直接看案例 图1与图2展示: 合并完结果: 2、什么是图像拼接 3、图像拼接步骤 1)加载图像 2)特征点检测与描述 3)特征点匹配 4)图像配准 5)图像变换和拼接 6&am…

鸿蒙harmonyos next flutter通信之BasicMessageChannel获取app版本号

本文将通过BasicMessageChannel获取app版本号,以此来演练BasicMessageChannel用法。 建立channel flutter代码: //建立通道 BasicMessageChannel basicMessageChannel BasicMessageChannel("com.xmg.basicMessageChannel",StringCodec());…

系统工程 > 霍尔三维结构

简介 霍尔三维结构模型是由美国系统工程专家霍尔(A.D.Hall)在1969年提出的一种系统工程方法论,它集中体现了系统工程方法的系统化、综合化、最优化、程序化和标准化等特点 。该模型将系统工程整个活动过程分为前后紧密衔接的七个阶段和七个步…

MySQL的驱动安装

1、下载并安装MySQL 下载地址: 建议在下列框中选择LTS长期支持版本,下载对应的MSI安装文件。 安装完成后,将MySQL的环境bin路径添加到环境变量中。 可以运行MySQL Configurator进行配置,主要设置密码,并初始化。其余…

机器学习课程学习周报十四

机器学习课程学习周报十四 文章目录 机器学习课程学习周报十四摘要Abstract一、机器学习部分1. EM算法与高斯混合模型2. 概率论复习(三) 总结 摘要 本周的学习重点是EM算法与高斯混合模型的应用。单高斯模型无法有效拟合多峰数据分布,因此引…

论文精读:拓扑超导体PdBi2Te4和PdBi2Te5计算

npj Computational Materials (2023) 9:188 ; https://doi.org/10.1038/s41524-023-01144-y 摘要节选 超导拓扑金属(SCTMs)近年来成为一种很有前途的量子计算拓扑超导(TSC)和马约拉纳零模式平台。 本文提出了一种通过将超导单元嵌入到拓扑绝缘体中来设计sctm的策略。还编制了…

二叉树的中序遍历(java)

概述 关于二叉树,我们都不陌生,许多基于递归的问题发起点都是一个二叉树的root节点。对于各种二叉树的问题,我们也是通过dfs进行求解。例如求二叉树的深度、最近公共祖先等 算法分析 关于二叉树的中序遍历,我们都知道应该先访…

无人机之集群路径规划篇

无人机的集群路径规划是一个复杂而重要的任务,它要求为一群无人机设计出既安全又高效的飞行路径,同时考虑到多种约束条件和目标。 一、路径规划的重要性 无人机集群路径规划对于确保无人机能够安全、高效地完成任务至关重要。通过合理的路径规划&#x…

Word办公自动化的一些方法

1.Word部分内容介绍 word本身是带有格式的一种文档,有人说它本质是XML,所以一定要充分利用标记了【样式】的特性来迅速调整【格式】,从而专心编辑文档内容本身。 样式(集) 编号(多级关联样式编号&#xff…

企业如何提升知识产权管理效率?

随着企业规模的扩大和创新活动的增加,知识产权管理日益复杂。有效的知识产权管理不仅能够保护企业的创新成果,还能为企业带来巨大的商业价值。然而,许多企业在知识产权管理方面面临着效率低下的问题,管理效率的提升成为企业亟需解…

XSS | 存储型 XSS 攻击

关注这个漏洞的其他相关笔记:XSS 漏洞 - 学习手册-CSDN博客 0x01:存储型 XSS —— 理论篇 存储型 XSS 又称持久型 XSS,攻击脚本将被永久的存放在目标服务器的数据库或文件中,具有很高的隐蔽性。 常见的攻击方式: 这种…

升级 Windows 后如何恢复丢失的文件

升级到 Windows 11 后可以恢复丢失的文件!阅读帖子直到最后,了解如何做到这一点。 为了获得安全更新并使用最新的操作系统,人们会升级到最新版本的 Windows。然而,在这样做的过程中,许多人丢失了他们的重要文件&#…