基于Redis实现的分布式锁
基于Redis实现的分布式锁
- 什么是分布式锁
- 分布式锁主流的实现方案
- Redis分布式锁
- Redis分布式锁的Java代码体现
- 优化一:使用UUID防止误删除
- 优化二:LUA保证删除原子性
什么是分布式锁
- 单体单机部署中可以为一个操作加上锁,这样其他操作就会等待锁释放才能操作
- 但是随业务的不断发展,单机应用常会被分布式集群系统所取代
在分布式集群中存在多台机器,如果给某台机器上加普通的锁,此锁只针对当前机器有效(因为jvm不能跨系统进行锁的控制),因此一种对所有机器都有效的锁应运而生,此即为分布式锁。
即随业务不断发展,需要一种跨JVM的互斥机制来控制共享资源的访问,这就是分布式锁机制要解决的问题!
分布式锁主流的实现方案
分布式锁主流实现方案:
-
- 基于数据库实现分布式锁
-
- 基于缓存(Redis等)
-
- 基于Zookeeper
每一种分布式锁解决方案都有各自的优缺点:
-
- 性能:redis最高
-
- 可靠性:zookeeper最高
这里,我们就基于redis实现分布式锁进行讲解。
Redis分布式锁
Redis中的setex命令就是针对分布式锁操作的一个命令。
回顾setex命令:(setnx中的“nx”表示“not exist:不存在”)
- setnx key value:只有在key 不存在时,才能设置 key 的值。如下图:

使用setnx命令相当于加了一把锁,只有当锁释放的时候此操作才可以继续进行。
思考此锁如何释放?
①首先我们想到的就是del命令删除数据,删除后锁释放,可以再次setnx。 如下图:

但此方案有缺陷。如果锁一直不释放,其他操作就只能等待。所以这样设计不合理!
②于是我们想到expire设置过期时间自动释放锁。如下图:

setnx上锁之后,设置过期时间(通过ttl命令可以查看key剩余多久过期)。过期之后,锁释放。即可再次进行setnx操作。
但上述方式依旧存在问题。
我们提倡的是原子操作,以上setnx操作和使用expire设置过期时间分了两步进行。如果setnx操作执行之后,还没有设置过期时间服务器就断电挂掉了,就不能设置过期时间。针对上锁之后出现异常的情况,引入第三种情况。
③上锁的同时设置上过期时间即可保证原子性操作
(ex表示expire:过期)

Redis分布式锁的Java代码体现
接下来我们通过编写Java代码用一个简单的例子进行演示:
①首先,创建一个SpringBoot空项目,将Redis整合进此项目
②存入redis一条数据,可以把此步骤看作一些具体业务

