Redis(十八) 分布式锁

news2024/11/18 11:22:13

在这里插入图片描述

文章目录

  • 前言
  • 什么是分布式锁
  • 分布式锁的基本实现
    • 引入过期时间
    • 引入校验 id
    • 引入 lua 脚本
    • 引入 watch dog(看门狗)
    • 引入 Redlock 算法

前言

在使用 redis 作为中间件的时候,如果使用单机部署的话,如果这个机器故障的话,那么整个的服务都会受到影响,所以为了避免单机问题,我们的 redis 通常都是部署在多个主机上的,也就是分布式系统。既然是分布式系统,也就不可避免的出现类似多线程上的线程安全问题,分布式系统也会因为多个节点访问同一个公共资源的顺序随机而出现问题,为了避免分布式出现的访问公共资源出现的问题,通过前面多线程使用的加锁的问题是无法解决的,因为多线程中的加锁解决的是同一个进程中的多个线程的问题,而我们这里的分布式则是在多个主机上,显然不属于同一个进程,那么如何解决分布式中出现的问题呢?那就是我们这篇文章要说的——分布式锁。

什么是分布式锁

在一个分布式系统中,不可避免的会出现多个节点访问同一个公共资源的问题,因为节点的访问顺序不同,所以就可能出现不可预期的问题,那么为了解决这种类似“线程不安全”的问题,就需要依赖于分布式锁来做互斥控制。

分布式锁本质上就是使用一个公共的服务器来记录加锁的状态,当有节点需要访问公共资源的时候,首先就需要判断这个资源是否被其他节点加锁了,如果没有,那么该节点就会访问这个公共资源并且对这个资源进行加锁操作,如果这个公共资源被其他节点加锁之后,那么该节点就会选择进行阻塞等待还是放弃访问这个资源。

这个公共的服务器可以是 redis,也可以是其他组件(比如 MySQL 或者 Zookeeper等),还可以是我们自己写的一个服务。

分布式锁的基本实现

分布式锁的实现思路是很简单的,就是通过一个键值对来表示锁的状态。给大家举个例子:

大家肯定都买过限量发售的东西吧,就是一个东西出售的数量是有限的,最多只卖 n 个,那么这个东西在发售的瞬间肯定会有很多的人抢着去买,也就是同一时间有大量的客户端去访问一个数据库:

在这里插入图片描述
不同的请求就会经过负载均衡器的计算给分配到不同的服务器中,不同的不服气访问同一个数据库,服务器处理请求的时候首先会先查询商品的数量,如果商品的数量大于等于1的话,就会生成订单信息,然后该商品的数量减去1,但是因为这个查询和修改的操作不具有原子性,所以这这两个操作中间就会穿插其他服务器的操作:

在这里插入图片描述
假设服务器1查询的商品的数量为1,1 >= 1,然后服务器2查询的商品的数量也是 1,1 >= 1,然后服务器1生成一份订单,服务器2也生成一份订单,也就是说订单的数量是多于出售的商品的数量的,这样就发生了超卖的问题。

为了解决这个问题,就引入了 redis 服务器作为分布式锁的管理器:

在这里插入图片描述

假设是服务器1先向数据库发起的访问数据请求,在访问数据库之前,会先去 redis 服务器中查看数据库资源是否被加锁了,因为是第一个访问数据库的,所以就没有其他节点对其进行加锁,那么服务器1就可以正常访问数据库资源,并且为了避免其他服务器同时也访问并且修改数据库中的数据,服务器1就会在 redis 分布式锁管理器中设置一个key-value,key 可以是商品的编号,然后 value 就是服务器1的编号,然后当服务器2要访问数据库资源的时候,也是会先访问 redis 分布式锁管理器,查看 001 这个 key 是否存在,此时 001 这个 key 是存在的,所以服务器2就会选择进入阻塞等待还是放弃访问这个资源,这样就保证了一个节点在访问和修改数据库的数据的时候不会被其他节点的操作影响。

当服务器 1 的访问操作结束的时候,就会进行“解锁”操作,在 redis 中就可以直接将这个 key 删除,删除这个 key 的时候,其他节点要访问的时候该 key 就不存在了,那么其他节点就可以正常访问了。

这个加锁的操作正好跟 redis 中的 setnx 的命令操作是类似的,当设置的 key 不存在的时候才设置成功,如果 key 存在那么就会报错。但是有一个问题:就是如果服务器1在 redis 中对资源进行加锁之后,突然这个服务器挂掉了,也就是该服务器没有对其进行解锁操作,那么其他服务器在访问这个数据的时候,就会一直读取不到,那么这个问题该如何解决呢?

引入过期时间

