使用redis实现分布式锁

news2024/10/5 22:45:39

为什么需要分布式锁

在一个分布式系统中,也会涉及多个节点访问同一个公共资源的情况,此时就需要通过锁来做互斥控制,避免出现类似于“线程安全”的问题,而java的synchronized这样的锁只能在当前进程中生效,在分布式的这种多个进程多个主机的场景无能为力,此时就需要分布式锁。

分布式锁的基础实现

例如:买票场景,现在车站提供了若干车次,每个车次的票数都是固定的。现在又多个服务器节点,都可能需要处理这个买票逻辑,先查询指定车次的余票,如果余票>0,则设置余票值-=1.

image.png
客户端1先查询余票,发现剩余1张,在即将执行1->0过程之前;客户端2也执行查询余票,发现也是剩余1张,也会执行1->0过程。这就造成1张票卖了给两个人,即超卖。
我们可以在上述架构中引入redis,作为分布式锁的管理器。
image.png
所谓的分布式锁,也是一个/一组单独的服务器程序(如redis),给其他服务器提供“加锁”服务。
买票服务器,在进行买票操作的时候,需要先加锁。往redis上设置一个特殊的键值对key-value,完成上述买票操作,再把这个key-value删除掉。其他服务器也想去买票的时候,也去redis上尝试设置key-value,如果发现key-value已经存在,就认为“加锁失败”(是放弃/阻塞等待,就看具体实现)。这样就可以保证,第一个服务器在执行“查询->更新"的过程中,第二个服务器不会执行”查询“,也就解决了”超卖“问题。
:::success
redis中提供的setnx操作,正好适合上述场景。即key不存在就设置,存在则设置失败
:::

引入过期时间

某个服务器中加锁成功后(setnx成功),如果该服务器意外发生宕机,就会导致解锁操作(删除该key)不能执行,就可能引起其他服务器始终无法获取到锁的情况。

在java的多线程编程中,可以把解锁操作放到finally中,保证解锁操作一定会被执行到。但是这种做法只是针对进程内的锁有用(进程异常退出,锁也就随之销毁)。而分布式锁是无效的,服务器宕机以后会导致redis上设置的key无人删除,也就导致其他服务器无法获取到锁

:::info
引入过期时间,使用set ex nx的方式,在设置锁的同时把过期时间设置进去,一但时间到了,key就会自动被删除掉。
:::
注意!此处设置过期时间只能使用一个命令的方式设置。

如果分开设置,比如setnx之后,再来个expire。redis多个指令之间,无法保证原子性(redis的原子性是只能保证执行,不能保证成功)。此时就可能出现这两个命令,一个执行成功,一个执行失败情况

引入校验id

对于redis中写入的加锁键值对,其他节点也是可以删除的。

比如 服务器1写入一个001:1这样的键值对,服务器2是完全可以把001:1给删除掉。当然,服务器2一般不会这样”恶意删除“操作,不过不能保证因为一些bug导致服务器2把锁给误删除

为了解决上述问题,我们可以引入一个校验id。

  1. 给服务器编号,每个服务器都有一个自己的身份标识
  2. 进行加锁的时候,设置key-value。key是针对哪个资源加锁(比如车次),value就可以存储刚才服务器的编号,标识出当前这个锁是哪个服务器加上的。
  3. 解锁的时候,先查询一下这个锁对应的服务器编号,然后判定一下value是否和当前执行解锁的服务器编号一致,如果一致,才能真正执行del,如果不是,就失败。

伪代码如下:

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是两步操作,不是原子的。

引入lua

在服务器内部,可能是多线程的。例如服务器1中有两个线程都在执行上述解锁操作。
image.png
在服务器1中,看起来只是重复执行del操作,问题不大???但是当服务器2,执行加锁时,就可能出现问题了。
线程A执行完del操作后,线程B执行del操作之前,服务器2的线程C正好要执行加锁操作。此时线程A已经把锁删除了,线程C是能够加锁成功的。但是紧接着,线程B就会执行del操作,就会把服务器2的加锁操作给解锁了。虽然del操作中有引入校验id,但是线程B在get操作中已经通过id校验,可以执行del操作,虽然线程C这把锁的id不同,也能够解锁。
使用redis是事务,能够避免命令之间的插队。但是实践中往往是使用lua脚本。由于lua语言非常轻量,因此可以内嵌到redis中。我们可以使用lua编写一些逻辑,把这个脚本上传到redis服务器上,然后就可以让客服端来控制redis执行上述脚本。redis执行lua脚本的过程,是原子的。并且redis官方也明确说明,lua属于事务的替代方案。
使用lua脚本实现上述解锁功能:

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

