redis实现相关分布式锁

为什么需要分布式锁
我们知道,当多个线程并发操作某个对象时,可以通过synchronized来保证同一时刻只能有一个线程获取到对象锁进而处理synchronized关键字修饰的代码块或方法。既然已经有了synchronized锁,为什么这里又要引入分布式锁呢?

因为现在的系统基本都是分布式部署的,一个应用会被部署到多台服务器上,synchronized只能控制当前服务器自身的线程安全,并不能跨服务器控制并发安全。比如下图,同一时刻有4个线程新增同一件商品,其中两个线程由服务器A处理,另外两个线程由服务器B处理,那么最后的结果就是两台服务器各执行了一次新增动作。这显然不符合预期。
在这里插入图片描述

而本篇文章要介绍的分布式锁就是为了解决这种问题的。

什么是分布式锁
分布式锁,就是控制分布式系统中不同进程共同访问同一共享资源的一种锁的实现。

所谓当局者迷,旁观者清,先举个生活中的例子,就拿高铁举例,每辆高铁都有自己的运行路线,但这些路线可能会与其他高铁的路线重叠,如果只让高铁内部的司机操控路线,那就可能出现撞车事故,因为司机不知道其他高铁的运行路线是什么。所以,中控室就发挥作用了,中控室会监控每辆高铁,高铁在什么时间走什么样的路线全部由中控室指挥。

分布式锁就是基于这种思想实现的,它需要在我们分布式应用的外面使用一个第三方组件(可以是数据库、Redis、Zookeeper等)进行全局锁的监控,由这个组件决定什么时候加锁,什么时候释放锁。
在这里插入图片描述

Redis如何实现分布式锁
在聊Redis如何实现分布式锁之前,我们要先聊一下redis的一个命令:setnx key value。我们知道,Redis设置一个key最常用的命令是:set key value,该命令不管key是否存在,都会将key的值设置成value,并返回成功:
在这里插入图片描述

setnx key value 也是设置key的值为value,不过,它会先判断key是否已经存在,如果key不存在,那么就设置key的值为value,并返回1;如果key已经存在,则不更新key的值,直接返回0:
**加粗样式
**

● 最简单的版本:setnx key value

基于setnx命令的特性,我们就可以实现一个最简单的分布式锁了。我们通过向Redis发送 setnx 命令,然后判断Redis返回的结果是否为1,结果是1就表示setnx成功了,那本次就获得锁了,可以继续执行业务逻辑;如果结果是0,则表示setnx失败了,那本次就没有获取到锁,可以通过循环的方式一直尝试获取锁,直至其他客户端释放了锁(delete掉key)后,就可以正常执行setnx命令获取到锁。流程如下:
在这里插入图片描述

这种方式虽然实现了分布式锁的功能,但有一个很明显的问题:没有给key设置过期时间,万一程序在发送delete命令释放锁之前宕机了,那么这个key就会永久的存储在Redis中了,其他客户端也永远获取不到这把锁了。

● 升级版本:设置key的过期时间

针对上面的问题,我们可以基于setnx key value的基础上,同时给key设置一个过期时间。Redis已经提供了这样的命令:set key value ex seconds nx。其中,ex seconds 表示给key设置过期时间,单位为秒,nx 表示该set命令具备setnx的特性。效果如下:
在这里插入图片描述

我们设置name的过期时间为60秒,60秒内执行该set命令时,会直接返回nil。60秒后,我们再执行set命令,可以执行成功,效果如下:
在这里插入图片描述

基于这个特性,升级后的分布式锁流程如下:
在这里插入图片描述

这种方式虽然解决了一些问题,但却引来了另外一个问题:存在锁误删的情况,也就是把别人加的锁释放了。例如,client1获得锁之后开始执行业务处理,但业务处理耗时较长,超过了锁的过期时间,导致业务处理还没结束时,锁却过期自动删除了(相当于属于client1的锁被释放了),此时,client2就会获取到这把锁,然后执行自己的业务处理,也就在此时,client1的业务处理结束了,然后向Redis发送了delete key的命令来释放锁,Redis接收到命令后,就直接将key删掉了,但此时这个key是属于client2的,所以,相当于client1把client2的锁给释放掉了:
在这里插入图片描述

● 二次升级版本:value使用唯一值,删除锁时判断value是否当前线程的

要解决上面的问题,最省事的做法就是把锁的过期时间设置长一点,要远大于业务处理时间,但这样就会严重影响系统的性能,假如一台服务器在释放锁之前宕机了,而锁的超时时间设置了一个小时,那么在这一个小时内,其他线程访问这个服务时就一直阻塞在那里。所以,一般不推荐使用这种方式。

另一种解决方法就是在set key value ex seconds nx时,把value设置成一个唯一值,每个线程的value都不一样,在删除key之前,先通过get key命令得到value,然后判断value是否是自己线程生成的,如果是,则删除掉key释放锁,如果不是,则不删除key。正常流程如下:
在这里插入图片描述

当业务处理还没结束的时候,key自动过期了,也可以正常释放自己的锁,不影响其他线程:

