分布式锁之redis6

一、分布式锁介绍

之前我们都是使用本地锁(synchronize、lock等)来避免共享资源并发操作导致数据问题,这种是锁在当前进程内

那么在集群部署下,对于多个节点,我们要使用分布式锁来避免共享资源并发操作导致数据问题,虽然还是锁,但是是多个进程共用的锁标记,可以用Redis、Zookeeper、Mysql等都可以实现。

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

我们设计分布式锁应该要考虑的东西:

  • 排他性:在分布式应用集群中,同一个方法在同一时间只能被一台机器上的一个线程执行。

  • 容错性:分布式锁一定能得到释放,比如客户端奔溃或者网络中断,可能会导致锁一直不被释放,从而导致死锁,我们可以设置锁的过期时间。

  • 满足可重入、高性能、高可用(集群部署)。

  • 注意分布式锁的开销、锁粒度。

二、分布式锁的实现

实现分布式锁可以用 Redis、Zookeeper、Mysql数据库这几种 , 性能最好的是Redis且是最容易理解。

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

基于redis实现分布式锁:

(1)、加锁 setnx key value:

setnx 的含义就是 set if not exists,有两个参数 setnx(key, value),该方法是原子性操作,如果 key 不存在,则设置当前 key 成功,返回 1;如果当前 key 已经存在,则设置当前 key 失败,返回 0

(2)、解锁 del (key):

得到锁的线程执行完任务,需要释放锁,以便其他线程可以进入,调用 del(key)。

(3)、配置锁超时 expire (key,30s):

客户端奔溃或者网络中断,资源将会永远被锁住,即死锁,因此需要给key配置过期时间,以保证即使没有被显式释放,这把锁也要在一定时间后自动释放。

综合的伪代码:

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

三、 基于Redis实现分布式锁的几种坑

上面我们写的伪代码中有几个坑,我们分别来分析一下。

1、多个命令之间不是原子性操作,如setnxexpire之间,如果setnx成功,但是expire失败,且宕机了,则这个资源就是死锁。

解决方法:使用原子命令来设置和配置过期时间 setnx / setex,在java里面是

redisTemplate.opsForValue().setIfAbsent("key","value",30,TimeUnit.MILLISECONDS)

成功了返回true,失败了返回false。 

2、业务超时,存在其他线程勿删,设置key30秒过期,假如线程A执行很慢超过30秒,则key就被释放了,其他线程B就得到了锁,这个时候线程A执行完成,而B还没执行完成,结果就是线程A删除了线程B加的锁,所以我们的value不能单单只是1。

解决方法:可以在 del 释放锁之前做一个判断,验证当前的锁是不是自己加的锁, 那 value 应该是当前线程的标识或者uuid。

String key = "coupon:id"
String value = Thread.currentThread().getId()
​
if(setnx(key,value) == 1){expire(key,30,TimeUnit.MILLISECONDS)try {//做对应的业务逻辑} finally {//删除锁,判断是否是当前线程加的if(get(key).equals(value)){//还存在时间间隔del(key)}}
}else{//睡眠100毫秒,然后自旋调用本方法
​
}

 3、进一步细化误删,当线程A获取到正常值value时,返回带代码中判断期间锁过期了,线程B刚好重新设置了新值,线程A那边有判断value是自己的标识,然后调用del方法,结果就是删除了新设置的线程B的值。

解决办法:由于redis没有相关的原子性api,所以采用 lua脚本+redis来实现多个命令的原子性。由于【判断和删除】是lua脚本执行,所以要么全成功,要么全失败。

总结:核心是保证多个指令原子性,加锁使用setnx setex 可以保证原子性,解锁采用 lua脚本+redis来保证原子性。

【判断和删除】的lua脚本:

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

 四、原生分布式锁的具体实现

@RestController
@RequestMapping("/api/v1/coupon")
public class CouponController {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@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 = stringRedisTemplate.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 = stringRedisTemplate.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);}}}

运行结果:

