【分布式】什么是分布式锁?正文揭晓
分布式锁的概念
分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。
举个例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人,
分布式锁就是保证这个房子只有一个门并且一次只有一个人可以进,而且门只有一把钥匙。
然后许多人要去看书,可以,排队,第一个人拿着钥匙把门打开进屋看书并且把门锁上,
然后第二个人没有钥匙,那就等着,等第一个出来,然后你在拿着钥匙进去,然后就是以此类推。
锁存在的意义
原因其实很简单:因为我们想让同一时刻只有一个线程在执行某段代码。
因为如果同时出现多个线程去执行,可能会带来我们不想要的结果,可能是数据错误,也可能是服务宕机等等。
以淘宝双11为例,在0点这一刻,如果有几十万甚至上百万的人同时去查看某个商品的详情,这时候会触发商品的查询,如果我们不做控制,全部走到数据库去,那是有可能直接将数据库打垮的。
这个时候一个比较常用的做法就是进行加锁,只让1个线程去查询,其他线程待等待这个线程的查询结果后,直接拿结果。在这个例子中,锁用于控制访问数据库的流量,最终起到了保护系统的作用。
再举个例子,某平台做活动“秒杀茅台”,假如活动只秒杀1瓶,但是同时有10万人在同一时刻去抢,如果底层不做控制,有10000个人抢到了,额外的9999瓶平台就要自己想办法解决了。此时,我们可以在底层通过加锁或者隐式加锁的方式来解决这个问题。
此外,锁也经常用来解决并发下的数据安全方面的问题,这里就不一一举例了。
分布式锁存在的意义
分布式锁是锁的一种,通常用来跟 JVM 锁做区别。
JVM 锁就是我们常说的 synchronized、Lock。
JVM 锁只能作用于单个 JVM,可以简单理解为就是单台服务器(容器),而对于多台服务器之间,JVM 锁则没法解决,这时候就需要引入分布式锁。
实现分布式锁的方式
实现分布式锁的方式其实很多,只要能保证对于抢夺“锁”的系统来说,这个东西是唯一的,那么就能用于实现分布式锁。
举个简单的例子,有一个 MySQL 数据库 Order,Order 库里有个 Lock 表只有一条记录,该记录有个状态字段 lock_status,默认为0,表示空闲状态,可以修改为1,表示成功获取锁。
我们的订单系统部署在100台服务器上,这100台服务器可以在“同一时刻”对上述的这1条记录执行修改,修改内容都是从0修改为1,但是 MysQL 会保证最终只会有1个线程修改成功。因此,这条记录其实就可以用于做分布式锁。
常见实现分布式锁的方式有:数据库、Redis、Zookeeper。
这其中又以 Redis 最为常见。
Redis 实现分布式锁
加锁
加锁通常使用 set 命令来实现,伪代码如下:
set key value PX milliseconds NX
几个参数的意义如下:
- key、value:键值对
- PX milliseconds:设置键的过期时间为 milliseconds 毫秒。
- NX:只在键不存在时,才对键进行设置操作。SET key value NX 效果等同于 SETNX key value。
- PX、expireTime 参数则是用于解决没有解锁导致的死锁问题。因为如果没有过期时间,万一程序员写的代码有 bug 导致没有解锁操作,则就出现了死锁,因此该参数起到了一个“兜底”的作用。
- NX 参数用于保证在多个线程并发 set 下,只会有1个线程成功,起到了锁的“唯一”性。
解锁
解锁需要两步操作:
1)查询当前“锁”是否还是我们持有,因为存在过期时间,所以可能等你想解锁的时候,“锁”已经到期,然后被其他线程获取了,所以我们在解锁前需要先判断自己是否还持有“锁”
2)如果“锁”还是我们持有,则执行解锁操作,也就是删除该键值对,并返回成功;否则,直接返回失败。
由于当前 Redis 还没有原子命令直接支持这两步操作,所以当前通常是使用 Lua 脚本来执行解锁操作,Redis 会保证脚本里的内容执行是一个原子操作。
脚本代码如下,逻辑比较简单:
if redis.call("get",KEYS[1]) == ARGV[1]
thenreturn redis.call("del",KEYS[1])
elsereturn 0
end
两个参数的意义如下:
-
KEYS[1]:我们要解锁的 key
-
ARGV[1]:我们加锁时的 value,用于判断当“锁”是否还是我们持有,如果被其他线程持有了,value 就会发生变化。
Redis 分布式锁过期了,还没处理完怎么办?
为了防止死锁,我们会给分布式锁加一个过期时间,但是万一这个时间到了,我们业务逻辑还没处理完,怎么办?
首先,我们在设置过期时间时要结合业务场景去考虑,尽量设置一个比较合理的值,就是理论上正常处理的话,在这个过期时间内是一定能处理完毕的。
之后,我们再来考虑对这个问题进行兜底设计。
关于这个问题,目前常见的解决方法有两种:
1、守护线程“续命”:额外起一个线程,定期检查线程是否还持有锁,如果有则延长过期时间。Redisson 里面就实现了这个方案,使用“看门狗”定期检查(每1/3的锁时间检查1次),如果线程还持有锁,则刷新过期时间。
2、超时回滚:当我们解锁时发现锁已经被其他线程获取了,说明此时我们执行的操作已经是“不安全”的了,此时需要进行回滚,并返回失败。
同时,需要进行告警,人为介入验证数据的正确性,然后找出超时原因,是否需要对超时时间进行优化等等。
守护线程续命的方案有什么问题吗
Redisson 使用看门狗(守护线程)“续命”的方案在大多数场景下是挺不错的,也被广泛应用于生产环境,但是在极端情况下还是会存在问题。
问题例子如下:
1、线程1首先获取锁成功,将键值对写入 redis 的 master 节点
2、在 redis 将该键值对同步到 slave 节点之前,master 发生了故障
3、redis 触发故障转移,其中一个 slave 升级为新的 master
4、此时新的 master 并不包含线程1写入的键值对,因此线程2尝试获取锁也可以成功拿到锁
5、此时相当于有两个线程获取到了锁,可能会导致各种预期之外的情况发生,例如最常见的脏数据
解决方法:上述问题的根本原因主要是由于 redis 异步复制带来的数据不一致问题导致的,因此解决的方向就是保证数据的一致。
当前比较主流的解法和思路有两种:
1)Redis 作者提出的 RedLock;
2)Zookeeper 实现的分布式锁。
接下来介绍下这两种方案。
RedLock
首先,该方案也是基于文章开头的那个方案(set加锁、lua脚本解锁)进行改良的,所以 antirez 只描述了差异的地方,大致方案如下。
假设我们有 N 个 Redis 主节点,例如 N = 5,这些节点是完全独立的,我们不使用复制或任何其他隐式协调系统,为了取到锁,客户端应该执行以下操作:
1、获取当前时间,以毫秒为单位。
2、依次尝试从5个实例,使用相同的 key 和随机值(例如UUID)获取锁。当向Redis 请求获取锁时,客户端应该设置一个超时时间,这个超时时间应该小于锁的失效时间。例如你的锁自动失效时间为10秒,则超时时间应该在 5-50 毫秒之间。这样可以防止客户端在试图与一个宕机的 Redis 节点对话时长时间处于阻塞状态。如果一个实例不可用,客户端应该尽快尝试去另外一个Redis实例请求获取锁。
3、客户端通过当前时间减去步骤1记录的时间来计算获取锁使用的时间。当且仅当从大多数(N/2+1,这里是3个节点)的Redis节点都取到锁,并且获取锁使用的时间小于锁失效时间时,锁才算获取成功。
4、如果取到了锁,其有效时间等于有效时间减去获取锁所使用的时间(步骤3计算的结果)。
5、如果由于某些原因未能获得锁(无法在至少N/2+1个Redis实例获取锁、或获取锁的时间超过了有效时间),客户端应该在所有的Redis实例上进行解锁(即便某些Redis实例根本就没有加锁成功,防止某些节点获取到锁但是客户端没有得到响应而导致接下来的一段时间不能被重新获取锁)。
可以看出,该方案为了解决数据不一致的问题,直接舍弃了异步复制,只使用 master 节点,同时由于舍弃了 slave,为了保证可用性,引入了 N 个节点,官方建议是 5。
该方案看着挺美好的,但是实际上我所了解到的在实际生产上应用的不多,主要有两个原因:1)该方案的成本似乎有点高,需要使用5个实例;2)该方案一样存在问题。
该方案主要存以下问题:
1)严重依赖系统时钟。如果线程1从3个实例获取到了锁,但是这3个实例中的某个实例的系统时间走的稍微快一点,则它持有的锁会提前过期被释放,当他释放后,此时又有3个实例是空闲的,则线程2也可以获取到锁,则可能出现两个线程同时持有锁了。
2)如果线程1从3个实例获取到了锁,但是万一其中有1台重启了,则此时又有3个实例是空闲的,则线程2也可以获取到锁,此时又出现两个线程同时持有锁了。
针对以上问题其实后续也有人给出一些相应的解法,但是整体上来看还是不够完美,所以目前实际应用得不是那么多。
Zookeeper 实现分布式锁
Zookeeper 的分布式锁实现方案如下:
1、创建一个锁目录 /locks,该节点为持久节点
2、想要获取锁的线程都在锁目录下创建一个临时顺序节点
3、获取锁目录下所有子节点,对子节点按节点自增序号从小到大排序
4、判断本节点是不是第一个子节点,如果是,则成功获取锁,开始执行业务逻辑操作;如果不是,则监听自己的上一个节点的删除事件
5、持有锁的线程释放锁,只需删除当前节点即可。
6、当自己监听的节点被删除时,监听事件触发,则回到第3步重新进行判断,直到获取到锁。
由于 Zookeeper 保证了数据的强一致性,因此不会存在之前 Redis 方案中的问题,整体上来看还是比较不错的。
Zookeeper 方案的主要问题在于性能不如 Redis 那么好,当申请锁和释放锁的频率较高时,会对集群造成压力,此时集群的稳定性可用性能可能又会遭受挑战。
分布式锁的选型
当前主流的方案有两种:
1)Redis 的 set 加锁+lua 脚本解锁方案,至于是不是用守护线程续命可以结合自己的场景去决定,个人建议还是可以使用的。
2)Zookeeper 方案
通常情况下,对于数据的安全性要求没那么高的,可以采用 Redis 的方案,对数据安全性要求比较高的可以采用 Zookeeper 的方案。
最后
当你的才华还撑不起你的野心的时候,你就应该静下心来学习,愿你在我这里能有所收获。
相关文章:
【分布式】什么是分布式锁?正文揭晓
分布式锁的概念 分布式锁其实可以理解为:控制分布式系统有序的去对共享资源进行操作,通过互斥来保持一致性。 举个例子:假设共享的资源就是一个房子,里面有各种书,分布式系统就是要进屋看书的人, 分布式锁…...

