手写redis分布式锁

一个靠谱的分布式锁应该有哪些特点?

1.独占性:任何时候有且仅有一个线程持有锁

2.放死锁:有超时控制机制或撤销操作,得有个释放锁的兜底方案

3.不乱抢:不能张冠李戴,不能unlock别人加的锁

4.可重入性:自己加的锁自己还可以再次获得

基于setnx命令实现分布式锁,setnx成功返回1,失败返回0

1.带过期时间的setnx加锁,finally再次手动解锁

 public void sale1(){String key="redisLock";String uuidValue= IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!redisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L, TimeUnit.SECONDS)){try {Thread.sleep(20L);} catch (InterruptedException e) {throw new RuntimeException(e);}}try {String result = (String)redisTemplate.opsForValue().get("inventory001");int inventorNum =result==null?0:Integer.parseInt(result);if(inventorNum>0){redisTemplate.opsForValue().set("inventory001",String.valueOf(--inventorNum));log.info("卖出一个商品,剩余库存:{}",inventorNum);}else {log.info("商品买完了");}} finally {redisTemplate.delete(key);}}

通过setnx命令加锁,set成功执行业务逻辑,set失败,sleep20毫秒继续抢锁。所有代码执行完,再在finally中手动释放锁。

问题:如果业务执行时间过长,锁已经自己过期了,业务代码还在执行,其它线程就能拿到锁进来了,第一个线程执行完后执行finally释放锁,就把第二个线程加的锁释放掉了。

改进:

2.判断是自己加的锁后再删除,判断和删除操作用lua脚本实现原子性

public void sale2(){String key="redisLock";String uuidValue= IdUtil.simpleUUID()+":"+Thread.currentThread().getId();while(!redisTemplate.opsForValue().setIfAbsent(key,uuidValue,30L, TimeUnit.SECONDS)){try {Thread.sleep(20L);} catch (InterruptedException e) {throw new RuntimeException(e);}}try {String result = (String)redisTemplate.opsForValue().get("inventory001");int inventorNum =result==null?0:Integer.parseInt(result);if(inventorNum>0){redisTemplate.opsForValue().set("inventory001",String.valueOf(--inventorNum));log.info("卖出一个商品,剩余库存:{}",inventorNum);}else {log.info("商品买完了");}} finally {String luaScript="if(redis.call('get',KEYS[1])==ARGV[1]) then return redis.call('del',KEYS[1]) else return 0 end";redisTemplate.execute(new DefaultRedisScript(luaScript, Boolean.class), Arrays.asList(key),uuidValue);}}

 finally删除锁时判断是自己的锁后再删除。判断删除操作通过lua脚本实现原子性。

lau脚本:

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

 解释:如果get key的值为自己的uuidValue才执行del key,否则返回0

还存在问题:不支持可重入性

改进:

3.使用redis的hash结构实现锁的可重入性

hash结构:k k v 依次是lockName  uuid:ThreadId  重入次数

加锁lua逻辑:

if redis.call('exists',KEYS[1]) == 0 or redis.call('hexists',KEYS[1],ARGV[1]) == 1 then redis.call('hincrby',KEYS[1],ARGV[1],1) redis.call('expire',KEYS[1],ARGV[2]) return 1 elsereturn 0end

 先判断lockName这个分布式锁是否存在:

        返回0,不存在,新建属于自己的锁 lockName uuId:ThreadId 1

        返回1,存在,再判断是不是自己的锁(是否存在自己的uuId:ThreadId)

                返回0,不存在,最终返回0,end

                返回1,存在,自己的锁自增1

解锁lau脚本:

if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 0 thenreturn nilelseif redis.call('HINCRBY',KEYS[1],ARGV[1],-1) == 0 thenreturn redis.call('del',KEYS[1])elsereturn 0end

判断是否有分布式锁且是自己的锁 

        否,返回nil

        是,再将自己的锁重入次数-1,-1后重入次数是否为0

                是,删除整个分布式锁

                否 ,返回0,end

问题:还是没有完全实现锁的独占性,当一个线程的执行时间过长,锁自动释放,另一个线程就能拿到锁进来了。

改进:

4.每次加锁成功后后台启动一个定时任务,用来锁续期