image.png

引入看门狗(watch dog)

上述方案中仍然存在一个重要问题,在加锁的时候,需要给key设置过期时间。过期时间,设置多少合适呢?

  • 设置太短,就可能业务逻辑还没执行完,就释放锁
  • 设置太长,会导致”锁释放不及时“问题

因此更好的方式是”动态续约“,这就需要服务器这边有一个专门的线程,负责续约这件事。我们把这个负责的线程,叫做”看门狗“(watch dog).

举个具体的例子:
初始情况下设置过期时间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算法。本质上是使用冗余解决可用性问题
image.png
此处加锁,就是按照一定的顺序,针对redis集群的所有分片都进行加锁操作。如果某个节点挂了(加不上锁了)继续给下一个节点加锁即可。如果写入key成功的节点个数超过总数的一半,就视为加锁成功。同理,进行解锁的时候,也就会把上述节点都设置一遍解锁。

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

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

相关文章

【Go】Golang环境配置与语法基础

😏★,:.☆( ̄▽ ̄)/$:.★ 😏 这篇文章主要介绍Golang环境配置与示例。 学其所用,用其所学。——梁启超 欢迎来到我的博客,一起学习,共同进步。 喜欢的朋友可以关注一下,下次更新不迷路…

【Pycharm/Anaconda配置环境记录】

文章目录 1、Pytorch配置2、mmcv配置 1、Pytorch配置 查看虚拟环境创建虚拟环境 conda env list:查看虚拟环境 conda create --name env_name python3.7:创建虚拟环境 conda activate env_name:激活/进入该虚拟环境 查看自己的CUDA版本以及P…

element-plus-自定义Dialog样式

实现如下 <template><h3>dialog3 test 全局</h3><el-button type"primary" size"default" click"show">全局弹窗</el-button><div class""><!-- 弹窗 --><el-dialog v-model"visi…

使用 Matter-SDK 快速搭建 Matter 环境 (Linux)

Matter 作为一个统一的智能家居互联协议&#xff0c;凭借其高兼容性的特点&#xff0c;正逐渐打破各个智能家居之间的壁垒。乐鑫作为在 Matter 项目发布之初的早期成员&#xff0c;提供了一套开源、完整、易用的 Matter-SDK。 乐鑫的 Matter-SDK 是建立在开源 Matter-SDK 之上…

MySQL - order by排序查询 (查询操作 四)

功能介绍&#xff1a;order by&#xff1a;对查询结果进行排序&#xff0c;执行顺序在查询完结果执行&#xff08;比如where后&#xff09; 排序方式&#xff1a;ASC&#xff1a;升序&#xff08;默认&#xff0c;不用指定&#xff09; DESC&#xff1a;降序 语法&#x…

Android跨进程通信:Binder机制原理

目录 1. Binder到底是什么&#xff1f; 2. 知识储备 2.1 进程空间划分 2.2 进程隔离 & 跨进程通信&#xff08; IPC &#xff09; 2.3 内存映射 2.3.1 作用 2.3.2 实现过程 2.3.3 特点 2.3.4 应用场景 2.3.5 实例讲解 ① 文件读 / 写操作 ② 跨进程通信 3. Bi…

【Java 基础篇】Java 图书管理系统详解

介绍 图书管理系统是一种用于管理图书信息、借阅记录、用户信息等的软件系统。它可以帮助图书馆、书店或个人管理和组织图书资源&#xff0c;提供了方便的借阅和查询功能。在这篇博客中&#xff0c;我们将详细介绍如何使用Java编程语言创建一个简单的图书管理系统。 功能需求…

SpringMVC 学习(一)Servlet

1. Hello Servlet (1) 创建父工程 删除src文件夹 引入一些基本的依赖 <!--依赖--> <dependencies><dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.12</version><scope>test<…

大模型lora微调-chatglm2

通义千问大模型微调源码&#xff08;chatglm2 微调失败&#xff0c;训练通义千问成功&#xff09;&#xff1a;GitHub - hiyouga/LLaMA-Efficient-Tuning: Easy-to-use LLM fine-tuning framework (LLaMA-2, BLOOM, Falcon, Baichuan, Qwen, ChatGLM2)Easy-to-use LLM fine-tun…

STM32存储左右互搏 I2C总线读写FRAM MB85RC1M

