分布式锁之Redis6+Lua脚本实现原生分布式锁

文章目录

        • 简介
        • 设计分布式锁应该考虑的东⻄
        • 基于Redis实现分布式锁
        • 总结
        • 解决解锁的原子性
        • 代码实现
        • 遗留⼀个问题

简介

分布式锁核⼼知识介绍和注意事项

背景

就是保证同⼀时间只有⼀个客户端可以对共享资源进⾏操作

案例

优惠券领劵限制张数、商品库存超卖

核⼼

为了防⽌分布式系统中的多个进程之间相互⼲扰,我们需要⼀种分布式协调技术来对这些进程进⾏调度利⽤互斥机制来控制共享资源的访问,这就是分布式锁要解决的问题避免共享资源并发操作导致数据问题

加锁

本地锁:synchronize、lock等,锁在当前进程内,集群部署下依旧存在问题
分布式锁:redis、zookeeper等实现,虽然还是锁,但是多个进程共⽤的锁标记,可以⽤Redis、Zookeeper、Mysql等都可以

设计分布式锁应该考虑的东⻄

1.排他性 --在分布式应⽤集群中,同⼀个⽅法在同⼀时间只能被⼀台机器上的⼀个线程执⾏
2.容错性 --分布式锁⼀定能得到释放,⽐如客户端奔溃或者⽹络中断
3.满⾜可重⼊、⾼性能、⾼可⽤
4.注意分布式锁的开销、锁粒度

基于Redis实现分布式锁

实现分布式锁 可以⽤ Redis、Zookeeper、Mysql数据库这⼏种 , 性能最好的是Redis

分布式锁离不开 key - value 设置
key 是锁的唯⼀标识,⼀般按业务来决定命名,⽐如想要给⼀种优惠券活动加锁,key 命名为 “coupon:id” 。value就可以使⽤固定值,⽐如设置成1

加锁 SETNX key value
解锁 del (key)
配置锁超时 expire (key,30s)

综合伪代码

1.setnx 的含义就是 SET if Not Exists,有两个参数setnx(key, value),该⽅法是原⼦性操作
2.如果 key 不存在,则设置当前 key 成功,返回 1
3.如果当前 key 已经存在,则设置当前 key 失败,返回 0
4.得到锁的线程执⾏完任务,需要释放锁,以便其他线程可以进⼊,调⽤ del(key)
5.客户端奔溃或者⽹络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在⼀定时间后⾃动释放