private void renewExpire(){String script ="if redis.call('HEXISTS',KEYS[1],ARGV[1]) == 1 then " +"return redis.call('expire',KEYS[1],ARGV[2]) " +"else " +"return 0 " +"end";String uuidValue=IdUtil.simpleUUID()+":"+Thread.currentThread().getId();new Timer().schedule(new TimerTask(){@Overridepublic void run(){if ((Boolean)redisTemplate.execute(new DefaultRedisScript<>(script, Boolean.class), Arrays.asList(lockName),uuidValue,String.valueOf(expireTime))) {renewExpire();}}},(this.expireTime * 1000)/3);}

 假设expireTime是30秒,加锁成功后再调用这个方法,每10秒执行一次定时任务

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

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

相关文章

如何给CAD文件加密丨五种超级简单的CAD图纸加密方法

CAD图纸作为企业核心竞争力的体现&#xff0c;其安全性直接关系到企业的生产效率和市场竞争力。一旦图纸被泄露&#xff0c;竞争对手可能会迅速模仿或改进产品&#xff0c;从而抢占市场份额。此外&#xff0c;图纸的非法获取还可能涉及知识产权纠纷&#xff0c;给企业带来法律风…

镜像加速方法

参考&#xff1a; https://github.com/DaoCloud/public-image-mirror 使用DaoCloud加速&#xff1a; 比如我想在dockerhub下载这个镜像&#xff1a; 本来的命令是&#xff1a; docker pull openjdk:11.0-jdk-slim-buster在要拉取的镜像前&#xff0c;添加前缀&#xff1a;m.…

迅为RK3588S开发板广泛用于边缘技术,人工智能,智能家居,智慧零售,智能网关等

性能强 iTOP-3588S开发板采用瑞芯微RK3588S处理器&#xff0c;是全新一代AloT高端应用芯片&#xff0c;搭载八核64位CPU&#xff0c;四核Cortex-A76和四核Cortex-A55架构主频高达2.4GHZ&#xff0c;8GB内存&#xff0c;32GBEMMC。 四核心架构GPU内置GPU可以完全兼容0penGLES1.1…

数据赋能(144)——开发:数据拆分——影响因素、直接作用、主要特征

影响因素 数据拆分过程中需要考虑的一些影响因素&#xff1a; 数据量和数据增长率&#xff1a; 数据量的大小直接决定了拆分的必要性和可能性。当数据量过大时&#xff0c;拆分可以帮助我们提高查询性能、降低管理复杂度。数据增长率也是考虑因素之一。如果数据增长迅速&…

告别大白嗓子,助唱歌小白华丽转身的秘籍

对于唱歌的小白来说&#xff0c;要去除大白嗓子&#xff0c;可以从以下几个方面入手&#xff1a; 气息控制 进行深呼吸练习&#xff1a;像闻花香一样慢慢地吸气&#xff0c;使气息充满腹部&#xff0c;然后缓缓呼气&#xff0c;感受气息的流动和控制。 练习长音&#xff1a;通…

MFC Ribbon菜单中英实时文切换方法

简介 最近在搞一个老外的项目&#xff0c;本来谈的好好的&#xff0c;纯英文界面。项目接近尾声了&#xff0c;又提出了中英文实时切换的新需求&#xff0c;没办法就只能想办法&#xff0c;毕竟客户最大嘛。 实现方法 还好本来的ribbon英文菜单不复杂&#xff0c;就用纯C编码…

struts2如何防止XSS脚本攻击(XSS防跨站脚本攻击过滤器)

只需要配置一个拦截器即可解决参数内容替换 一、配置web.xml <filter><filter-name>struts-xssFilter</filter-name><filter-class>*.*.filters.XssFilter</filter-class></filter><filter-mapping><filter-name>struts-xss…

国产CPU、国产操作系统、国产服务器,厂商和产品

1、国产CPU 国产CPU的发展正处在一个关键时期&#xff0c;以飞腾、鲲鹏、海光、龙芯、兆芯、申威为代表的厂商正在全力打造“中国芯”。具体分析如下&#xff1a; 飞腾&#xff1a;由中国电子信息产业集团等单位联合成立&#xff0c;基于ARM V8架构永久授权&#xff0c;拥有覆…

节流和防抖的基础概念

节流&#xff08;throttle&#xff09;案例&#xff1a; 假设有一个网页&#xff0c;其中有一个元素&#xff08;例如一个长列表&#xff09;的滚动事件需要监听&#xff0c;以便在用户滚动时执行某些操作&#xff08;如加载更多内容&#xff09;。由于滚动事件可能会非常频繁…

专属大学生的创作活动,你在CSDN坚持创作,虚竹哥带你成长,带你涨粉