③Controller新增接口中写入如下代码
@GetMapping("testLock")public void testLock(){//1,获取锁,setnxBoolean lock = redisTemplate.opsForValue().setIfAbsent("lock","111"); //此处相当于setnx的同时设置过期时间为3s//2,获取锁成功,则从Redis中查询num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//判断num为空则直接returnif(StringUtils.isEmpty(value)){return;}//有值就转成成intint num = Integer.parseInt(value+"");//把redis的num加1redisTemplate.opsForValue().set("num", ++num);//释放锁,delredisTemplate.delete("lock");}else{//3获取锁失败,则每隔0.1秒再获取try {Thread.sleep(100);testLock();} catch (InterruptedException e) {e.printStackTrace();}}}
优化一:使用UUID防止误删除
以上的代码还是存在问题的,可能会释放掉其他服务器的锁(即锁释放错的问题)。
异常场景:
两个操作分别记为a、b,设置锁在10秒内过期。
如果a先上锁,在a执行业务操作过程中,其服务器突然卡顿超过10秒。此时分布式锁就会过期而自动释放(此时a的业务操作还未结束)。b拿到这把锁,b拿到之后会先上锁并执行业务操作,b在业务操作过程中,a的服务器卡顿结束,就需要继续完成a的业务操作,并手动释放锁(但a的锁已经过期自动释放了,此时手动释放锁就会释放掉b的锁),显然这是存在问题的。
解决上述问题的一个很好的方法是使用uuid防止误删除。
- 上锁的时候 set key uuid nx ex 10,上锁时设置value为一个唯一的随机值
- 利用uuid的唯一性,表示不同的操作
- 释放锁的时候补充
判断当前uuid和要释放锁的uuid是否一致,一致则释放,否则不释放
代码优化如下:
@GetMapping("testLock")public void testLock(){//1,生成uuidString uuid = UUID.randomUUID().toString();//2,获取锁,setnx (设置value为uuid)Boolean lock = redisTemplate.opsForValue().setIfAbsent("lock",uuid,10,TimeUnit.SECONDS); //3,获取锁成功,则从Redis中查询num的值if(lock){Object value = redisTemplate.opsForValue().get("num");//判断num为空则直接returnif(StringUtils.isEmpty(value)){return;}//有值就转成成intint num = Integer.parseInt(value+"");//把redis的num加1redisTemplate.opsForValue().set("num", ++num);//释放锁,del (释放之前判断当前的uuid是否一致,一致则释放)String lock1 = (String) redisTemplate.opsForValue().get("lock");if (lock1.equals(uuid)) {redisTemplate.delete("lock");}}else{//3,获取锁失败,则每隔0.1秒再获取try {Thread.sleep(100);testLock();} catch (InterruptedException e) {e.printStackTrace();}}}
优化二:LUA保证删除原子性
上一个环节,我们通过uuid解决了误删除问题。但优化后的代码依然存在问题:缺乏原子性。
异常场景:
两个操作分别记为a、b,设置锁在10秒内过期。
如果a先上锁,a执行完成业务操作需要释放锁,假设判断发现uuid一致,此时即将进行释放锁。但服务器此时突然卡顿超过10秒。此时分布式锁就会过期而自动释放(
此时a的锁还未释放)。b拿到这把锁,b拿到之后会先上锁并执行业务操作,b在业务操作过程中,a的服务器卡顿结束,就需要继续释放锁(但a的锁已经过期自动释放了,此时手动释放锁就会释放掉b的锁),显然这是存在原子性问题的。
解决上述问题的一个很好的方法是使用lua脚本(特点:支持原子性操作)。
将复杂的或多步骤的Redis操作,写为一个脚本,一次性提交给Redis执行,减少反复连接Redis,提高性能。
LUA脚本类似Redis事务,有一定的原子性,不会被其他命令插队,可以完成一些类似Redis事务性的操作。
注意:LUA脚本只有Redis 2.6以上版本可用。
@GetMapping("testLockLua")public void testLockLua() {//1 声明一个uuid ,将做为一个value 放入我们的key所对应的值中String uuid = UUID.randomUUID().toString();//2 定义一个锁:lua 脚本可以使用同一把锁,来实现删除!String skuId = "25"; // 访问skuId 为25号的商品 100008348542String locKey = "lock:" + skuId; // 锁住的是每个商品的数据// 3 获取锁Boolean lock = redisTemplate.opsForValue().setIfAbsent(locKey, uuid, 10, TimeUnit.SECONDS);// 第一种: lock 与过期时间中间不写任何的代码。// 如果trueif (lock) {// 执行的业务逻辑开始// 获取缓存中的num 数据Object value = redisTemplate.opsForValue().get("num");// 如果是空直接返回if (StringUtils.isEmpty(value)) {return;}// 不是空 int num = Integer.parseInt(value + "");// 使num 每次+1 放入缓存redisTemplate.opsForValue().set("num", String.valueOf(++num));/*使用lua脚本来释放锁*/// 定义lua 脚本String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";// 使用redis执行lua执行DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(script);// 设置一下返回值类型 为Long// 因为删除判断的时候,返回的0,给其封装为数据类型。如果不封装那么默认返回String 类型,// 那么返回字符串与0 会有发生错误。redisScript.setResultType(Long.class);// 第一个要是script 脚本 ,第二个需要判断的key,第三个是value值。redisTemplate.execute(redisScript, Arrays.asList(locKey), uuid);} else {// 其他线程等待try {// 睡眠Thread.sleep(1000);// 睡醒了之后,调用方法。testLockLua();} catch (InterruptedException e) {e.printStackTrace();}}}
总结:
为确保分布式锁可用,我们至少要确保锁的实现同时满足以下四个条件:
- 互斥性。在任意时刻,只有一个客户端能持有锁。
- 不会发生死锁。即使有一个客户端在持有锁的期间崩溃而没有主动解锁,也能保证后续其他客户端能加锁。
- 解铃还须系铃人。加锁和解锁必须是同一个客户端,客户端自己不能释放掉别人加的锁。
- 加锁和解锁必须具有原子性。
相关文章:
基于Redis实现的分布式锁
基于Redis实现的分布式锁什么是分布式锁分布式锁主流的实现方案Redis分布式锁Redis分布式锁的Java代码体现优化一:使用UUID防止误删除优化二:LUA保证删除原子性什么是分布式锁 单体单机部署中可以为一个操作加上锁,这样其他操作就会等待锁释…...
2023年,还找算法岗工作吗?
点击下方卡片,关注“CVer”公众号AI/CV重磅干货,第一时间送达2023年春招(补招)已经大规模启动了!距离2023年暑期实习不到2个月!距离2024届校招提前批不到4个月!距离2024届秋招正式批不到6个月&a…...
正点原子ARM裸机开发篇
裸机就是手动的操作硬件来实现驱动设备,后面会有驱动框架不需要这么麻烦 第八章 汇编 LED 灯实验 核心过程 通过汇编语言来控制硬件(驱动程序) 代码流程 1、使能 GPIO1 时钟 GPIO1 的时钟由 CCM_CCGR1 的 bit27 和 bit26 这两个位控制&…...
20222023华为OD机试 - 压缩报文还原(JS)
压缩报文还原 题目 为了提升数据传输的效率,会对传输的报文进行压缩处理。 输入一个压缩后的报文,请返回它解压后的原始报文。 压缩规则:n[str],表示方括号内部的 str 正好重复 n 次。 注意 n 为正整数(0 < n <= 100),str只包含小写英文字母,不考虑异常情况。 …...
SheetJS的部分操作
成文时间:2023年2月18日 使用版本:"xlsx": "^0.18.5" 碎碎念: 有错请指正。 这个库自说自话升级到0.19。旧版的文档我记得当时是直接写在github的README上。 我不太会使用github,现在我不知道去哪里可以找到…...
pytest总结
这里写目录标题一、pytest的命名规则二、界面化配置符合命名规则的方法前面会有运行标记三、pytest的用例结构三部分组成四、pytest的用例断言断言写法:五、pytest测试框架结构六、pytest参数化用例1、pytest参数化实现方式2、单参数:每一条测试数据都会…...
CNI 网络分析(九)Calico IPIP
文章目录环境流量分析Pod 间Node 到 PodPod 到 serviceNode 到 serviceNetworkPolicy理清和观测网络流量环境 可以看到,在宿主机上有到每个 pod IP 的路由指向 veth 设备 到对端节点网段的路由 指向 tunl0 下一跳 ens10 的 ip 有到本节点网段 第一个 ip 即 tunl0 的…...
分布式任务调度(XXL-JOB)
什么是分布式任务调度? 任务调度顾名思义,就是对任务的调度,它是指系统为了完成特定业务,基于给定时间点,给定时间间隔或者给定执行次数自动执行任务。通常任务调度的程序是集成在应用中的,比如:…...
Django框架之模型视图--Session
Session 1 启用Session Django项目默认启用Session。 可以在settings.py文件中查看,如图所示 如需禁用session,将上图中的session中间件注释掉即可。 2 存储方式 在settings.py文件中,可以设置session数据的存储方式,可以保存…...
二极管的“几种”应用
不知大家平时有没有留意,二极管的应用范围是非常广的,下面我们来看看我想到几种应用,也可以加深对电路设计的认识: A,特性应用: 由于二极管的种类非常之多,这里这个大类简单罗列下:…...
github上传本地文件详细过程
repository 也就是俗称的仓库 声明:后续操作基于win10系统 前提:有一个github账号、电脑安装了git(官方安装地址) 目的: 把图中pdf文件上传到github上的个人仓库中 效果: 温馨提示: git中复制: ctrl insert…...
常用聚类算法分析
1. 什么是聚类 1.1. 聚类的定义 聚类(Clustering)是按照某个特定标准(如距离)把一个数据集分割成不同的类或簇,使得同一个簇内的数据对象的相似性尽可能大,同时不在同一个簇中的数据对象的差异性也尽可能地大。也即聚类后同一类的数据尽可能聚集到一起…...
OSG三维渲染引擎编程学习之五十八:“第五章:OSG场景渲染” 之 “5.16 简单光源”
目录 第五章 OSG场景渲染 5.16 简单光源 5.16.1 场景中使用光源 5.16.2 简单光源示例 第五章 OSG场景渲染 OSG存在场景树和渲染树,“场景数”的构建在第三章“OSG场景组...
80211无线网络架构
无线网络架构物理组件BSS(Basic Service Set)基本服务集BSSID(BSS Identification)ssid(Service Set Identification)ESS(Extended Service Set)扩展服务集物理组件 无线网络包含四…...
基于springboot+vue的便利店库存管理系统
基于springbootvue的便利店库存管理系统 ✌全网粉丝20W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ 🍅文末获取项目下载方式🍅 一、项目背景…...
3|物联网控制|计算机控制-刘川来胡乃平版|第1章:绪论|青岛科技大学课堂笔记|U1 ppt
目录绪论(2学时)常用仪表设备(3学时)计算机总线技术(4学时)过程通道与人机接口(6学时)数据处理与控制策略(6学时)网络与通讯技术(3学时࿰…...
js打印本地pdf(使用HttpPrinter打印插件)
js打印本地pdf(使用HttpPrinter打印插件)第一步:启动HttpPrinter打印插件第二步:用浏览器打开示例文件\调用示例\websocket协议示例\html\打印pdf.html输入pdf地址 点击 “下载并打印pdf文件”按钮,就可以静默打印了。…...
华为OD机试 - 双十一(Python) | 机试题算法思路 【2023】
最近更新的博客 【新解法】华为OD机试 - 关联子串 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试 - 停车场最大距离 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试 - 任务调度 | 备考思路,刷题要点,答疑,od Base 提供【新解法】华为OD机试…...
2020年UML 秋季期末测试题
1.UML的全称是(B )。A.Unified Making LanguageB.Unified Modeling LanguageC.Unified Meodem languageD.Unify Modeling Language2.UML主要应用于( C)。A.基于螺旋模型的结构化开发方法B.基于数据的数据流开发方法C.基于对象的面…...
SpringCloud - Ribbon负载均衡
目录 负载均衡流程 负载均衡策略 Ribbon加载策略 负载均衡流程 Ribbon将http://userservice/user/1请求拦截下来,帮忙找到真实地址http://localhost:8081LoadBalancerInterceptor类对RestTemplate的请求进行拦截,然后从Eureka根据服务id获取服务列表&…...
IGP(Interior Gateway Protocol,内部网关协议)
IGP(Interior Gateway Protocol,内部网关协议) 是一种用于在一个自治系统(AS)内部传递路由信息的路由协议,主要用于在一个组织或机构的内部网络中决定数据包的最佳路径。与用于自治系统之间通信的 EGP&…...
Linux --进程控制
本文从以下五个方面来初步认识进程控制: 目录 进程创建 进程终止 进程等待 进程替换 模拟实现一个微型shell 进程创建 在Linux系统中我们可以在一个进程使用系统调用fork()来创建子进程,创建出来的进程就是子进程,原来的进程为父进程。…...
ip子接口配置及删除
配置永久生效的子接口,2个IP 都可以登录你这一台服务器。重启不失效。 永久的 [应用] vi /etc/sysconfig/network-scripts/ifcfg-eth0修改文件内内容 TYPE"Ethernet" BOOTPROTO"none" NAME"eth0" DEVICE"eth0" ONBOOT&q…...
springboot整合VUE之在线教育管理系统简介
可以学习到的技能 学会常用技术栈的使用 独立开发项目 学会前端的开发流程 学会后端的开发流程 学会数据库的设计 学会前后端接口调用方式 学会多模块之间的关联 学会数据的处理 适用人群 在校学生,小白用户,想学习知识的 有点基础,想要通过项…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的“no matching...“系列算法协商失败问题
【SSH疑难排查】轻松解决新版OpenSSH连接旧服务器的"no matching..."系列算法协商失败问题 摘要: 近期,在使用较新版本的OpenSSH客户端连接老旧SSH服务器时,会遇到 "no matching key exchange method found", "n…...
[ACTF2020 新生赛]Include 1(php://filter伪协议)
题目 做法 启动靶机,点进去 点进去 查看URL,有 ?fileflag.php说明存在文件包含,原理是php://filter 协议 当它与包含函数结合时,php://filter流会被当作php文件执行。 用php://filter加编码,能让PHP把文件内容…...
Python实现简单音频数据压缩与解压算法
Python实现简单音频数据压缩与解压算法 引言 在音频数据处理中,压缩算法是降低存储成本和传输效率的关键技术。Python作为一门灵活且功能强大的编程语言,提供了丰富的库和工具来实现音频数据的压缩与解压。本文将通过一个简单的音频数据压缩与解压算法…...
自然语言处理——文本分类
文本分类 传统机器学习方法文本表示向量空间模型 特征选择文档频率互信息信息增益(IG) 分类器设计贝叶斯理论:线性判别函数 文本分类性能评估P-R曲线ROC曲线 将文本文档或句子分类为预定义的类或类别, 有单标签多类别文本分类和多…...
【Kafka】Kafka从入门到实战:构建高吞吐量分布式消息系统
Kafka从入门到实战:构建高吞吐量分布式消息系统 一、Kafka概述 Apache Kafka是一个分布式流处理平台,最初由LinkedIn开发,后成为Apache顶级项目。它被设计用于高吞吐量、低延迟的消息处理,能够处理来自多个生产者的海量数据,并将这些数据实时传递给消费者。 Kafka核心特…...