在这里插入图片描述

二次升级后的方案看起来似乎已经没什么问题了,但其实不然。仔细分析流程后我们发现,判断锁是否属于当前线程和释放锁两个步骤并不是原子操作。正常来说,如果线程1通过get操作从Redis中得到的value是123,那么就会执行删除锁的操作,但假如在执行删除锁的动作之前,系统卡顿了几秒钟,恰好在这几秒钟内,key自动过期了,线程2就顺利获取到锁开始执行自己的逻辑了,此时,线程1卡顿恢复了,开始继续执行删除锁的动作,那么此时删除的还是线程2的锁。
在这里插入图片描述

● 终极版本:Lua脚本

针对上述Redis原始命令无法满足部分业务原子性操作的问题,Redis提供了Lua脚本的支持。Lua脚本是一种轻量小巧的脚本语言,它支持原子性操作,Redis会将整个Lua脚本作为一个整体执行,中间不会被其他请求插入,因此Redis执行Lua脚本是一个原子操作。

在上面的流程中,我们把get key value、判断value是否属于当前线程、删除锁这三步写到Lua脚本中,使它们变成一个整体交个Redis执行,改造后流程如下:
在这里插入图片描述

这样改造之后,就解决了释放锁时取值、判断值、删除锁等多个步骤无法保证原子操作的问题了。关于Lua脚本的语法可以自行学习,并不复杂,很简单,这里就不做过多讲述。

既然Lua脚本可以在释放锁时使用,那肯定也能在加锁时使用,而且一般情况下,推荐使用Lua脚本,因为在使用上面set key value ex seconds nx命令加锁时,并不能做到重入锁的效果,也就是当一个线程获取到锁后,在没有释放这把锁之前,当前线程自己也无法再获得这把锁,这显然会影响系统的性能。使用Lua脚本就可以解决这个问题,我们可以在Lua脚本中先判断锁(key)是否存在,如果存在则再判断持有这把锁的线程是否是当前线程,如果不是则加锁失败,否则当前线程再次持有这把锁,并把锁的重入次数+1。在释放锁时,也是先判断持有锁的线程是否是当前线程,如果是则将锁的重入次数-1,直至重入次数减至0,即可删除该锁(key)。
在这里插入图片描述

实际项目开发中,其实基本不用自己写上面这些分布式锁的实现逻辑,而是使用一些很成熟的第三方工具,当下比较流行的就是Redisson,它既提供了Redis的基本命令的封装,也提供了Redis分布式锁的封装,使用非常简单,只需直接调用相应方法即可。但工具虽然好用,底层原理还是要理解的,这就是本篇文章的目的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

react useState useEffect useMemo实际业务场景中的使用

