当前位置: 首页 > news >正文

一步步优化Redis实现分布式锁

分布式锁概念

在多线程的程序里,为了避免同时操作一个共享变量产生数据问题,会加一个互斥锁,以确保共享变量的正确性,使用范围是同一个进程

那如果是多个进程,需要同时操作一个共享资源,如何互斥呢?

比如,现在的业务基本上都是微服务架构,一个应用会部署多个进程,这多个进程需要修改MySQL的同一行记录时,就需要引入分布式锁来解决这个问题了。

要实现分布式锁,需要借助一个外部系统,所有的进程都去这个系统上申请加锁,而这个外部系统必须要实现互斥的能力,换言之,如果有两个请求同时进来,也只会给一个进程返回成功,另一个返回失败或等待。

这个外部系统可以是MySQL、Redis或Zookeeper,一般使用Redis或ZK

如何实现

可以使用SETNX命令,表示SET IF NOT EXISTS,也就是当Key不存在的时候才会设置他的值。

比如,客户端1申请加锁,加锁成功;

127.0.0.1:6379> SETNX lock 1
(integer) 1

客户端2申请加锁,因为到达的比客户端1晚,加锁失败。

127.0.0.2:6379> SETNX lock 1
(integer) 0

此时,加锁成功的客户端,就可以去操作共享资源,例如,修改MySQL的某一行数据,或是调用一个API请求。

操作完成后,还要释放锁,这样后续的客户端才能继续操作共享资源。

释放锁可以通过DEL 命令删除这个key

以上是分布式锁最简单的实现,存在一个很大的问题,如果客户端1拿到锁以后,没有释放锁,就会造成死锁。没有释放锁的原因有以下几个:

  1. 程序处理业务逻辑异常,没有及时释放锁

  2. 整个进程挂了/宕机,没有办法去释放锁

如何避免死锁

那么如何解决上述的问题呢?比较容易想到的方案是:申请锁的时候,给锁设置一个租期

对于刚刚的Redis实现,就是给这个Key设置一个过期时间

比如

127.0.0.1:6379> SETNX lock 1 // 加锁
(integer) 1
127.0.0.1:6379> EXPIRE lock 10 // 10s后自动过期
(integer) 1

这样如果客户端异常的话,这个锁在10秒后会被自动释放,其他客户端依旧可以拿到锁

这样还是有问题,刚刚的操作里,加锁、设置过期时间这是2条命令,有可能执行完第一条,第二条来不及执行,比如:

  • 执行第二条语句时因为网络问题,执行失败

  • Redis异常宕机,第二条没时间执行

  • 客户端异常崩溃/退出,第二条命令没机会执行

如果这两条命令不能保证原子操作,就有潜在的风险导致过期时间设置失败,依旧会发生死锁。

Redis 2.6.12以后,只需要使用 SET lock 1 EX 10 NX就可以了

接下来分析下还有没有别的问题?