在加锁的时候,也就是设置键值对的时候,为这个锁(键值对)设置过期时间,当达到指定的时间之后,这个 key 就会自动删除,这样就算加锁的服务器没有正常解锁,也不会影响其他服务器的正常操作。

在 redis 中可以使用 set ex nx 的方式保证只有当 key 不存在的时候才设置 key-value 成功,并且指定过期时间。那么只有这一种方式吗?在 redis 中不是可以先使用 setnx 设置 key-value,然后再使用 expire 为存在的 key 设置过期时间吗?在 redis 中确实如此,但是该方式在分布式锁中是非常不建议使用的,分布式锁设置键值对应尽量保证一个命令就可以实现。为什么呢?同样是“线程不安全”问题,因为 setnx 和 expire 两个命令不是原子性的操作,并且 redis 事务本身不支持回滚操作,这样就可能出现 expire 设置过期时间失败的问题出现,所以应使用 set ex nx 来设置键值对。

设置过期时间,那么过期时间设置多少才合适呢?过期时间设置太短,一个节点的操作还没解锁,锁就被释放了,那么就无法达到分布式锁的作用,但是如果过期时间设置的太长的话,又会导致其他节点无法及时访问资源的问题出现。这个问题文章的后面再为大家介绍。

接下来我们来思考一下下面的问题:加锁就是在 redis 分布式管理锁系统中设置键值对,解锁就是删除键值对的操作,那么是否会出现一个问题就是,当一个节点加锁之后,这个锁被其他节点解锁了的情况了呢?这种情况是可能出现的,如果一个节点内部出现了故障,导致错误的解开了其他节点加的锁的的话,分布式锁的目的就无法达到了,那么如何解决这个问题呢?

引入校验 id

我们在加锁设置键值对的时候,设置的 value 的值不再是简单的 value,而是设置为加锁的节点的服务器的编号,例如“001”,这样当在进行解锁删除键值对的操作的时候,会根据 key 中 value 值表示的服务器的编号,然后跟进行解锁操作的服务器的编号进行比较,如果相等则可以解锁,不相等,则不能解锁:

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 操作也是非原子的操作,在这两个操作执行期间也是可能出现问题的:

在这里插入图片描述
假设一个服务器中的多个线程同时执行解锁操作,服务器1中的线程 A 先执行 GET 操作,然后服务器1中的线程B再执行 GET 操作,因为这个锁就是服务器1加的,所以 GET 比较之后的结果就是可以进行 DEL 操作,然后线程 A 执行 DEL 操作,在线程 B 执行 DEL 操作之前,服务器2上的线程 C 又执行了 set ex nx 加锁操作对相同的资源进行了加锁,也就是 key 相同,然后服务器1的线程 B 再执行 DEL 操作,因为之前的比较得到的结果是可以进行解锁操作,所以就导致其他服务器加的锁被其他服务器解锁了。

所以这个问题如何解决呢?这个问题虽然可以用事务来保证一个服务器上的命令执行之间不会插入其他服务器的命令,但是实际中更好的解决方案是引入 lua 脚本。

引入 lua 脚本

Lua是一种轻量级、可嵌入的脚本语言,设计目标是通过简单、紧凑、可扩展的方式支持通用过程式编程。它经常被用于游戏开发、Web应用程序、扩展和配置、机器人控制、系统监控和管理、以及许多其他需要灵活性和高效性的领域。

lua可以作为 redis 的内嵌脚本,事务可以完成的事情,使用 lua 脚本也基本可以实现,可以使用 lua 编写一些逻辑,然后将这个逻辑上传到 redis 服务器中,这样就可以使用客户端来控制 redis 执行上述脚本了。redis 执行 lua 脚本的过程就相当于执行一条命令的过程,也可以说 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 或者 jedis 等客户端加载,并发送给 redis 服务器,由 redis 服务器来执行这段逻辑。

当使用 lua 脚本解决上述问题之后,我们再来看看如何解决上面的 key 过期时间的设置问题。

引入 watch dog(看门狗)

相比于设置一个可能不合适的固定的过期时间,不如动态的调整时间更合适,所谓的看门狗本质上就是加锁的服务器上的一个单独的线程,通过这个线程来对锁的过期时间进行“续约”。

假设初始的过期时间设置的是 10s,同时设定看门狗线程每隔 3s 检测一次。那么当 3s 时间到的时候,看门狗就会判断当前任务是否完成:

  • 如果任务已经完成,则直接通过 lua 脚本的方式,释放锁
  • 如果任务未完成,则把过期时间重写设置为 10s,既续约

