当前位置: 首页 > news >正文

springboot整合lua脚本在Redis实现商品库存扣减

1、目的

        使用lua脚本,可以保证多条命令的操作原子性;同时可以减少操作IO(比如说判断redis对应数据是否小于0,小于0就重置为100,这个场景一般是取出来再判断,再存放进行,就至少存在2次IO,用lua脚本一条命令1次IO就解决了,在批量扣减情况存在多次IO,lua脚本1次也可以解决),提高速度,降低IO.

2、使用案列

        根据传入的产品标识及数量扣减该产品数量;此处为单个产品扣减,可优化为批量产品传入,lua内部用table处理。

2.1 初始化redis参数

添加产品及库存(hash结构)。添加两种水果的库存。

// 向Redis中添加数据
redisTemplate.opsForHash().put("productMap", "pro1", "{\"name\":\"苹果\",\"stock\":100}");
redisTemplate.opsForHash().put("productMap", "pro2", "{\"name\":\"西瓜\",\"stock\":1200}");

查看结果:

2.2 业务代码

传入lua脚本,实现对应产品库存数量扣减。(可优化为多产品批量扣减)

通过setnx加锁,防止死锁设置锁超时时间,同时业务执行完手动释放锁。设置锁等待时间、及锁等待轮询获取锁。(eg:自旋释放cpu资源重新抢占资源)

@Test
public void tete(){// 向Redis中添加数据redisTemplate.opsForHash().put("productMap", "pro1", "{\"name\":\"苹果\",\"stock\":100}");redisTemplate.opsForHash().put("productMap", "pro2", "{\"name\":\"西瓜\",\"stock\":1200}");// Lua 脚本字符串    String luaScript = "local productKey = KEYS[1]; " +"local pro = KEYS[2]; " +"local lockKey = KEYS[3]; " +"local lockTimeout = tonumber(ARGV[1]); " +"local deductAmount = tonumber(ARGV[2]); " +"local spinIntervalMs = tonumber(ARGV[3]); " +"local maxSpinCount = tonumber(ARGV[4]); " +"local lockAcquired = redis.call('setnx', lockKey, 1); " +"if lockAcquired == 1 then " +"    redis.call('pexpire', lockKey, lockTimeout); " +"    local currentValue = redis.call('hget', productKey, pro); " +"    if currentValue then " +"        local dbObj = cjson.decode(currentValue);" +"        local currentStock = tonumber(dbObj.stock); " +"        if currentStock >= deductAmount then " +"            dbObj.stock = currentStock - deductAmount; " +"            local updatedValue = cjson.encode(dbObj); " +"            redis.call('hset', productKey, pro, updatedValue); " +"            redis.call('del', lockKey); " +  // 释放锁"            return true; " +"        else " +"            return false; " +"        end " +"    else " +"        return false; " +"    end " +"else " +"    local spinCount = 0; " +"    while spinCount < maxSpinCount do " +"        local lockValue = redis.call('get', lockKey); " +"        if not lockValue then " +"            lockAcquired = redis.call('setnx', lockKey, 1); " +"            if lockAcquired == 1 then " +"                redis.call('pexpire', lockKey, lockTimeout); " +"                local currentValue = redis.call('hget', productKey, pro); " +"                if currentValue then " +"                   local dbObj = cjson.decode(currentValue);" +"                   local currentStock = tonumber(dbObj.stock); " +"                    if currentStock >= deductAmount then " +"                        dbObj.stock = currentStock - deductAmount; " +"                        local updatedValue = cjson.encode(dbObj); " +"                        redis.call('hset', productKey, pro, updatedValue); " +"                        redis.call('del', lockKey); " +  "                        return true; " +"                    else " +"                        return false; " +"                    end " +"                else " +"                    return false; " +"                end " +"            end " +"            break; " +"        end " +"        spinCount = spinCount + 1; " +"    end " +"    return false; " +"end";// 创建DefaultRedisScript对象DefaultRedisScript<Boolean> script = new DefaultRedisScript<>();script.setScriptText(luaScript);script.setResultType(Boolean.class); // 设置返回类型为Boolean// 执行脚本Boolean result = (Boolean) redisTemplate.execute(script,Collections.unmodifiableList(List.of("productMap","pro1","lock:pro1")), // KEYS参数"50000", // ARGV参数第一个:锁过期时间(毫秒)"10", // ARGV参数第二个:扣减数量"1000",// ARGV参数第3个:等待时间"5");// ARGV参数第4个:轮询次数System.out.println("result1:"+result);Boolean result2 = (Boolean) redisTemplate.execute(script,Collections.unmodifiableList(List.of("productMap","pro1","lock:pro1")), // KEYS参数"50000", // ARGV参数第一个:锁过期时间(毫秒)"10", // ARGV参数第二个:扣减数量"1000","5");System.out.println("result2:"+result2);Boolean result3 = (Boolean) redisTemplate.execute(script,Collections.unmodifiableList(List.of("productMap","pro1","lock:pro1")), // KEYS参数"50000", // ARGV参数第一个:锁过期时间(毫秒)"10", // ARGV参数第二个:扣减数量"1000","5");System.out.println("result3:"+result3);if (result2) {System.out.println("Stock deduction successful.");} else {System.out.println("Insufficient stock or lock already acquired.");}// 验证库存是否正确扣减Object updatedValue = redisTemplate.opsForHash().get("productMap", "pro1");System.out.println(updatedValue);Boolean result5 = (Boolean) redisTemplate.execute(script,Collections.unmodifiableList(List.of("productMap","pro2","lock:pro2")), // KEYS参数"5000", // ARGV参数第一个:锁过期时间(毫秒)"500", // ARGV参数第二个:扣减数量"1000","5");
}