d124ae03-5de6-4e25-82b8-fb0b30d7c7fc加锁状态:true
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
解锁状态:1
54041d23-ab3c-492e-977b-99c9b531534f加锁状态:true
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:false
加锁失败,睡眠5秒 进行自旋
解锁状态:1
51f16a96-45cd-476b-95ff-2ee6cc398e37加锁状态:true
解锁状态:1

 

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

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

相关文章

ubuntu中使用安卓模拟器

本文这里介绍 使用 android studio Emulator &#xff0c; 当然也有 Anbox (Lightweight)&#xff0c; Waydroid (Best for Full Android Experience), 首先确保自己安装了 android studio &#xff1b; sudo apt update sudo apt install openjdk-11-jdk sudo snap install…

二语习得理论(Second Language Acquisition, SLA)如何学习英语

二语习得理论&#xff08;Second Language Acquisition, SLA&#xff09;是研究学习者如何在成人或青少年阶段学习第二语言&#xff08;L2&#xff09;的理论框架。该理论主要关注语言习得过程中的认知、社会和文化因素&#xff0c;解释了学习者如何从初学者逐渐变得流利并能够…

WinDbg. From A to Z! 笔记(下)

原文链接: WinDbg. From A to Z! 文章目录 使用WinDbg临界区相关命令示例 -- 查看临界区其他有用的命令 WinDbg中的伪寄存器自动伪寄存器 WinDbg中的表达式其他操作默认的表达式计算方式 WinDbg中的重命名调试器命令语言编程控制流命令程序执行 WinDbg 远程调试事件监控WinDbg …

RainbowDash 的旅行

D RainbowDash 的旅行 - 第七届校赛正式赛 —— 补题 题目大意&#xff1a; 湖中心有一座岛&#xff0c;湖的外围有 m m m 间木屋&#xff08;围绕小岛&#xff09; &#xff0c;第 i i i 间木屋和小岛之间有 a i a_i ai​ 座 A A A 类桥&#xff0c; b i b_i bi​ 座 B …

MySQL-SQL-DDL语句、表结构创建语句

一.SQL SQL&#xff1a;一门操作关系型数据库的编程语言&#xff0c;定义操作所有关系型数据库的统一标准 二. DDL-数据库 1. 查询所有数据库 命令&#xff1a;show databases; 2. 查询当前数据库 命令&#xff1a;select database(); 3. 创建数据库 命令&#xff1a;create da…

Sora结构猜测

方案&#xff1a;VAE Encoder&#xff08;视频压缩&#xff09; -> Transform Diffusion &#xff08;从视频数据中学习分布&#xff0c;并根据条件生成新视频&#xff09; -> VAE Decoder &#xff08;视频解压缩&#xff09; 从博客出发&#xff0c;经过学术Survey&am…

TortoiseSVN设置忽略清单

1.TortoiseSVN > Properties&#xff08;如果安装了 TortoiseSVN&#xff09;。 2. 在弹出的属性窗口中&#xff0c;点击 New > Other。 4. 在 Property name 中输入 svn:ignore 。 5. 在 Property value 中输入要忽略的文件夹或文件名称&#xff0c;例如&#xff1a; #…

深入解析Java哈希表:从理论到实践

哈希表&#xff08;Hash Table&#xff09;是计算机科学中最重要的数据结构之一&#xff0c;也是Java集合框架的核心组件。本文将以HashMap为切入点&#xff0c;深入剖析Java哈希表的实现原理、使用技巧和底层机制。 一、哈希表基础原理 1. 核心概念 键值对存储&#xff1a;通…

leetcode:1582. 二进制矩阵中的特殊位置(python3解法)

难度&#xff1a;简单 给定一个 m x n 的二进制矩阵 mat&#xff0c;返回矩阵 mat 中特殊位置的数量。 如果位置 (i, j) 满足 mat[i][j] 1 并且行 i 与列 j 中的所有其他元素都是 0&#xff08;行和列的下标从 0 开始计数&#xff09;&#xff0c;那么它被称为 特殊 位置。 示…

《数字图像处理》教材寻找合作者