假设这样一种场景:

  1. 客户端1加锁成功,开始操作共享资源

  2. 客户端1操作共享资源的时间,超过了锁的过期时间,锁被自动释放

  3. 客户端2加锁成功,开始操作共享资源

  4. 客户端1操作共享资源完成,释放锁(注意这里的锁是客户端2刚刚加的锁

这里有两个很严重的问题:

  • 锁过期:客户端1操作共享资源耗时太久,导致锁被自动释放,之后客户端2加锁

  • 释放别人的锁:客户端1操作共享资源后,释放了客户端2的锁

第一个问题,可能是我们评估共享资源的时间不准确导致的。

简单的解决方案就是增大冗余时间,比如10秒过期,但是操作共享资源的时间最慢是15秒,那我就设置过期时间为20秒。但是这样没法根本解决问题,预估的时间只是大致计算,并不能预估到所有增加耗时的场景,比如程序内部发生异常、网络请求超时、异常耗时增加等。

第二个问题,一个客户端释放了其他客户端持有的锁

导致这个问题的原因是 每个客户端在释放锁的时候,没有检查这个锁是不是自己加上的

如何解决锁被别人释放的问题

客户端在加锁的时候,设置一个只有自己知道的唯一标识进去,可以是自己的主机名字、线程ID等,也可以是一个UUID

127.0.0.1:6379> SET lock $uuid EX 20 NX
OK

之后释放锁的时候,需要检查以下

if redis.get(“lock”) == $uuid:
    redis.del(“lock”)

这里又是一个原子操作,可以写成lua脚本,让Redis来执行

if redis.call("GET",KEYS[1]) == ARGV[1]
thenreturn redis.call("DEL",KEYS[1])
elsereturn 0
end

这样,整个加锁和解锁的过程就比较严谨了,大概流程如下:

  1. 加锁:SET lock $uuid EX 10 NX

  2. 操作共享资源

  3. 释放锁:lua脚本

不好评估锁过期时间怎么办

前面还有一个遗留问题就是,锁会有提前过期的风险

简单的方案就是尽量冗余过期时间,降低锁提前过期的概率。但是这个方案并不能完美解决问题。

比较主流的方案是,加锁的时候,先设置一个过期时间,同时开启一个守护线程,定时去检测这个锁的失效时间,如果锁快过期了,但是操作共享资源还未完成,自动对锁进行续期,重新设置过期时间。

JavaRedisson库已经把这部分封装好了,看门狗线程

分布式场景

之前分析的场景都是,锁在单个Redis实例中可能会产生的问题,并没有考虑Redis的部署架构细节

但实际上在使用Redis的时候,都是采用主从集群+哨兵的模式部署,当主库异常宕机的时候,哨兵可以进行故障自动切换,把从库提升为主库,继续提供服务,来保证可用性。

假设这样一个场景:

  • 客户端1在主库上加锁成功

  • 主库宕机,SET命令还未同步到从库上(这里是因为主从同步是异步的

  • 哨兵把从库提升为主库,这个锁在新的主库上就丢失了

当引入Redis副本后,分布式锁还是可能受影响,为此,Redis的作者提出一种解决方案,就是Redlock 红锁

相关文章:

一步步优化Redis实现分布式锁

分布式锁概念 在多线程的程序里,为了避免同时操作一个共享变量产生数据问题,会加一个互斥锁,以确保共享变量的正确性,使用范围是同一个进程。 那如果是多个进程,需要同时操作一个共享资源,如何互斥呢&…...

C++进阶——二叉搜索树

目录 一、基本概念 二、性能分析 三、模拟实现 四、使用场景 1.key搜索场景 2.key/value搜索场景 一、基本概念 二叉搜索树(Binary Search Tree),看名字就知道,是可以用来搜索数据的一种二叉树。 它可以是空树(一个数据都…...

Require:业界优秀的HTTP管理方案。

方案异步JDK额外依赖特点HttpURLConnection 【优点】Java内置,简单易用。对于简单的HTTP请求和响应处理非常合适。 【缺点】功能相对较少,不支持现代特性(如异步请求、连接池等)。API相对繁琐,处理复杂请求时代码冗长。…...

装饰模式(Decorator Pattern)在 Go 语言中的应用

文章目录 引言什么是装饰模式?在Go语言中的应用定义接口实现具体逻辑创建装饰器使用装饰器 装饰模式 vs 中间件装饰模式中间件区别 总结 引言 在软件开发中,设计模式是解决常见问题的模板。装饰模式(Decorator Pattern)是一种结构…...

Windows系统部署redis自启动服务

文章目录 引言I redis以本地服务运行(Windows service)使用MSI安装包配置文件,配置端口和密码II redis服务以终端命令启动缺点运行redis-server并指定端口和密码III 知识扩展确认redis-server可用性Installing the Service引言 服务器是Windows系统,所以使用Windows不是re…...

34岁IT男的职场十字路口:是失业预警,还是转型契机?

在信息技术这片充满机遇与挑战的广袤领域,34岁,一个看似正值壮年却暗藏危机的年龄,成为了许多IT男性不得不面对的职场考验。当“34岁现象”逐渐凸显,我们不禁要问:在这个快速变化的时代,34岁的IT男&#xf…...

复试经验分享《三、计算机学科专业基础综合》- 数据结构篇

复试经验分享 三、计算机学科专业基础综合 3.1 数据结构 3.1.1 概念 时间复杂度 时间复杂度是指执行算法所需要的计算工作量一般情况下,按照基本操作次数最多的输入来计算时间复杂度,并且多数情况下我们去最深层循环内的语句所描述的操作作为基本操作…...

数学建模算法与应用 第16章 优化与模拟方法

目录 16.1 线性规划 Matlab代码示例:线性规划求解 16.2 整数规划 Matlab代码示例:整数规划求解 16.3 非线性规划 Matlab代码示例:非线性规划求解 16.4 蒙特卡洛模拟 Matlab代码示例:蒙特卡洛模拟计算圆周率 习题 16 总结…...

windows下安装、配置neo4j并服务化启动

第一步:下载Neo4j压缩包 官网下载地址:https://neo4j.com/download-center/ (官网下载真的非常慢,而且会自己中断,建议从以下链接下载) 百度网盘下载地址:链接:https://pan.baid…...

【JVM】—深入理解G1回收器—回收过程详解

深入理解G1回收器—回收过程详解 ⭐⭐⭐⭐⭐⭐ Github主页👉https://github.com/A-BigTree 笔记链接👉https://github.com/A-BigTree/Code_Learning ⭐⭐⭐⭐⭐⭐ 如果可以,麻烦各位看官顺手点个star~😊 文章目录 深入理解G1回收…...

2、CSS笔记

文章目录 二、CSS基础CSS简介CSS语法规范CSS代码风格CSS选择器CSS基础选择器标签选择器类选择器--最常用id选择器通配符选择器 CSS复合选择器交集选择器--重要并集选择器--重要后代选择器--最常用子代选择器--重要兄弟选择器相邻兄弟选择器通用兄弟选择器 属性选择器伪类选择器…...

使用XML实现MyBatis的基础操作

目录 前言 1.准备工作 1.1⽂件配置 1.2添加 mapper 接⼝ 2.增删改查操作 2.1增(Insert) 2.2删(Delete) 2.3改(Update) 2.4查(Select) 前言 接下来我们会使用的数据表如下: 对应的实体类为:UserInfo 所有的准备工作都在如下文章。 MyBatis 操作…...

智汇云舟亮相WAFI世界农业科技创新大会,并参编数字农业产业图谱

10月10日,2024WAFI世界农业科技创新大会农食行业创新与投资峰会在北京金海湖国际会展中心举行。中国农业大学MBA教育中心主任、教授付文阁、平谷区委常委、统战部部长刘堃、华为公共事业军团数字政府首席专家刘丹、荷兰瓦赫宁根大学前校长Aalt Dijkhuizen、牧原食品…...

昇思MindSpore进阶教程--数据处理性能优化(中)

大家好,我是刘明,明志科技创始人,华为昇思MindSpore布道师。 技术上主攻前端开发、鸿蒙开发和AI算法研究。 努力为大家带来持续的技术分享,如果你也喜欢我的文章,就点个关注吧 shuffle性能优化 shuffle操作主要是对有…...

Vivado - Aurora 8B/10B IP

目录 1. 简介 2. 设计调试 2.1 Physical Layer 2.2 Link Layer 2.3 Receiver 2.4 IP 接口 2.5 调试过程 2.5.1 Block Design 2.5.2 释放 gt_reset 2.5.3 观察数据 3. 实用技巧 3.1 GT 坐标与布局 3.1.1 选择器件并进行RTL分析 3.1.2 进入平面设计 3.1.3 收发器布…...

图(Java语言实现)

一、图的概念 顶点(Vertex):图中的数据元素,我们称之为顶点,图至少有一个顶点(非空有穷集合)。 边(Edge):顶点之间的关系用边表示。 1.图(Graph…...

GPT 生成绘画_Java语言例子_超详细

基于spring ai :简化Java AI开发,提升效率与维护性 过去在使用Java编写AI应用时,主要困境在于缺乏统一的标准化封装,开发者需要针对不同的AI服务提供商查阅各自独立的文档并进行接口对接,这不仅增加了开发的工作量&am…...

华为OD机试 - 小朋友分组最少调整次数 - 贪心算法(Python/JS/C/C++ 2024 E卷 100分)

华为OD机试 2024E卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试真题(Python/JS/C/C)》。 刷的越多,抽中的概率越大,私信哪吒,备注华为OD,加入华为OD刷题交流群,…...

数字农业与遥感监测平台

随着全球人口的增长和气候变化的挑战,农业的可持续发展变得尤为重要。数字农业作为现代农业发展的重要方向,正逐渐成为提高农业生产效率、保障粮食安全的关键手段。遥感技术作为数字农业的重要组成部分,通过监测作物生长状况、土壤湿度、病虫…...

2023年12月中国电子学会青少年软件编程(Python)等级考试试卷(一级)答案 + 解析

一、单选题 1、下列程序运行的结果是?( ) print(hello) print(world) A.helloworld B.hello world C.hello world D.helloworld 正确答案:B 答案解析:本题考察的 Python 编程基础,print 在打印时…...

springboot 百货中心供应链管理系统小程序

一、前言 随着我国经济迅速发展,人们对手机的需求越来越大,各种手机软件也都在被广泛应用,但是对于手机进行数据信息管理,对于手机的各种软件也是备受用户的喜爱,百货中心供应链管理系统被用户普遍使用,为方…...

Vue2 第一节_Vue2上手_插值表达式{{}}_访问数据和修改数据_Vue开发者工具

文章目录 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染2. 插值表达式{{}}3. 访问数据和修改数据4. vue响应式5. Vue开发者工具--方便调试 1.Vue2上手-如何创建一个Vue实例,进行初始化渲染 准备容器引包创建Vue实例 new Vue()指定配置项 ->渲染数据 准备一个容器,例如: …...

OkHttp 中实现断点续传 demo

在 OkHttp 中实现断点续传主要通过以下步骤完成,核心是利用 HTTP 协议的 Range 请求头指定下载范围: 实现原理 Range 请求头:向服务器请求文件的特定字节范围(如 Range: bytes1024-) 本地文件记录:保存已…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

🚀 C extern 关键字深度解析:跨文件编程的终极指南 📅 更新时间:2025年6月5日 🏷️ 标签:C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言🔥一、extern 是什么?&…...

Spring AI与Spring Modulith核心技术解析

Spring AI核心架构解析 Spring AI(https://spring.io/projects/spring-ai)作为Spring生态中的AI集成框架,其核心设计理念是通过模块化架构降低AI应用的开发复杂度。与Python生态中的LangChain/LlamaIndex等工具类似,但特别为多语…...

python报错No module named ‘tensorflow.keras‘

是由于不同版本的tensorflow下的keras所在的路径不同,结合所安装的tensorflow的目录结构修改from语句即可。 原语句: from tensorflow.keras.layers import Conv1D, MaxPooling1D, LSTM, Dense 修改后: from tensorflow.python.keras.lay…...

JVM虚拟机:内存结构、垃圾回收、性能优化

1、JVM虚拟机的简介 Java 虚拟机(Java Virtual Machine 简称:JVM)是运行所有 Java 程序的抽象计算机,是 Java 语言的运行环境,实现了 Java 程序的跨平台特性。JVM 屏蔽了与具体操作系统平台相关的信息,使得 Java 程序只需生成在 JVM 上运行的目标代码(字节码),就可以…...

6个月Python学习计划 Day 16 - 面向对象编程(OOP)基础

第三周 Day 3 🎯 今日目标 理解类(class)和对象(object)的关系学会定义类的属性、方法和构造函数(init)掌握对象的创建与使用初识封装、继承和多态的基本概念(预告) &a…...

C++_哈希表

本篇文章是对C学习的哈希表部分的学习分享 相信一定会对你有所帮助~ 那咱们废话不多说,直接开始吧! 一、基础概念 1. 哈希核心思想: 哈希函数的作用:通过此函数建立一个Key与存储位置之间的映射关系。理想目标:实现…...

拟合问题处理

在机器学习中,核心任务通常围绕模型训练和性能提升展开,但你提到的 “优化训练数据解决过拟合” 和 “提升泛化性能解决欠拟合” 需要结合更准确的概念进行梳理。以下是对机器学习核心任务的系统复习和修正: 一、机器学习的核心任务框架 机…...