STM32存储左右互搏 I2C总线读写FRAM MB85RC1M 在较低容量存储领域&#xff0c;除了EEPROM的使用&#xff0c;还有铁电存储器FRAM的使用&#xff0c;相对于EEPROM, 同样是非易失性存储单元&#xff0c;FRAM支持更高的访问速度&#xff0c; 其主要优点为没有EEPROM持续写操作跨页…

day06_循环

今日内容 零、 复习昨日 一、循环 二、流程控制关键词 零、 复习昨日 8个基本数据类型 变量的使用步骤 1)声明2)赋值3)使用 声明,数据类型 变量名 不一定非得是基本类型 int a; String s; Scanner scanner;赋值,只要符合类型(能默认转换)就能赋值 int a 1; double d 1; Scann…

uni-app问题记录

一、启动问题记录 1. 报错1 解决办法: 开启微信开发者工具服务端口 2. 报错2:调用getLocation获取位置信息时报错以下内容 {errMsg: “getLocation:fail the api need to be declared in the requiredPrivateInfos field in app.json/ext.json”} 解决办法: manifest.json文…

到广阔的边缘市场去,浪潮信息首次发布全栈边缘计算软硬件新品

出品 | CSDN 云计算 智慧时代&#xff0c;一切皆计算&#xff0c;早已不再是一句口号。据国际研究机构 IDC 数据显示&#xff0c;2023 年超过 50%的企业新增 IT 基础设施会部署在边缘&#xff0c;而 Gartner 研究显示&#xff0c;到 2025 年&#xff0c;超过 75%的数据生成和数…

【ROS入门】使用 ROS 服务(Service)机制实现同步请求与答复

文章结构 任务要求话题模型实现步骤自定义srv定义srv文件编辑配置文件编译 自定义srv调用vscode配置编写服务端实现编写客户端实现 执行启动roscore编译启动客户端和服务端编译启动roscore启动节点 任务要求 编写代码实现 ROS 中的服务请求与答复: 创建服务端&#xff0c;注册…

vue中使用富文本编辑器,@tinymce/tinymce-vue

富文本就是在后台管理系统中常见的录入带格式的内容&#xff0c;如&#xff1a;表格&#xff0c;字体加粗&#xff0c;斜体&#xff0c;文字颜色等等&#xff0c;就像一个word一样。类似于这样的效果&#xff1a; 我们使用通用在线编辑器tinymce。支持vue和react。 1. 安装 np…

opencv形态学-腐蚀

opencv形态学-腐蚀 腐蚀就是取每一个位置结构元领域内最小值作为该位置的输出灰度值&#xff1b; 结构元有很多&#xff0c;一般采用矩形&#xff0c;圆 解析 下图左测是原始图片的灰阶&#xff0c;右边是经过3X3矩形腐蚀后的结果&#xff0c;我们拿19,44,99进行分析&#…

Flink CDC MySQL同步MySQL错误记录

1、启动 Flink SQL [appuserwhtpjfscpt01 flink-1.17.1]$ bin/sql-client.sh2、新建源表 问题1&#xff1a;Encountered “(” 处理方法&#xff1a;去掉int(11)&#xff0c;改为int Flink SQL> CREATE TABLE t_user ( > uid int(11) NOT NULL AUTO_INCREMENT COMME…

关于DDR协议的一些操作的理解4

address 1.DDR中的地址&#xff0c;下表中的*4/ *8/ *16表示的是颗粒位宽。不同位宽的颗粒的行列地址的分步是不一样的。图中的page size表示的就是一行所存储的内容&#xff0c;以64MB*16格式为例&#xff0c;一行一共有10列&#xff0c;每一列存储16bit&#xff0c;也就是2B…

每日一题 146. LRU 缓存

难度&#xff1a;中等 由于周日没做&#xff0c;今天又是困难题&#xff0c;所以假装今天是周日 思路&#xff1a; 在字典结构的基础之上完成三个要求显然题目要求构建一个有序字典&#xff08;当然不使用OrderedDict&#xff09;&#xff0c;由于 key 是唯一的&#xff0c;…

TLS/SSL(六) 非对称密码应用 PKI 证书体系

一 PKI 证书体系 概念&#xff1a; PKI、CA、数字证书、证书链、数字签名之前讲解的公钥不同于https站点所获取的证书,公钥只是数字证书的一部分信息说明&#xff1a; 以下内容仅作为个人笔记 华为云证书管理服务 CCM ① 基础 PKI目前有一系列标准规范定义,主要包括: ② …