通过看门狗这样的机制,就不会因为 key 过期时间设置的不合理而导致提前解锁问题以及过期时间过长其他服务器无法及时拿到资源的问题。并且另一个方面,如果服务器挂了,看门狗线程自然业绩挂了,那么也就无法自动续约,这个 key 到了指定的时间就会自动删除了。

使用 redis 作为分布式锁的管理器,那么如果这个 redis 服务器挂了该怎么办呢?

我们可以使用多个 redis 节点,使得这些节点构成主从的关系,并且引入哨兵机制,当主节点挂了之后,就会由哨兵选出一个从节点作为新的主节点,但是因为主从复制的过程中需要点时间,如果服务器在加锁的过程中,先在 master 节点中设置键值对,当 master 设置完成之后,master 节点挂掉了,那么就算从节点成为了新的主节点,这个新加的锁也是无法记录的,所以就出现了 Redlock 算法。

引入 Redlock 算法

我们引入一组 redis 节点,其中每一组 redis 节点都包含一个主节点和若干从节点,每一组的节点中存储的数据都是全量数据,在加锁的时候,会按照一定的顺序,将加锁的信息写入多个 master 节点,并且在写锁的时候设置“超时时间”,如果某个 master 的写锁执行时间超过了指定的时间,就认为加锁失败,然后所有 master 都尝试加锁之后,枷锁成功的节点的个数超过所有 master 节点个数的一半的时候就认为加锁成功,解锁的时候也是同样的操作:

在这里插入图片描述
简而言之,Redlock 算法的核心就是,加锁操作不能只写给一个 redis 节点,而是要写多个,分布式系统的任何一个节点都是不可靠的,最终加锁成功的结论是“少数服从多数”。

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

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

相关文章

优先级队列(堆)学的好,头发掉的少(Java版)

本篇会加入个人的所谓鱼式疯言 ❤️❤️❤️鱼式疯言:❤️❤️❤️此疯言非彼疯言 而是理解过并总结出来通俗易懂的大白话, 小编会尽可能的在每个概念后插入鱼式疯言,帮助大家理解的. 🤭🤭🤭可能说的不是那么严谨.但小编初心是能让更多人…

取证与数据恢复:冷系统分析,实时系统分析与镜像分析之间的过渡办法

天津鸿萌科贸发展有限公司是 ElcomSoft 系列取证软件的授权代理商。 ElcomSoft 系列取证软件 ElcomSoft 系列取证软件支持从计算机和移动设备进行数据提取、解锁文档、解密压缩文件、破解加密容器、查看和分析证据。 计算机和手机取证的完整集合硬件加速解密最多支持10,000计…

arduino IDE 处于read only editor模式

当我们浏览一些arduino的例子的时候,有时候想修改这些例子。但是这些例子即使另存到自己的文件目录下,仍然不能修改,提示处于read only 模式。 网上有一些什么说法,说要设置什么之类的,当我们点开之后,好像…

13-4 GPT-5:博士级AI,人工智能的新时代

图片来源:AI Disruptive 人工智能世界正在迅速发展,新的创新和突破层出不穷。在本文中,我们将深入探讨最新的进展,从即将推出的 GPT-5 模型到 Apple 和 Meta 之间可能的合作。 GPT-5:博士级别的人工智能 虽然尚未正…

Windows Server 2008近源应急OS-1

前景需要:小王从某安全大厂被优化掉后,来到了某私立小学当起了计算机老师。某一天上课的时候,发现鼠标在自己动弹,又发现除了某台电脑,其他电脑连不上网络。感觉肯定有学生捣乱,于是开启了应急。 我们需要…

微信小程序 typescript 开发日历界面

