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…...
在HarmonyOS ArkTS ArkUI-X 5.0及以上版本中,手势开发全攻略:
在 HarmonyOS 应用开发中,手势交互是连接用户与设备的核心纽带。ArkTS 框架提供了丰富的手势处理能力,既支持点击、长按、拖拽等基础单一手势的精细控制,也能通过多种绑定策略解决父子组件的手势竞争问题。本文将结合官方开发文档,…...
JVM垃圾回收机制全解析
Java虚拟机(JVM)中的垃圾收集器(Garbage Collector,简称GC)是用于自动管理内存的机制。它负责识别和清除不再被程序使用的对象,从而释放内存空间,避免内存泄漏和内存溢出等问题。垃圾收集器在Ja…...
苍穹外卖--缓存菜品
1.问题说明 用户端小程序展示的菜品数据都是通过查询数据库获得,如果用户端访问量比较大,数据库访问压力随之增大 2.实现思路 通过Redis来缓存菜品数据,减少数据库查询操作。 缓存逻辑分析: ①每个分类下的菜品保持一份缓存数据…...
Python爬虫(二):爬虫完整流程
爬虫完整流程详解(7大核心步骤实战技巧) 一、爬虫完整工作流程 以下是爬虫开发的完整流程,我将结合具体技术点和实战经验展开说明: 1. 目标分析与前期准备 网站技术分析: 使用浏览器开发者工具(F12&…...
如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
dify打造数据可视化图表
一、概述 在日常工作和学习中,我们经常需要和数据打交道。无论是分析报告、项目展示,还是简单的数据洞察,一个清晰直观的图表,往往能胜过千言万语。 一款能让数据可视化变得超级简单的 MCP Server,由蚂蚁集团 AntV 团队…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...
GruntJS-前端自动化任务运行器从入门到实战
Grunt 完全指南:从入门到实战 一、Grunt 是什么? Grunt是一个基于 Node.js 的前端自动化任务运行器,主要用于自动化执行项目开发中重复性高的任务,例如文件压缩、代码编译、语法检查、单元测试、文件合并等。通过配置简洁的任务…...
LabVIEW双光子成像系统技术
双光子成像技术的核心特性 双光子成像通过双低能量光子协同激发机制,展现出显著的技术优势: 深层组织穿透能力:适用于活体组织深度成像 高分辨率观测性能:满足微观结构的精细研究需求 低光毒性特点:减少对样本的损伤…...
在 Spring Boot 项目里,MYSQL中json类型字段使用
前言: 因为程序特殊需求导致,需要mysql数据库存储json类型数据,因此记录一下使用流程 1.java实体中新增字段 private List<User> users 2.增加mybatis-plus注解 TableField(typeHandler FastjsonTypeHandler.class) private Lis…...