methodA(){String key = "coupon_66";if(setnx(key,1== 1{expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑//查询⽤户是否已经领券//如果没有则扣减库存//新增领劵记录} finally {del(key)}}else{//睡眠100毫秒,然后⾃旋调⽤本⽅法methodA()}
}

存在什么问题?

多个命令之间不是原⼦性操作,如setnx和expire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁业务超时,存在其他线程勿删,key 30秒过期,假如线程A执⾏很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执⾏完成,⽽B还没执⾏完成,结果就是线程A删除了线程B加的锁

问题解决

使⽤原⼦命令:设置和配置过期时间 setnx / setex
如: set key 1 ex 30 nx
java⾥⾯redisTemplate.opsForValue().setIfAbsent(“seckill_1”,“success”,30,TimeUnit.MILLISECONDS)
可以在 del 释放锁之前做⼀个判断,验证当前的锁是不是⾃⼰加的锁, 那 value 应该是存当前线程的标识或者uuid
String key = "coupon_66"String value = Thread.currentThread().getId()
进⼀步细化误删当线程A获取到正常值时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是⾃⼰的标识,然后调⽤del⽅法,结果就是删除了新设置的线程B的值核⼼还是判断和删除命令不是原⼦性操作导致

总结

加锁+配置过期时间:保证原⼦性操作
解锁: 防⽌误删除、也要保证原⼦性操作

解决解锁的原子性

前⾯说了redis做分布式锁存在的问题核⼼是保证多个指令原⼦性,加锁使⽤setnx setex 可以保证原⼦性,那解锁使⽤ 判断和删除怎么保证原⼦性

多个命令的原⼦性:采⽤ lua脚本+redis, 由于【判断和删除】是lua脚本执⾏,所以要么全成功,要么全失败

//获取lock的值和传递的值⼀样,调⽤删除操作返回1,否则返回0
String script = "if redis.call('get',KEYS[1])== ARGV[1] then returnredis.call('del',KEYS[1]) else return 0 end";
//Arrays.asList(lockKey)是key列表,uuid是参数
Integer result = redisTemplate.execute(newDefaultRedisScript<>(script, Integer.class),Arrays.asList(lockKey), uuid);

代码实现

import net.xdclass.xdclassredis.util.JsonData;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;import java.time.Duration;
import java.util.Arrays;
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.TimeUnit;@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {@Autowiredprivate RedisTemplate<String,Object> redisTemplate;@GetMapping("add")public JsonData saveCoupon(@RequestParam(value = "coupon_id",required = true) int couponId){//防止其他线程误删String uuid = UUID.randomUUID().toString();String lockKey = "lock:coupon:"+couponId;lock(couponId,uuid,lockKey);return JsonData.buildSuccess();}private void lock(int couponId,String uuid,String lockKey){//lua脚本String script = "if redis.call('get',KEYS[1]) == ARGV[1] then return redis.call('del',KEYS[1]) else return 0 end";Boolean nativeLock = redisTemplate.opsForValue().setIfAbsent(lockKey,uuid,Duration.ofSeconds(30));System.out.println(uuid+"加锁状态:"+nativeLock);if(nativeLock){//加锁成功try{//TODO 做相关业务逻辑TimeUnit.SECONDS.sleep(10L);} catch (InterruptedException e) {} finally {//解锁Long result = redisTemplate.execute( new DefaultRedisScript<>(script,Long.class),Arrays.asList(lockKey),uuid);System.out.println("解锁状态:"+result);}}else {//自旋操作try {System.out.println("加锁失败,睡眠5秒 进行自旋");TimeUnit.MILLISECONDS.sleep(5000);} catch (InterruptedException e) { }//睡眠一会再尝试获取锁lock(couponId,uuid,lockKey);}}}

遗留⼀个问题

锁的过期时间,如何实现锁的⾃动续期或者避免业务执⾏时间过⻓,锁过期了?

1.原⽣⽅式的话,⼀般把锁的过期时间设置久⼀点,⽐如10分钟时间
原⽣代码+redis实现分布式锁使⽤⽐较复杂,且有些锁续期问题更难处理

2.框架 官⽅推荐⽅式:https://redis.io/topics/distlock 使⽤特别简单

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

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

相关文章

JFreeChart(二)之饼图

转载自 JFreeChart饼图 在饼图中&#xff0c;每个扇区的弧长成正比它代表的数量。本章演示了如何使用JFreeChart 从一个给定的业务数据创建饼图。 业务数据 下面的例子描述了移动销售饼图。以下是不同移动品牌和销售(每天单位)列表。 S.N.手机品牌销售(天)1Iphone 5S202Sam…

mysql中如何将默认用户名root改成其他?

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。最近&#xff0c;我的个人站上线啦&#xff0c;大家可以直接在浏览器的地址栏中输入&#xff1a;穆雄雄.com&#xff0c;轻轻敲击回车&#xff0c;即可直接进入……欢迎大家多多关注&#xff0c;多多留…

ASP.NET Core - Razor 页面介绍

简介 随着ASP.NET Core 2 即将来临&#xff0c;最热门的新事物是Razor页面。在之前的一篇文章中&#xff0c;我们简要介绍了ASP.NET Core Razor 页面。 Razor页面是ASP.NET Core的一个新功能&#xff0c;可以使基于页面的编程方式更容易&#xff0c;更高效。 大众的初步印象是…

P2153-晨跑【费用流,网络流,拆点】

前言 这是评测记录 正题 AC评测记录链接&#xff1a; https://www.luogu.org/record/show?rid7945350 大意 一个图&#xff0c;没错要求不能走重复的边和点。求走最多次的情况下路最短。 解题思路 每次行走就是一个流量在流&#xff0c;然后将边权设为1就可以保证边只能走…

Redis(案例三:天热销视频榜单实战-List数据)

需求 1.⼩滴课堂官⽹需要⼀个视频学习榜单&#xff0c;每天更新⼀次 2.需要⽀持⼈⼯运营替换榜单位置 企业中流程 1.定时任务计算昨天最多⼈学习的视频 2.晚上12点到1点更新到榜单上 3.预留⼀个接⼝&#xff0c;⽀持⼈⼯运营 类似场景 京东&#xff1a;热销⼿机榜单、电脑榜单等…

从试卷中悟出的道理……

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。最近&#xff0c;我的个人站上线啦&#xff0c;大家可以直接在浏览器的地址栏中输入&#xff1a;穆雄雄.com&#xff0c;轻轻敲击回车&#xff0c;即可直接进入……不管是三班还是四班&#xff0c;一听…

JFreeChart(三)之条形图

转载自 JFreeChart条形图 本章演示了如何使用JFreeChart从一个给定的业务数据创建条形图。 条形图使用不同的方位(水平或垂直)条&#xff0c;以显示不同类别的比较。图表中的一个轴(域轴)示出了特定的域进行比较&#xff0c;并在另一个轴(范围轴)表示的离散值。 业务数据 …

ASP.NET Core 快速入门(实战篇)

上篇讲了《asp.net core在linux上的环境部署》。今天我们将做几个小玩意实战一下。用到的技术和工具有mysql、websocket、AngleSharp&#xff08;爬虫html解析&#xff09;、nginx多站点部署。 NO1 留言板&#xff08;mysql的使用&#xff09; 演示&#xff1a;http://haojim…

P2053-修车【网络流,费用流】

前言 n和m搞反调半天系列 正题 AC记录链接&#xff1a; https://www.luogu.org/record/show?rid7949136 大意 又m个员工&#xff0c;n辆车&#xff0c;第j个员工修第i辆车需要T[i][j]的时间&#xff0c;求分配让顾客平均等待时间最短。 解题思路 首先先假设一个修车工要修…

走的走的居然飞起来了……

第一次坐飞机&#xff0c;终于满足了我的一个愿望了。记得之前在老家的山头上放羊时&#xff0c;远远的听到飞机想&#xff0c;我和我弟弟能把飞机目送到只剩下一条白线。。。。。一直在定睛观察飞机到底长啥样&#xff0c;那时候对于我们来说&#xff0c;能看见飞机的全面目就…

JFreeChart(四)之线型图

转载自 JFreeChart线型图 线图或折线图来显示信息为一系列由直线段连接的数据点(标记)。线图显示数据在相同的时间频率如何变化。本章从一个给定的业务数据演示如何使用JFreeChart创建线型图。 业务数据 下面的示例绘制折线图显示从1970年开始学校在不同年份开通数量。 给…

对象拷贝的工具类DeepBeanUtils

创建一个对象拷贝的工具类 在Spring 开发框架内部提供有一个BeanUJtils工具类&#xff0c;这个工具类有一个最大的特点就是可以直接实现对象的数据的拷贝操作&#xff0c;可是这个拷贝操作不包含有集合数据的拷贝处理。 import org.springframework.beans.BeanUtils;import jav…

如何在Linux上使用VIM进行.Net Core开发

对于在Linux上开发.Net Core的程序员来说, 似乎都缺少一个好的IDE.Windows上有Visual Studio, Mac上有Visual Studio for Mac, 难道Linux只有Visual Studio Code了吗?Linux上有两个最好的编辑器: VIM和Emacs, 哪个更好不是这一篇的主题, 这一篇的主题是如何在Linux上简单的构建…

P2517-订货【网络流,费用流】

正题 AC链接&#xff1a; https://www.luogu.org/record/show?rid7949532 大意 有n个月&#xff0c;每个月商品价格di&#xff0c;需求量Ui。有容量为S的仓库&#xff0c;一个商品汇存一个月要m。求最低成本 解题思路 首先是月份做为点&#xff0c;成本作为费用&#xff0…

教学交流研讨会总结(一)

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。最近&#xff0c;我的个人站上线啦&#xff0c;大家可以直接在浏览器的地址栏中输入&#xff1a;穆雄雄.com&#xff0c;轻轻敲击回车&#xff0c;即可直接进入……

JFreeChart(五)之XY图

转载自 JFreeChart XY图 在xy图(散点图)是根据一个数据系列组成的x和y值的列表。每个值对(x&#xff0c;y)是坐标系中的一个点。这里1值确定水平(X)位置&#xff0c;而另一个确定垂直(Y)位置。本章演示了如何使用JFreeChart从一个给定的业务数据创建XY图表。 业务数据 考虑…

服务环境搭建

文章目录Nacos服务搭建一、nacos-server 环境搭建二、nacos-mysql 环境搭建三、建 领域模型REST访问配置&#xff08;给其他语言使用&#xff09;四、nacos-server 集群搭建Nacos服务搭建 一、nacos-server 环境搭建 1、 [nacos-server主机]&#xff08;考虑到后续开发的问题…

p2762-太空飞行计划问题【网络流,最大权闭合图,最小割】

正题 AC评测记录链接&#xff1a; https://www.luogu.org/record/show?rid7965757 大意 有nn个实验,有m" role="presentation">mm个实验器材的集合GG。完成第i" role="presentation">ii个实验可以获得cici元&#xff0c;第ii个实验需…

ASP.NET Core 源码学习之Logging[1]:Introduction

在ASP.NET 4.X中&#xff0c;我们通常使用 log4net, NLog 等来记录日志&#xff0c;但是当我们引用的一些第三方类库使用不同的日志框架时&#xff0c;就比较混乱了。而在 ASP.Net Core 中内置了日志系统&#xff0c;并提供了一个统一的日志接口&#xff0c;ASP.Net Core 系统以…

全国教学交流研讨会“教学为本”主题总结

大家好&#xff0c;我是雄雄&#xff0c;欢迎关注公众号【雄雄的小课堂】。最近&#xff0c;我的个人站上线啦&#xff0c;大家可以直接在浏览器的地址栏中输入&#xff1a;穆雄雄.com&#xff0c;轻轻敲击回车&#xff0c;即可直接进入……