下面的代码实现了上面图片的功能 import React, { useMemo } from "react"; import "./HomeHead.less"; import Img from "../assets/images/timg.jpg";const HomeHead function HomeHead(props) {{ /*父组件传过来的值 */}let { today } pro…

在线培训系统的保障措施带来安全、可靠的学习环境

在今天的数字时代,越来越多的人选择在线培训系统作为学习的方式。然而,随着在线教育市场的不断增长,安全和可靠性成为消费者普遍关心的问题。因此,在线培训系统需要采取一系列保护措施以确保学生的数据和隐私得到保护,…

Flutter 状态管理框架 Provider 和 Get 分析

状态管理一直是 Flutter 开发中一个火热的话题。谈到状态管理框架,社区也有诸如有以Get、Provider为代表的多种方案,它们有各自的优缺点。面对这么多的选择,你可能会想:「我需要使用状态管理么?哪种框架更适合我&#…

集群基础1——集群概念、LVS负载均衡

文章目录 一、基本了解二、LVS负载均衡2.1 基本了解2.2 工作模式2.2.1 NAT模式2.2.2 DR模式2.2.3 LVS-TUN模式2.2.4 LVS-FULLNAT模式 三、调度器算法四、ipvsadm命令 一、基本了解 什么是集群? 多台服务器做同一件事情。 集群扩展方式: scale up&#xf…

每日科技分享-POE新增文件和链接发送功能

POE推出新功能 注意POE需要魔法上午才能进去。 实测 实测可以发送论文给chatgpt,然后和AI进行共享的对话。 POE网站链接: 也可以发送链接,实测了一下,似乎有时候并不准确,我发送了关于分层强化的文章,但是…

05 Docker 安装常用软件 (mongoDB)

目录 1. mongoDB简介 1.1 mongodb的优势 2. mongodb的安装 2.1 创建数据文件夹 2.2 备份日志 2.3 配置文件夹 2.4 创建两个文件 ---> 2.4.1 配置如下: 2.5 拉取mongodb 2.6 运行容器 2.7 进入mongodb容器 ---> 2.7.0 高版本(6.0)以上是这样的 , 旧版的没研究 …

SpringBoot 集成 Mybatis

SpringBoot 集成 Mybatis 详细教程 (只有操作,没有理论,仅供参考学习) 一、操作部分 1. 准备数据库 1.1 数据库版本: C:\WINDOWS\system32>mysql -V mysql Ver 8.0.25 for Win64 on x86_64 (MySQL Community …

PyTorch深度学习实战(5)——计算机视觉

PyTorch深度学习实战(5)——计算机视觉 0. 前言1. 图像表示2. 将图像转换为结构化数组2.1 灰度图像表示2.2 彩色图像表示 3 利用神经网络进行图像分析的优势小结系列链接 0. 前言 计算机视觉是指通过计算机系统对图像和视频进行处理和分析,利…

笔记本电脑清灰换硅脂

文章目录 一、完整过程0.准备工具1.拆笔记本后盖2.洗手擦干断电3.清理部件浮尘4.拆风扇5.拆散热模具6.换硅脂7.装回去 二、图片 一、完整过程 0.准备工具 拆机螺丝刀、硅脂、撬片/撬棒、毛刷、气吹、卫生纸。 正常电脑是十字螺丝,推荐刀头使用 PH00 或 PH0。 1.拆…

基于单片机的智能太阳能手机充电器的设计与实现

功能介绍 以STM32/51单片机作为主控系统;LCD1602液晶显示当前电压值;太阳能电池板采集当前光照转换为电能,然后TP4056锂电池充放电模块给锂电池进行充电,充完后自动断电,防过充;通过CE8301模块对锂电池电压…

1-4 架构师所需要具备的技术栈与能力

架构师所需要具备的技术栈与能力 全局图解 全局图解

CSS整段文字缩进(一段多行文字中首列位置相对应)

<style>p {text-align: justify;padding-left: 2em;} </style>

学习系统编程No.28【多线程概念实战】

引言&#xff1a; 北京时间&#xff1a;2023/6/29/15:33&#xff0c;刚刚更新完博客&#xff0c;目前没什么状态&#xff0c;不好趁热打铁&#xff0c;需要去睡一会会&#xff0c;昨天睡的有点迟&#xff0c;然后忘记把7点到8点30之间的4个闹钟关掉了&#xff0c;恶心了我自己…

基于单片机智能饮水机加热系统的设计与实现

功能介绍 以51单片机作为主控系统&#xff1b;LCD1602液晶显示当前水温&#xff0c;定时提醒&#xff0c;水量变化DS18B20检测当前水体温度&#xff1b;水位传感器检测当前水位&#xff1b;继电器驱动加热片进行水温加热&#xff1b;定时提醒喝水&#xff0c;蜂鸣器报警&#x…

Java-通过IP获取真实地址

文章目录 前言功能实现测试 前言 最近写了一个日志系统&#xff0c;需要通过访问的 IP 地址来获取真实的地址&#xff0c;并且存到数据库中&#xff0c;我也是在网上看了一些文章&#xff0c;遂即整理了一下供大家参考。 功能实现 这个是获取正确 IP 地址的方法&#xff0c;可…

【css】用css样式快速写右上角badge徽标,颜色设置为渐变色

先看效果展示&#xff0c;已公开显示在图片卡片的右上角。 首先是dom代码&#xff1a;需要两个view或者div&#xff0c;public-badge是“已公开”那个矩形&#xff0c;show-signal是右边那个下三角&#xff0c;也就是阴影部分&#xff0c;这样看起来比较有立体感。 <view…

LabVIEW-实现波形发生器

一、题目 用两种方法实现一种多类型信号波形发生器&#xff08;至少包括&#xff1a;正弦波、三角波、方波等&#xff09;&#xff0c;可以调节信号频率、幅度、相位等参数&#xff0c;可以图形化显示信号波形。 需要给出产生信号波形的基本方法、程序设计基本方法以及程序实现…

云计算的学习(二)

二、计算虚拟化 1.计算虚拟化的介绍 1.1虚拟化简介 a.什么是虚拟化 将物理设备逻辑化&#xff0c;转化成文件或者文件夹&#xff0c;这个文件或文件夹一定包含两个部分&#xff1a;一部分用于记录设备配置信息&#xff0c;另一部分记录用户数据。 虚拟机摆脱了服务器的禁锢…

性能测试工具 Jmeter 测试 JMS (Java Message Service)/ActiveMQ 性能

目录 前言 ActiveMQ 介绍 准备工作 编写jndi.properties添加到ApacheJMeter.jar 中 下载 ActiveMQ 配置 Jmeter 进行测试 点对点 (Queues 队列) 配置 Jmeter 进行测试 发布/订阅 (Topic 队列) 配置发布 Publisher 配置订阅 Subscriber 总结 前言 JMeter是一个功能强大…

全方位对比 Postgres 和 MySQL (2023 版)

根据 2023 年 Stack Overflow 调研&#xff0c;Postgres 已经取代 MySQL 成为最受敬仰和渴望的数据库。 随着 Postgres 的发展势头愈发强劲&#xff0c;在 Postgres 和 MySQL 之间做选择变得更难了。 如果看安装数量&#xff0c;MySQL 可能仍是全球最大的开源数据库。 Postgre…