Redis高并发分布锁实战
Redis高并发分布锁实战
问题场景
场景一: 没有捕获异常
// 仅仅加锁
// 读取 stock=15
Boolean ret = stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v)
// TODO 业务代码 stock--
stringRedisTemplate.delete("lock_key");
- **
问题**- 以上场景在代码出现异常的时候,会出现死锁,导致后面的线程无法获取锁,会阻塞所有线程
场景二: 线程间交互删除锁
// 加锁,且设置锁过期时间
// 读取 stock = 15
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", "1", 10, TimeUnit.SECONDS);
// TODO 业务代码 stock--
stringRedisTemplate.delete(key);
问题- 相对于场景一多了锁的
过期时间 - 假如线程A执行业务代码的时间是15s,而锁的时间是10s,那么锁过期后自动会被删除,此时线程B获取锁,执行业务代码时间为8s,而这个时候线程A刚好执行完业务代码了,就会
出现线程A把线程B的锁删除掉
- 相对于场景一多了锁的
// 加锁,且(给每个线程)设置锁过期时间, 删除锁时判断是否当前线程
// 读取 stock = 15
String uuid = UUID.getUuid;
Boolean result = stringRedisTemplate.opsForValue().setIfAbsent("key", uuid, 10, TimeUnit.SECONDS);
// TODO 业务代码 stock-- 15 -> 14
// 判断是否当前线程
if (uuid.equals(stringRedisTemplate.opsForValue().get(key)) {// 极端场景下:(执行时间定格在9.99秒)突然卡顿 10ms or redis服务宕机!!!// 此时刚好锁过期,自动删除// 其他线程获取锁,然后会把上个线程的锁删除,又会出现bugstringRedisTemplate.delete(key);
}
问题- 当线程A持有锁,执行完扣减库存后,假设锁过期时间是10s,
恰好此时在执行9.99s的时候出现卡顿,等服务器反应过来之间,锁过期自动删除了,这个时候线程B获取锁,然后执行业务代码,此时线程A刚好反应过来,执行锁删除,这样就会把线程B的锁删除,要知道此时线程B是没有执行完业务代码的,锁删除后,线程C又获取锁,此时线程B执行完,又会把线程C的锁删除,依次类推
- 当线程A持有锁,执行完扣减库存后,假设锁过期时间是10s,
解决方案
方案: 使用Redisson分布式锁
@Autowire
public Redisson redisson;public void stock () {String key = "key";RLock lock = redisson.getLock(key);try {lock.lock();// TODO: 业务代码 } catch(Exception e) {lock.unlock();}}
优点
-
自带
锁续命功能,默认30s过期时间,可以自行调整过期时间 -
LUA脚本模拟商品减库存
//模拟一个商品减库存的原子操作
//lua脚本命令执行方式:redis-cli --eval /tmp/test.lua , 10
jedis.set("product_stock_10016", "15"); // 初始化商品10016的库存
String script = " local count = redis.call('get', KEYS[1]) " +" local a = tonumber(count) " +" local b = tonumber(ARGV[1]) " +" if a >= b then " +" redis.call('set', KEYS[1], a-b) " +// 模拟语法报错回滚操作" bb == 0 " +" return 1 " +" end " +" return 0 ";
Object obj = jedis.eval(script, Arrays.asList("product_stock_10016"), Arrays.asList("10"));
System.out.println(obj);
Redisson实现
public void lockInterruptibly(long leaseTime, TimeUnit unit) throws InterruptedException {long threadId = Thread.currentThread().getId();Long ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl != null) {RFuture<RedissonLockEntry> future = this.subscribe(threadId);this.commandExecutor.syncSubscription(future);try {while(true) {ttl = this.tryAcquire(leaseTime, unit, threadId);if (ttl == null) {return;}if (ttl >= 0L) {this.getEntry(threadId).getLatch().tryAcquire(ttl, TimeUnit.MILLISECONDS);} else {this.getEntry(threadId).getLatch().acquire();}}} finally {this.unsubscribe(future, threadId);}}
}
-
LUA脚本适合用于做原子操作,在Redisson分布式锁实现中,就有用到LUA脚本实现创建/获取锁的操作,而Redis的事务机制(multi/exec)非常鸡肋,
可以对相同的key通过不同的数据结构做修改,比如事务开启后,将String类型的key,再次使用hset修改,而且还能修改成功,这就意味着事务已失效,而且不支持事务回滚 -
Redisson分布式锁流程-
高并发下Lua脚本保证了原子性
-
Schedule定期锁续命
-
未获取锁的线程先Subscribe channel
-
自旋,再次尝试获取锁
-
如果还是未获取锁,则通过Semaphore->tryAcquire(ttl.TimeUnit)阻塞所有进入自旋代码块的线程(
这样做的目的是为了不让其他线程因为不停的自旋而给服务器造成压力,所以让其他线程先阻塞一段时间,等阻塞时间结束,再次自旋) -
获取锁的线程解锁后,使用Redis的发布功能进行发布消息,订阅消息的线程调用release方法释放阻塞的线程,再次尝试获取锁
-
如果是调用Redisson的tryAcquire(1000,TimeUnit.SECONDS)方法,那么未获取到锁的线程不用进行自旋,因为时间一到,未获取到锁的线程就会自动往下走进入业务代码块
-

-
总结
- Redis分布式锁自己去实现
可能会出现几个问题- 没有在finally显示释放锁,当客户端挂掉了,锁没有被及时删除,这样会导致死锁问题,它这个是
需要我们显示的释放锁 - 假如此时我们设置过期时间,但是我们用的是同一个key,就可能出现下一个线程删除上一个线程的锁,但是上一个线程还没有执行完,它这个
需要key是不能重复的 - 假如我们既设置了过期时间也指定了不同的key,此时可能因为网络延迟出现上一个线程删除下一个线程的锁,也就是说业务执行的时间超过了锁过期的时间,它这个
需要一个锁续命的功能
- 没有在finally显示释放锁,当客户端挂掉了,锁没有被及时删除,这样会导致死锁问题,它这个是
- 对于Redis它也有事务,但是它的事务非常鸡肋,仅仅只能保证多个指令按照顺序执行,并不能保证原子性,而且key还能被其他指令修改对应的数据结构,所以我们选择Redisson来进行分布式锁的实现,因为它提供了锁续命的功能以及通过lua脚本保证了多个指令的原子操作,
主要流程是这样的- 当线程抢到了锁,假如业务没执行完,会定时去进行锁续命,而其他线程会订阅这个抢到锁的线程的channel,然后自旋一定时间去尝试获取锁,如果获取锁失败,会被安排进入队列中阻塞,一旦线程释放锁,他们会被通知到,然后继续去自旋一定时间去尝试获取锁,重复此操作
相关文章:
Redis高并发分布锁实战
Redis高并发分布锁实战 问题场景 场景一: 没有捕获异常 // 仅仅加锁 // 读取 stock15 Boolean ret stringRedisTemplate.opsForValue().setIfAbsent("lock_key", "1"); // jedis.setnx(k,v) // TODO 业务代码 stock-- stringRedisTemplate.delete(&quo…...
Kotlin基础——DSL
DSL(领域特定语言) 常见的DSL就是SQL和正则表达式,用于操作数据库和文本字符串,Kotlin DSL通常为嵌套的Lambda表达式或链式方法,如 https://github.com/gradle/gradle-script-kotlin 用于构建Gradle脚本https://gith…...
《Docker 简易速速上手小册》第4章 Docker 容器管理(2024 最新版)
文章目录 4.1 容器生命周期管理4.1.1 重点基础知识4.1.2 重点案例:启动并管理 Python Flask 应用容器4.1.3 拓展案例 1:调试运行中的容器4.1.4 拓展案例 2:优雅地停止和清理容器 4.2 容器数据管理与持久化4.2.1 重点基础知识4.2.2 重点案例&a…...
【人脸朝向识别与分类预测】基于PNN神经网络
课题名称:基于PNN神经网络的人脸朝向识别分类 版本日期:2024-02-20 运行方式:直接运行PNN0503.m文件 代码获取方式:私信博主或 QQ:491052175 模型描述: 采集到一组人脸朝向不同角度时的图像,图像来自不…...
【Python笔记-设计模式】组合模式
一、说明 组合模式是一种结构型设计模式, 你可以使用它将对象组合成树状结构, 并且能像使用独立对象一样使用它们。 (一) 解决问题 处理树形结构:可以很好地处理树形结构的数据,使得用户可以统一对待单个对象和对象组合。统一接…...
51单片机学习(5)-----蜂鸣器的介绍与使用
前言:感谢您的关注哦,我会持续更新编程相关知识,愿您在这里有所收获。如果有任何问题,欢迎沟通交流!期待与您在学习编程的道路上共同进步。 目录 一. 蜂鸣器的介绍 1.蜂鸣器介绍 2.压电式蜂鸣器 (无源…...
-bash: /root/.ssh/authorized_keys: Read-only file system
问题背景 由于跳板机不支持 ssh-copy-id 命令,为了配置免密登录,考虑在服务器上手动使用 cat 命令写入跳板机公钥 cat <<EOL >> ~/.ssh/authorized_keys [Your public key] EOL但却出现了以下错误 -bash: /root/.ssh/authorized_keys: Re…...
3,设备无关位图显示
建立了一个类Dib Dib.h #pragma once #include “afx.h” class CDib :public CObject { public: CDib(); ~CDib(); char* GetFileName(); BOOL IsValid(); DWORD GetSize(); UINT GetWidth(); UINT GetHeight(); UINT GetNumberOfColors(); RGBQUAD* GetRGB(); BYTE* GetDat…...
转前端了!!
大家好,我是冰河~~ 没错,为了更好的设计和开发分布式IM即时通讯系统,也为了让大家能够直观的体验到分布式IM即时通讯系统的功能,冰河开始转战前端了。也就是说,整个项目从需求立项到产品设计,从架构设计到…...
RESTful API如何使用它构建 web 应用程序。
链接:华为机考原题 RESTful API(Representational State Transfer)是一种基于网络的软件架构风格,用于设计和访问网络资源。它是一种轻量级、灵活、可扩展的架构,常用于构建Web应用程序和服务。 使用RESTful API构建Web应用程序的步骤如下&…...
现在学Oracle是49年入国军么?
今天周末,不聊技术,聊聊大家说的最多的一个话题 先说明一下,防止挨喷😆 本人并不是职业dba,对数据库就是爱好,偶尔兼职,以下仅个人观点分析,如有不同观点请轻喷,哈哈&…...
【回溯】组合问题||
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。 candidates 中的每个数字在每个组合中只能使用 一次 。 注意:解集不能包含重复的组合。 示例 1: 输入: candidates [10,1,2,7,6,…...
【c语言】字符函数和字符串函数(下)
前言 书接上回 【c语言】字符函数和字符串函数(上) 上一篇讲解的strcpy、strcat、strcmp函数的字符串长度是不受限制的 而本篇strncpy、strncat、strcnmp函数的字符串长度是受限制的 欢迎关注个人主页:逸狼 创造不易,可以点点赞吗~ 如有错误,…...
基于Java的艺培管理解决方案
✍✍计算机毕业编程指导师 ⭐⭐个人介绍:自己非常喜欢研究技术问题!专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目:有源码或者技术上的问题欢迎在评论区一起讨论交流! ⚡⚡ Java、…...
Python算法题集_实现 Trie [前缀树]
Python算法题集_实现 Trie [前缀树] 题208:实现 Trie (前缀树)1. 示例说明2. 题目解析- 题意分解- 优化思路- 测量工具 3. 代码展开1) 标准求解【定义数据类默认字典】2) 改进版一【初始化字典无额外类】3) 改进版二【字典保存结尾信息无额外类】 4. 最优算法5. 相关…...
pytorch简单新型模型测试参数
import torch from torch.nn import Conv2d,MaxPool2d,Sequential,Flatten,Linear import torchvision import torch.optim.optimizer from torch.utils.data import DataLoader,dataset from torch import nn import torch.optim.optimizer# 建模 model nn.Linear(2,1)#损失 …...
Unity中URP下实现水体(水面高光)
文章目录 前言一、实现高光反射原理1、原理:2、公式: 二、实现1、定义 _SpecularColor 作为高光反射的颜色2、定义 _SpecularIntensity 作为反射系数,控制高光反射的强度3、定义 _Smoothness 作为高光指数,用于模型高光范围4、模拟…...
26.HarmonyOS App(JAVA)列表对话框
列表对话框的单选模式: //单选模式 // listDialog.setSingleSelectItems(new String[]{"第1个选项","第2个选项"},1);//单选 // listDialog.setOnSingleSelectListener(new IDialog.ClickedListener() { // Override …...
五种主流数据库:常用字符函数
SQL 字符函数用于字符数据的处理,例如字符串的拼接、大小写转换、子串的查找和替换等。 本文比较五种主流数据库常用数值函数的实现和差异,包括 MySQL、Oracle、SQL Server、PostgreSQL 以及 SQLite。 字符函数函数功能MySQLOracleSQL ServerPostgreSQ…...
软考笔记--企业资源规划和实施
企业资源是指企业业务活动和战略运营的事物,包括人、财和物,也包括信息资源,同时也包括企业的内部和外部资源。企业资源可以归纳为物流,资金流和信息流。企业资源规划(ERP)是只建立在信息技术基础上&#x…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...
java_网络服务相关_gateway_nacos_feign区别联系
1. spring-cloud-starter-gateway 作用:作为微服务架构的网关,统一入口,处理所有外部请求。 核心能力: 路由转发(基于路径、服务名等)过滤器(鉴权、限流、日志、Header 处理)支持负…...
ETLCloud可能遇到的问题有哪些?常见坑位解析
数据集成平台ETLCloud,主要用于支持数据的抽取(Extract)、转换(Transform)和加载(Load)过程。提供了一个简洁直观的界面,以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...
前端开发面试题总结-JavaScript篇(一)
文章目录 JavaScript高频问答一、作用域与闭包1.什么是闭包(Closure)?闭包有什么应用场景和潜在问题?2.解释 JavaScript 的作用域链(Scope Chain) 二、原型与继承3.原型链是什么?如何实现继承&a…...
Unit 1 深度强化学习简介
Deep RL Course ——Unit 1 Introduction 从理论和实践层面深入学习深度强化学习。学会使用知名的深度强化学习库,例如 Stable Baselines3、RL Baselines3 Zoo、Sample Factory 和 CleanRL。在独特的环境中训练智能体,比如 SnowballFight、Huggy the Do…...
技术栈RabbitMq的介绍和使用
目录 1. 什么是消息队列?2. 消息队列的优点3. RabbitMQ 消息队列概述4. RabbitMQ 安装5. Exchange 四种类型5.1 direct 精准匹配5.2 fanout 广播5.3 topic 正则匹配 6. RabbitMQ 队列模式6.1 简单队列模式6.2 工作队列模式6.3 发布/订阅模式6.4 路由模式6.5 主题模式…...
push [特殊字符] present
push 🆚 present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中,push 和 present 是两种不同的视图控制器切换方式,它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...
Linux nano命令的基本使用
参考资料 GNU nanoを使いこなすnano基础 目录 一. 简介二. 文件打开2.1 普通方式打开文件2.2 只读方式打开文件 三. 文件查看3.1 打开文件时,显示行号3.2 翻页查看 四. 文件编辑4.1 Ctrl K 复制 和 Ctrl U 粘贴4.2 Alt/Esc U 撤回 五. 文件保存与退出5.1 Ctrl …...
HTML前端开发:JavaScript 获取元素方法详解
作为前端开发者,高效获取 DOM 元素是必备技能。以下是 JS 中核心的获取元素方法,分为两大系列: 一、getElementBy... 系列 传统方法,直接通过 DOM 接口访问,返回动态集合(元素变化会实时更新)。…...
aardio 自动识别验证码输入
技术尝试 上周在发学习日志时有网友提议“在网页上识别验证码”,于是尝试整合图像识别与网页自动化技术,完成了这套模拟登录流程。核心思路是:截图验证码→OCR识别→自动填充表单→提交并验证结果。 代码在这里 import soImage; import we…...