❤️作者主页&#xff1a;小虚竹 ❤️作者简介&#xff1a;大家好,我是小虚竹。2022年度博客之星评选TOP 10&#x1f3c6;&#xff0c;Java领域优质创作者&#x1f3c6;&#xff0c;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;掘金年度人气作…

PHP智慧社区小区物业管理系统小程序源码

让生活更便捷&#xff0c;社区更和谐✨ &#x1f3e1;【开篇&#xff1a;智慧生活&#xff0c;从社区开始】&#x1f3e1; 在快节奏的现代生活中&#xff0c;寻找一份便捷与舒适成为了我们共同的追求。小区&#xff0c;作为我们日常生活的温馨港湾&#xff0c;其管理水平和服…

`yield` 关键字

yield 是 Python 中的一个关键字&#xff0c;用于定义生成器函数。生成器函数是一种特殊类型的函数&#xff0c;它可以在迭代过程中产生一系列的值&#xff0c;而不是一次性返回所有结果。这种特性使得生成器在处理大数据集或无限序列时非常高效&#xff0c;因为它不需要一次性…

老年基础护理实训室的介绍:设施配置与应用

本文围绕老年基础护理实训室的设施配置展开讨论&#xff0c;详细阐述了各类设施的种类、功能及其在教学与实践中的应用。同时&#xff0c;强调了合理配置设施对于提高学生实践能力和培养专业素养的重要性&#xff0c;为优化老年基础护理教学提供了参考。 一、引言 随着人口老龄…

【Python 基础】函数 - 1

函数 从前面的章节中,你已经熟悉了 print()、input()和 len()函数。Python 提供了这样一些内建函数,但你也可以编写自己的函数。“函数”就像一个程序内的小程序。 为了更好地理解函数的工作原理,让我们来创建一个 函 数 。 在 文 件 编 辑器 中 输 入 下 面 的 程 序 , …

安泰高压放大器设计要求是什么样的

高压放大器是一种在电子系统中用于放大高电压信号的重要组件。它通常用于应对需要处理高电压信号的应用&#xff0c;如医疗设备、实验室仪器和通信系统。设计高压放大器需要满足一系列严格的要求&#xff0c;以确保其性能稳定、可靠&#xff0c;并符合特定应用的需求。 以下是关…

适合学生写作业的台灯怎么选?一文读懂护眼台灯怎么选!

不知大家发现没有&#xff0c;近些年&#xff0c;戴眼镜的小孩儿是越来越多了&#xff0c;甚至有的地方好多刚上小学一年级的孩子&#xff0c;就已经戴着200度的近视镜了。据统计&#xff0c;如今&#xff0c;中国小学生近视比例为42%&#xff0c;初中生近视比例为80.7%&#x…

LabVIEW航空发动机试验器数据监测分析

1. 概述 为了适应航空发动机试验器的智能化发展&#xff0c;本文基于图形化编程工具LabVIEW为平台&#xff0c;结合航空发动机试验器原有的软硬件设备&#xff0c;设计开发了一套数据监测分析功能模块。主要阐述了数据监测分析功能设计中的设计思路和主要功能&#xff0c;以及…

捷配笔记-如何设计PCB板布线满足生产标准?

PCB板布线是铺设连接各种设备与通电信号的路径的过程。PCB板布线是铺设连接各种设备与通电信号的路径的过程。 在PCB设计中&#xff0c;布线是完成产品设计的重要步骤。可以说&#xff0c;之前的准备工作已经为它做好了。在整个PCB设计中&#xff0c;布线设计过程具有最高的极限…

探索 GraphRAG:图结构与生成式模型的融合

在当今数据驱动的时代&#xff0c;处理和理解复杂的图结构数据成为了一项重要的任务。GraphRAG&#xff08;Graph Retrieval-Augmented Generation&#xff09;作为一种新兴的技术&#xff0c;为解决图相关的问题提供了创新的思路和方法。 一、GraphRAG 简介 GraphRAG 是一种…

[Err] 2006 - MySQL server has gone away 错误 MySQL server hasgoneaway报错原因分析及解决办法

导入sql文件报错&#xff1a; Your SQL statement was too large. 当查询的结果集超过 max_allowed_packet 也会出现这样的报错。定位方法是打出相关报错的语句。 用select * into outfile 的方式导出到文件&#xff0c;查看文件大小是否超过 max_allowed_packet &#xff0c;如…