超详细JDK1.8所有版本下载地址
JDK1.8即为JDK8,JDK8是目前是最成熟最稳定的版本,本文将详细介绍JDK1.8历史版本的下载方式。 在此附上JDK1.8安装与配置教程 超详细JDK1.8安装与配置 一、JDK官网 首先打开oracle官网,官网首页地址为 JDK官网首页地址 点击Products 点击…...

论文解析[11] CAT: Cross Attention in Vision Transformer
发表时间:2021 论文地址:https://arxiv.org/abs/2106.05786v1 文章目录摘要3 方法3.1 总体结构3.1.1 Inner-Patch Self-Attention Block3.1.2 Cross-Patch Self-Attention Block3.1.3 Cross Attention based Transformer结论摘要 使用图像patch来替换tr…...

嵌入式和Python(一):python环境搭建的详细步骤
目录 ● 安装python ① 更新软件列表 ② 安装编译python需要用到的环境 ③ 下载python源码 ④ 解压源码包 ⑤ 配置 ⑥ 编译 ⑦ 安装 ● 建立软连接 说明 ① 删除原来的软连接 ② 在/usr/bin/目录创建软连接python,定向/usr/local/bin/python3.9 ③ 检查…...

嵌入式学习笔记——STM32硬件基础知识
STM32开发硬件知识前言单片机参数主频位数STM32最小系统电源电路晶振电路复位电路BOOT选择电路调试接口电路其他电路本文重点本文参考博客链接前言 上一篇中我们重点是讲了一下怎么搭建开发环境以及怎么下载烧录的过程,这都是解决的电脑端的开发环境问题࿰…...