1.界面代码 <view class"o-calendar"><view class"o-calendar-container" ><view class"o-calendar-titlebar"><view class"o-left_arrow" bind:tap"prevMonth">《</view>{{year}}年{{month…

py黑帽子学习笔记_burp

配置burp kali虚机默认装好了社区版burp和java&#xff0c;其他os需要手动装 burp是用java&#xff0c;还得下载一个jython包&#xff0c;供burp用 配apt国内源&#xff0c;然后apt install jython --download-only&#xff0c;会只下载包而不安装&#xff0c;下载的目录搜一…

基于最大相邻夹角的边缘点提取(matlab)

1、背景介绍 边缘点是指点云数据中代表物体或场景几何形状突变的那些点。在三维点云中&#xff0c;边缘点通常标志着不同表面或物体的分界&#xff0c;或者是物体表面上的不规则性&#xff0c;如裂缝、棱角、突起等。点云边缘检测的作用非常重要&#xff0c;最常见是进行特征点…

Transformation(转换)开发-switch/case组件

一、switch/case组件-条件判断 体育老师要做一件非常重要的事情&#xff1a;判断学生是男孩还是女孩、或者是蜘蛛&#xff0c;然后让他们各自到指定的队伍中 体育老师做的事情&#xff0c;我们同样也会在Kettle中会经常用来。在Kettle中&#xff0c;switch/case组件可以来做类似…

私有云统一多云管理平台主要服务内容

私有云统一多云管理平台&#xff0c;作为企业IT架构现代化的关键组成部分&#xff0c;旨在为企业提供高效、灵活、安全的云计算资源管理解决方案。这类平台通过整合和优化不同云环境(包括私有云、公有云、混合云)的管理&#xff0c;帮助企业打破云孤岛&#xff0c;实现资源的统…

守护创新之魂:源代码防泄漏的终极策略

在信息化快速发展的今天&#xff0c;企业的核心机密数据&#xff0c;尤其是源代码&#xff0c;成为了企业竞争力的关键所在。然而&#xff0c;源代码的泄露风险也随之增加&#xff0c;给企业的安全和发展带来了巨大威胁。在这样的背景下&#xff0c;SDC沙盒作为一种创新的源代码…

【JAVA入门】Day12 - 权限修饰符

【JAVA入门】Day12 - 权限修饰符 文章目录 【JAVA入门】Day12 - 权限修饰符一、private二、空着不写三、protected四、public五、权限修饰符的使用规则 权限修饰符是用来控制一个成员能够被访问的范围的。 权限修饰符可以修饰成员变量、方法、构造方法、内部类。 publ…

解析Kotlin中的内联函数,inline、noinline、crossinline【笔记摘要】

用编译时常量的概念&#xff0c;引出本文要讲内联函数inline&#xff1a; 1.编译时常量 Java的编译时常量 Compile-time Constant 它有四个要求&#xff1a;1.这个变量需要是 final 的  2.类型只能是字符串或者基本类型  3.这个变量需要在声明的时候就赋值  4.等号右边…

华为路由器静态路由配置(eNSP模拟实验)

实验目标 如图下所示&#xff0c;让PC1ping通PC2 具体操作 配置PC设备ip 先配置PC1的ip、掩码、网关。PC2也做这样的配置 配置路由器ip 配置G0/0/0的ip信息 #进入系统 <Huawei>system-view #进入GigabitEthernet0/0/0接口 [Huawei]int G0/0/0 #设置接口的ip和掩码 […

springboot 自定义的全局捕获异常失效

背景&#xff1a;springbootspringcloud 分布式微服务。 问题&#xff1a;公共模块在使用RestControllerAdvice全局捕获异常时&#xff0c;捕获不到子服务抛出的相应异常 首先看一下全局异常组件有么有被扫描到 如何查看&#xff0c;很简单只需要写一段类加载打印代码&#x…

Ansys Zemax|场曲跟畸变图的前世今生

实现 OpticStudio通过在X和Y方向&#xff08;弧矢和子午方向&#xff09;的傍轴光线追踪确定近轴图像平面的Z坐标&#xff0c;并测量该近轴焦平面与系统图像平面的Z坐标之间的距离。 切向数据是沿Z轴从图像平面到近轴图像平面在切向&#xff08;YZ&#xff09;平面测量的距离…

解决pip安装时的“SyntaxError: invalid syntax”错误

项目场景&#xff1a; 项目中有新的成员加入时&#xff0c;第一步就是安装开发环境&#xff0c;然而往往同样的机器、同样的配置&#xff0c;我们却总能遇到各种各样不同的问题。 今天分享一个简单的操作问题。 问题描述 项目用到pandas&#xff0c;安装pandas时遇到Syntax…

使用 App Store Connect API 生成和读取分析报告

文章目录 前言安装 API Swift SDK配置 API Swift SDK生成分析报告获取所有可用的报告获取报告的分段下载分段的数据总结 前言 Apple 最近推出了50多个新的分析报告&#xff0c;其中包含数百个新的数据点和指标&#xff0c;以帮助开发者了解他们的应用程序的表现情况。 这些报…

k8s部署rancher

一、添加helm chart仓库 二、创建命名空间 # kubectl create ns cattile-system 三、安装cert-manager https://github.com/cert-manager/cert-manager/releases/download/v1.14.5/cert-manager.crds.yaml# kubectl apply -f cert-manager.crds.yaml 四、安装rangcher # he…

day02-广播机制

广播机制 广播是numpy对不同形状的数组进行数值计算的方式&#xff0c;对数组的算术运算通常在相应的元素上进行 1.如果两个数组a和b形状相同&#xff0c;即满足a.shape b.shape&#xff0c;那么a*b的结果就是a与b数组对应位相乘。这要求维数相同且各维度的长度相同 a np.a…