2.3 正常执行结果

2.4 若获取锁超时,则会出现扣减失败

脚本执行时间过长会导致。(此处可通过删除手动释放锁实现:模拟业务耗时过长没办法手动释放锁需等待锁国企时间)

第二个扣减等待超时。可通过设置调整添加自旋时间重试或业务代码判断重试机制

相关文章:

springboot整合lua脚本在Redis实现商品库存扣减

1、目的 使用lua脚本&#xff0c;可以保证多条命令的操作原子性&#xff1b;同时可以减少操作IO&#xff08;比如说判断redis对应数据是否小于0&#xff0c;小于0就重置为100&#xff0c;这个场景一般是取出来再判断&#xff0c;再存放进行&#xff0c;就至少存在2次IO,用lua脚…...

MySQL ON DUPLICATE KEY UPDATE影响行数

目录 分析为什么Updates返回7 总结 数据库更新日志如下 insertOrUpdateList|> Preparing: INSERT INTO clue_user_tag (vuid, tag_id, tag_type, content) VALUES (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) , (?, ?, ?, ?) ON DUPLICATE KEY UPDATE …...

uniapp小程序 slot中无法传递外部参数的解决方案

最近在封装一个List组件&#xff0c;外部传给我数据&#xff0c;我循环后将每个Item部分slot到外部&#xff0c;由调用者自己去写item布局&#xff0c;类似ElementUI、iView的Tabe列表。 List: <view v-if"list.length > 0" class"list-scroll__item&quo…...

umi实现动态获取菜单权限

文章目录 前景登录组件编写登录逻辑菜单的时机动态路由页面刷新手动修改地址 前景 不同用户拥有不同的菜单权限&#xff0c;现在我们实现登录动态获取权限菜单。 登录组件编写 //当我们需要使用dva的dispatch函数时&#xff0c;除了通过connect函数包裹组件还可以使用这种方…...

Pytest-Bdd-Playwright 系列教程(14):Docstring 参数

Pytest-Bdd-Playwright 系列教程&#xff08;14&#xff09;&#xff1a;Docstring 参数 前言一、什么是docstring?二、基本语法三、主要特点四、实际例子五、注意事项六、使用建议总结 前言 在自动化测试的过程中&#xff0c;我们经常需要处理复杂的测试数据或需要输入多行文…...

交互开发---测量工具(适用VTK或OpenGL开发的应用程序)