Rafael Gonzalez和Richard Woods所著的《数字图像处理》关于滤波器的部分几乎全错&#xff0c;完全从零开始写&#xff0c;困难重重。关于他的问题已经描述在《数字图像处理&#xff08;面向新工科的电工电子信息基础课程系列教材&#xff09;》。 现寻找能够共同讨论、切磋、…

为 Jenkins Agent 添加污点(Taint)容忍度(Toleration)

在 Kubernetes&#xff08;k8s&#xff09;环境中使用 Jenkins 时&#xff0c;为 Jenkins Agent 添加污点&#xff08;Taint&#xff09;容忍度&#xff08;Toleration&#xff09;是一种常见的配置操作&#xff0c;它允许 Jenkins Agent Pod 被调度到带有特定污点的节点上。下…

LeetCode算法题(Go语言实现)_28

题目 Dota2 的世界里有两个阵营&#xff1a;Radiant&#xff08;天辉&#xff09;和 Dire&#xff08;夜魇&#xff09; Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定。他们以一个基于轮为过程的投票进行。在每一轮中&#xff0c;每一…

使用python实现视频播放器(支持拖动播放位置跳转)

使用python实现视频播放器&#xff08;支持拖动播放位置跳转&#xff09; Python实现视频播放器&#xff0c;在我早期的博文中介绍或作为资料记录过 Python实现视频播放器 https://blog.csdn.net/cnds123/article/details/145926189 Python实现本地视频/音频播放器https://bl…

用Python和Pygame创造粉色粒子爱心:3D渲染的艺术

引言 在计算机图形学中&#xff0c;3D效果的2D渲染是一个迷人的领域。今天&#xff0c;我将分享一个使用Python和Pygame库创建的粉色粒子爱心效果。这个项目不仅视觉效果惊艳&#xff0c;而且代码简洁易懂&#xff0c;非常适合图形编程初学者学习3D渲染的基础概念。 项目概述…

在汇编层面理解MESI

理解MESI协议在汇编层面的表现需要结合缓存一致性机制和处理器指令执行的行为。以下是分步骤的解释&#xff1a; 1. MESI协议基础 MESI是缓存行&#xff08;Cache Line&#xff09;状态的协议&#xff0c;定义四种状态&#xff1a; Modified&#xff08;修改&#xff09;&…

爱瑞编程2025暑期CSP集训营开始招生啦!

一、什么是暑期CSP集训营&#xff1f; 为全力备战2025年9月CSP-J/S认证&#xff0c;举办的线下编程集训活动。 旨在通过高强度编程训练&#xff0c;帮助学员提升竞赛能力&#xff0c;冲刺一等奖。 二、为什么参加集训营&#xff1f; 高效编程特训&#xff1a;封闭式学习&…

问题大集10-git使用commit提交中文显示乱码

&#xff08;1&#xff09;问题 &#xff08;2&#xff09;解决步骤 1&#xff09; 设置全局编码为 UTF-8 git config --global core.quotepath false git config --global i18n.commitEncoding utf-8 git config --global i18n.logOutputEncoding utf-8 2&#xff09; 显示或设…

当AI开始“思考“:大语言模型的文字认知三部曲

引言&#xff1a;从《黑客帝国》说起 1999年上映的科幻经典《黑客帝国》描绘了一个令人震撼的未来图景——人类生活在一个由人工智能构造的数字矩阵中。当我们观察现代大型语言模型的工作原理时&#xff0c;竟发现与这个虚构世界有着惊人的相似&#xff1a;人们正在用矩阵以及矩…

Golang改进后的任务调度系统分析

以下是整合了所有改进点的完整代码实现: package mainimport ("bytes""context""fmt""io""log""net/http""sync""time""github.com/go-redis/redis/v8""github.com/robfig/…

前沿技术有哪些改变生活新趋势

太阳能技术正在改变的生活 它让移动设备有了新的能源选择 太阳能板能直接把阳光转成电能 这对户外活动或者电力不便的地方特别有用 比如现在市面上有不少太阳能充电宝 小巧便携 可以随时给手机平板充电 需要注意的是 这些设备得放在太阳下才能工作 但它们确实能让人在野外多用…