Mybatis插件开发及执行原理
mybatis源码下载 https://github.com/mybatis/mybatis-3,本文分析源码版本3.4.5 mybatis启动大致流程 在看这篇文章前,建议查看我另一篇文章,以了解框架启动的流程和框架中一些重要对象:https://blog.csdn.net/Aqu415/article/…...
vue父子组件通信,兄弟组件通信
目录 一、父子组件通信 1、子组件通过 props 获取父组件变量和父组件调用子组件中的方法(这两个都是父传子的思想) a:子组件通过 props 获取父组件变量 b:父组件调用子组件中的方法 2、父组件通过ref获取子组件变量和子组件调用父组件的方法(这两个都是子传父的…...

大数据技术之Hadoop集群配置
作者简介:大家好我是小唐同学(๑><๑),好久不见,为梦想而努力的小唐又回来了,让我们一起加油!!! 个人主页:小唐同学(๑><๑)的博客主页 目前…...

MicroBlaze系列教程(7):AXI_SPI的使用(M25P16)
文章目录 AXI_SPI简介MicroBlaze硬件配置常用函数使用示例波形实测参考资料工程下载本文是Xilinx MicroBlaze系列教程的第7篇文章。 AXI_SPI简介 Xilinx AXI-SPI IP共有两个:一个是标准的AXI_SPI,即4线制SPI,CS、SCLK、MOSI和MISO,另一个是AXI_Quad SPI,支持配置成标准SP…...

使用Python通过拉马努金公式快速求π
使用Python通过拉马努金公式快速求π 一、前言 π是一个数学常数,定义为:圆的周长与直径的比值。 π是一个无理数,也是一个超越数,它的小数部分无限不循环。 π可以用来精确计算圆周长、圆面积、球体积等几何形状的关键值。 有关…...
第六章 使用系统类提供国家语言支持 - 创建自定义语言环境
文章目录第六章 使用系统类提供国家语言支持 - 创建自定义语言环境创建自定义语言环境第六章 使用系统类提供国家语言支持 - 创建自定义语言环境 创建自定义语言环境 此示例将提供一个模板,用于使用自定义表创建自定义语言环境。自定义表将在 EBCDIC(美…...
「题解」解决二进制数中1的个数
🐶博主主页:ᰔᩚ. 一怀明月ꦿ ❤️🔥专栏系列:线性代数,C初学者入门训练 🔥座右铭:“不要等到什么都没有了,才下定决心去做” 🚀🚀🚀大家觉不错…...

泛型详解.
1 泛型的引入 问题:我们之前实现过的顺序表,只能保存 int 类型的元素,如果现在需要保存 指向 Person 类型对象的引用的顺序表,请问应该如何解决?如果又需要保存指向 Book 对象类型的引用呢? 之前写的顺序表…...
Vue 3.0 响应性 深入响应性原理 【Vue3 从零开始】
现在是时候深入了!Vue 最独特的特性之一,是其非侵入性的响应性系统。数据模型是被代理的 JavaScript 对象。而当你修改它们时,视图会进行更新。这让状态管理非常简单直观,不过理解其工作原理同样重要,这样你可以避开一…...

升级 vue3 常见问题总汇
Ⅰ、前言 虽然 vue3 是没有删除 vue2 的 选项式 API , 但是我们升级vue3 还是需要修改很多问题的下面来看看我们升级常见的一些问题 👇 文章目录Ⅰ、前言Ⅱ、解决兼容问题1、路由的创建方式2、路由的方法变化3、升级 vuex 到 4.x4、作用域 插槽语法修改…...

汽车 Automotive > T-BOX GNSS高精定位测试相关知识
参考:https://en.wikipedia.org/wiki/Global_Positioning_SystemGPS和GNSS的关系GPS(Global Positioning System),全球定位系统是美国军民两用的导航定位卫星系统,GPS包含双频信号,频点L1、L2和L5GNSS&…...
大数据面试核心101问【大厂超级喜欢这些题】
之前出过《史上最全的大数据开发八股文》这篇文章,同学们都觉得还不错,但是有些同学觉得那个背起来还是有些吃力,于是我再次回顾了自己之前面试所有大厂的一些面试题以及牛客上面的一些面经,然后总结了频率问的最高的101问&#x…...
代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机,122.买卖股票的最佳时机II
代码随想录算法训练营第四十八天 | leetcode 121. 买卖股票的最佳时机,122.买卖股票的最佳时机II121. 买卖股票的最佳时机122.买卖股票的最佳时机II121. 买卖股票的最佳时机 题目: 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支…...

RAD 11.3 delphi和C++改进后新增、废弃及优化的功能
RAD 11.3 delphi和C改进后新增和废弃的功能 目录 RAD 11.3 delphi和C改进后新增和废弃的功能 一、版本RAD 11.3 delphi和C改进后新增功能 1、官方视频位置: 2、官方文档的链接位置: 二、版本RAD 11.3 delphi和C改进后废弃的功能 2.1、编译器不再使…...

【C++】引用
🏖️作者:malloc不出对象 ⛺专栏:C的学习之路 👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈 目录前言一、引用1.1 引用概念1.2 引用特性…...
Python|GIF 解析与构建(5):手搓截屏和帧率控制
目录 Python|GIF 解析与构建(5):手搓截屏和帧率控制 一、引言 二、技术实现:手搓截屏模块 2.1 核心原理 2.2 代码解析:ScreenshotData类 2.2.1 截图函数:capture_screen 三、技术实现&…...

Python:操作 Excel 折叠
💖亲爱的技术爱好者们,热烈欢迎来到 Kant2048 的博客!我是 Thomas Kant,很开心能在CSDN上与你们相遇~💖 本博客的精华专栏: 【自动化测试】 【测试经验】 【人工智能】 【Python】 Python 操作 Excel 系列 读取单元格数据按行写入设置行高和列宽自动调整行高和列宽水平…...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案
问题描述:iview使用table 中type: "index",分页之后 ,索引还是从1开始,试过绑定后台返回数据的id, 这种方法可行,就是后台返回数据的每个页面id都不完全是按照从1开始的升序,因此百度了下,找到了…...

【第二十一章 SDIO接口(SDIO)】
第二十一章 SDIO接口 目录 第二十一章 SDIO接口(SDIO) 1 SDIO 主要功能 2 SDIO 总线拓扑 3 SDIO 功能描述 3.1 SDIO 适配器 3.2 SDIOAHB 接口 4 卡功能描述 4.1 卡识别模式 4.2 卡复位 4.3 操作电压范围确认 4.4 卡识别过程 4.5 写数据块 4.6 读数据块 4.7 数据流…...

Springcloud:Eureka 高可用集群搭建实战(服务注册与发现的底层原理与避坑指南)
引言:为什么 Eureka 依然是存量系统的核心? 尽管 Nacos 等新注册中心崛起,但金融、电力等保守行业仍有大量系统运行在 Eureka 上。理解其高可用设计与自我保护机制,是保障分布式系统稳定的必修课。本文将手把手带你搭建生产级 Eur…...

分布式增量爬虫实现方案
之前我们在讨论的是分布式爬虫如何实现增量爬取。增量爬虫的目标是只爬取新产生或发生变化的页面,避免重复抓取,以节省资源和时间。 在分布式环境下,增量爬虫的实现需要考虑多个爬虫节点之间的协调和去重。 另一种思路:将增量判…...
Linux离线(zip方式)安装docker
目录 基础信息操作系统信息docker信息 安装实例安装步骤示例 遇到的问题问题1:修改默认工作路径启动失败问题2 找不到对应组 基础信息 操作系统信息 OS版本:CentOS 7 64位 内核版本:3.10.0 相关命令: uname -rcat /etc/os-rele…...
GitHub 趋势日报 (2025年06月06日)
📊 由 TrendForge 系统生成 | 🌐 https://trendforge.devlive.org/ 🌐 本日报中的项目描述已自动翻译为中文 📈 今日获星趋势图 今日获星趋势图 590 cognee 551 onlook 399 project-based-learning 348 build-your-own-x 320 ne…...
WebRTC从入门到实践 - 零基础教程
WebRTC从入门到实践 - 零基础教程 目录 WebRTC简介 基础概念 工作原理 开发环境搭建 基础实践 三个实战案例 常见问题解答 1. WebRTC简介 1.1 什么是WebRTC? WebRTC(Web Real-Time Communication)是一个支持网页浏览器进行实时语音…...

HubSpot推出与ChatGPT的深度集成引发兴奋与担忧
上周三,HubSpot宣布已构建与ChatGPT的深度集成,这一消息在HubSpot用户和营销技术观察者中引发了极大的兴奋,但同时也存在一些关于数据安全的担忧。 许多网络声音声称,这对SaaS应用程序和人工智能而言是一场范式转变。 但向任何技…...