简介&#xff1a; 经常使用RadiAnt DICOM Viewer来查看DICOM数据&#xff0c;该软件中的测量工具比较好用&#xff0c;就想着仿照其交互方式自己实现下。后采用VTK开发应用程序时&#xff0c;经常需要开发各种各样的测量工具&#xff0c;如果沿用VTK的widgets的思路&#xff0c…...

Qt 一个简单的QChart 绘图

Qt 一个简单的QChart 绘图 先上程序运行结果图&#xff1a; “sample9_1QChart.h” 文件代码如下&#xff1a; #pragma once#include <QtWidgets/QMainWindow> #include "ui_sample9_1QChart.h"#include <QtCharts> //必须这么设置 QT_CHARTS_USE_NAME…...

【Java笔记】LinkedList 底层结构

一、LinkedList 的全面说明 LinkedList底层实现了双向链表和双端队列特点可以添加任意元素(元素可以重复)&#xff0c;包括null线程不安全&#xff0c;没有实现同步 二、LinkedList 的底层操作机制 三、LinkedList的增删改查案例 public class LinkedListCRUD { public stati…...

el-table组件树形数据修改展开箭头

<style lang"scss" scoped> ::v-deep .el-table__expand-icon .el-icon-arrow-right:before {content: ">"; // 箭头样式font-size: 16px; }::v-deep .el-table__expand-icon{ // 没有展开的状态background-color: rgba(241, 242, 245, 1);color:…...

太速科技-FMC154-基于FMC 八路SFP+万兆光纤子卡

FMC154-基于FMC 八路SFP万兆光纤子卡 一、板卡概述 本卡是一个FPGA夹层卡&#xff08;FMC&#xff09;模块&#xff0c;可提供高达8个SFP / SFP 模块接口&#xff0c;直接插入千兆位级收发器&#xff08;MGT&#xff09;的赛灵思FPGA。支持业界标准的小型可插拔&#xff0…...

记:排查设备web时慢时快问题,速度提升100%

问题描述 问题1&#xff1a; 发现web登录界面刷新和登录功能都比较卡&#xff0c;开浏览器控制台看了下&#xff0c;让我很惊讶&#xff0c;居然能这么慢&#xff1a; 公司2个局域网内的表现不同&#xff0c;局域网A中的都比较卡&#xff0c;局域网B中的又不存在该现象。 问…...

音视频入门基础:MPEG2-TS专题(13)——FFmpeg源码中,解析Section Header的实现

一、引言 在《音视频入门基础&#xff1a;MPEG2-TS专题&#xff08;11&#xff09;—— TS中的Section》中讲述了Section Header的基本概念&#xff0c;本文讲述FFmpeg源码中是怎样解析Section Header的。 二、parse_section_header函数的定义 FFmpeg源码中通过parse_section…...

根据PDF模板单个PDF导出到浏览器和多个PDF打包ZIP导出到浏览器

一、单个PDF导出到浏览器 /*** * param templatePath 模板路径* param fileName 文件名称* param data 填充文本* param images 填充图片* param response* throws IOException*/public static void generateTempPDF(String templatePath, String fileName, Map<String, S…...

如何创建一个基本的Spring Boot应用程序

以下是一个简单的Spring Boot应用开发代码示例&#xff0c;它展示了如何创建一个基本的Spring Boot应用程序&#xff0c;并实现一个简单的RESTful API服务。 步骤1&#xff1a;创建项目 使用Spring Initializr或您喜欢的IDE&#xff08;如IntelliJ IDEA或Eclipse&#xff09;…...

1.2 计算机网络的分类和应用(重要知识点)

1.2.1 计算机网络的分类 计算机网络的定义&#xff1a; 由通信线路互相连接的、能自主工作的计算机构成&#xff0c;强调各计算机&#xff08;工作站&#xff09;拥有独立的计算资源和任务能力。与多终端分时系统不同&#xff0c;后者终端仅作为主机接口&#xff0c;不具备计…...

@JsonSerialize失效解决

当在实体类中加入这个注解时&#xff0c;本意是想如果是空值则返回0给页面&#xff0c;但是发现使用 JsonSerialize(using BigSerializer.class)无效&#xff0c;因为如果是null值会不走序列化的接口实现类&#xff0c;需要使用nullUsing 需要这样使用...

Docker部署WebRTC-Streamer

文章目录 WebRTC-Streamer概述Docker部署WebRTC-StreamerVue使用WebRTC-Streamer一些问题 WebRTC-Streamer概述 WebRTC-Streamer是一个基于WebRTC技术的流媒体传输工具&#xff0c;它可以通过Web浏览器实现实时音视频流的传输和播放。它提供了一种简单而强大的方式&#xff…...

2025年的大模型计划重点在于跨领域智能、工作流自动化、多模态能力强化

明年的计划和大模型发展方向可以围绕以下几个方面展开&#xff0c;结合实际应用场景和技术趋势&#xff0c;明确可执行的目标和期待的成果&#xff1a; 2025 年计划与展望&#xff1a;大模型能做些什么&#xff1f; 1. 更深层次的跨领域能力融合 目标&#xff1a;构建更强的跨…...

day12 接口测试 ——入门→精通→实战(1)

【没有所谓的运气&#x1f36c;&#xff0c;只有绝对的努力✊】 目录 1、接口测试分类 1.1 内部接口&#xff1a; 1.2 外部接口&#xff1a; 2、目前接口架构设计 2.1、基于SOAP架构&#xff0c; 2.2、基于RPC架构&#xff0c; 2.3、基于RestFul架构&#xff0c; 2.3.1…...

伏羲0.07(文生图)

为了使0.06代码能够有效运行并输出项目目录及所有文件&#xff0c;我们在代码中添加一些额外的功能。 项目目录结构 项目目录结构如下&#xff1a; text_to_image_project/ │ ├── config.yaml ├── data/ │ ├── train_data.csv │ └── test_data.txt ├── mod…...

scala的泛型特质的应用场景

//泛型特质的应用场景 //作比较找出最大值 //定义一个函数&#xff0c;用来求List元素中的最大值参考代码&#xff1a;object Test4 {def getMax[T](list:List[T])(implicit ev:T > Ordered[T]): T {list.reduce((a:T,b:T)> if(a>b) a else b)}def main(args: Array…...

Win10环境vscode+latex+中文快速配置

安装vscodelatex workshop 配置&#xff1a; {"liveServer.settings.donotVerifyTags": true,"liveServer.settings.donotShowInfoMsg": true,"explorer.confirmDelete": false,"files.autoSave": "afterDelay","exp…...

【vue2】el-select,虚拟滚动(vue-virtual-scroller)

需求背景​​​​​​ vue2+element-ui项目中,当el-select中数据量较大时(超出5000个dom节点),会导致页面加载和渲染卡顿、el-select下拉列表延迟展开。 在现在的el-select的基础上使用分页或者虚拟列表的形式去处理大量的下拉菜单,可以保证页面的正常渲染及el-select的…...

【ETCD】[源码阅读]深度解析 EtcdServer 的 processInternalRaftRequestOnce 方法

在分布式系统中&#xff0c;etcd 的一致性与高效性得益于其强大的 Raft 协议模块。而 processInternalRaftRequestOnce 是 etcd 服务器处理内部 Raft 请求的核心方法之一。本文将从源码角度解析这个方法的逻辑流程&#xff0c;帮助读者更好地理解 etcd 的内部实现。 方法源码 …...

【RabbitMQ】RabbitMQ中核心概念交换机(Exchange)、队列(Queue)和路由键(Routing Key)等详细介绍

博主介绍&#xff1a;✌全网粉丝21W&#xff0c;CSDN博客专家、Java领域优质创作者&#xff0c;掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域✌ 技术范围&#xff1a;SpringBoot、SpringCloud、Vue、SSM、HTML、Nodejs、Python、MySQL、PostgreSQL、大数据、物…...

【AI知识】过拟合、欠拟合和正则化

一句话总结&#xff1a; 过拟合和欠拟合是机器学习中的两个相对的概念&#xff0c;正则化是用于解决过拟合的方法。 1. 欠拟合&#xff1a; 指模型在训练数据上表现不佳&#xff0c;不能充分捕捉数据的潜在规律&#xff0c;导致在训练集和测试集上的误差都很高。欠拟合意味着模…...

计算机毕设-基于springboot的航空散货调度系统的设计与实现(附源码+lw+ppt+开题报告)

博主介绍&#xff1a;✌多个项目实战经验、多个大型网购商城开发经验、在某机构指导学员上千名、专注于本行业领域✌ 技术范围&#xff1a;Java实战项目、Python实战项目、微信小程序/安卓实战项目、爬虫大数据实战项目、Nodejs实战项目、PHP实战项目、.NET实战项目、Golang实战…...

视图、转发与重定向、静态资源处理

目录 视图 默认视图 视图机制原理 自定义视图 请求转发与重定向 静态资源处理 视图 每个视图解析器都实现了 Ordered 接口并开放出一个 order 属性 可以通过 order 属性指定解析器的优先顺序&#xff0c;order 越小优先级越高 默认是最低优先级&#xff0c;Integer.MAX_…...

优选算法——分治(快排)

1. 颜色分类 题目链接&#xff1a;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; 题目展示&#xff1a; 题目分析&#xff1a;本题其实就要将数组最终分成3块儿&#xff0c;这也是后面快排的优化思路&#xff0c;具体大家来看下图。 这里我们上来先定义了3个指针&…...

【Linux系统】文件系统

Windows 和 Linux 的文件系统&#xff1a; windows:NTFS —> NTFS&#xff1a;磁盘大于目录&#xff1a;目录是磁盘的一部分。ubuntu :EXT4 —> EXT4: 目录大于磁盘&#xff1a;磁盘是目录的一部分。 Windows文件系统的特点 基于分区的文件系统&#xff1a; Windows…...

郑州做网站大量网站被关/企业网页设计制作

圆圈舞蹈 [问题描述] 熊大妈的奶牛在时针的带领下&#xff0c;围成了一个圆圈跳舞。由于没有严格的教育&#xff0c;奶牛们之间的间隔不一致。 奶牛想知道两只最远的奶牛到底隔了多远。奶牛A到B的距离为A顺时针走和逆时针走&#xff0c;到达B的较短路程。告诉你相邻两个奶牛间的…...

福州官网建站厂/新闻最近新闻10条

一、关于异常的一些面试题 1、请说明throws和 throw的区别? 2、请说明Exception和 RuntimeException的区别和关系? 3、请说明Error和 Exception的区别和联系? 4、请说出五个常见的 RuntimeExcetion 5、请说明异常处理的流程? 1、请说明throws和 throw的区别? 答: throws:…...

做方案收集图片的网站/创网站永久免费建站

使用 RDF 摘要图的基于关键字的语义搜索方法论文原文&#xff1a;Using RDF Summary Graph For Keyword-based Semantic Searches1 介绍该文提出一个基于关键字的语义搜索框架&#xff0c;使用摘要图用于探索 RDF 数据并提供相关结果。对于高效的图探索&#xff0c;一个摘要图结…...

新一站保险网/放心网站推广优化咨询

接上文&#xff1a;http://blog.csdn.net/l241002209/article/details/72763774[mysqllocalhost MySQL-5.6.36-1.linux_glibc2.5.x86_64.rpm-bundle]$ mysql -uroot -p123456#设置远程访问#root是允许远程访问的账户名(可以是其他的)&#xff0c;"192.168.7.200"表示…...

怎么在虚拟空间做两个网站/企业网站有哪些功能

传送门 解题思路 首先给出的树形态没用&#xff0c;因为除根结点外每个点只有一个父亲&#xff0c;它只需要保证和父亲颜色不同即可。设\(f(k)\)表示至多染了\(k\)种颜色的方案&#xff0c;那么\(f(k)(k-1)^{(n-1)}*k\)&#xff0c;而我们要求的是恰好染\(k\)种颜色的方案数&am…...

成都免费建站/万网的app叫什么

&#x1f447;&#x1f447;关注后回复 “进群” &#xff0c;拉你进程序员交流群&#x1f447;&#x1f447;来源丨新智元https://mp.weixin.qq.com/s/IXpWSnbNczpACoJ8JVxEPQ新智元报道 编辑&#xff1a;袁榭 好困【新智元导读】苹果的产品发布会将近&#xff0c;从果粉顾客…...