redis 结合Lua脚本实现 秒杀、防止超卖

需求:
同1商品单个用户限购1件,库存不会超卖

1 Lua脚本,因可实现原子性操作,这个文件放到resources目录下

local userId = KEYS[1] -- 当前秒杀的用户 ID
local goodsId = KEYS[2] -- 秒杀的商品 ID
-- 订单id
local orderId = ARGV[1]redis.log(redis.LOG_NOTICE,"秒杀商品ID:‘"..goodsId.."’,当前秒杀用户 ID:‘"..userId.."’") -- 日志记录-- 使用一个统一的前缀来存储所有商品的库存信息
local stockHashKey = "Seckill:Stock" -- 秒杀商品的库存哈希KEY-- 如果一个用户已经参加过秒杀了,那么不应该重复参加
-- 所有的秒杀的商品一定要保存在 SET 集合(用户 ID 不能重复)
local resultKey = "Seckill:Result:"..goodsId
local resultExists = redis.call('SISMEMBER', resultKey, userId)
redis.log(redis.LOG_NOTICE,"【"..userId.."-"..goodsId.."】当前用户参加秒杀的状态:"..resultExists)if tonumber(resultExists) == 1 thenreturn -1 -- 用户参加过秒杀了
else-- 获取当前商品库存数量,使用HGET命令从哈希表中获取local goodsCount = redis.call('HGET', stockHashKey, goodsId)if goodsCount == false thengoodsCount = 0 -- 如果没有这个字段,默认库存为0endredis.log(redis.LOG_NOTICE,"【"..userId.."-"..goodsId.."】当前商品库存量:"..goodsCount)if tonumber(goodsCount) <= 0 then -- 商品抢光了redis.log(redis.LOG_NOTICE,"【"..userId.."-"..goodsId.."】用户秒杀失败。")return 0else -- 还有库存-- 更新库存数量,使用HINCRBY命令减少库存redis.call('HINCRBY', stockHashKey, goodsId, -1)-- 秒杀结果记录redis.call('SADD', resultKey, userId)-- 发送一条消息到stream队列中redis.call('xadd', 'Seckill:orders_queue', '*', 'userId', userId, 'goodsId',goodsId,'orderId', orderId)redis.log(redis.LOG_NOTICE,"【"..userId.."-"..goodsId .."】用户秒杀成功。")return 1end
end

2 写个配置类,读取Lua脚本

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;@Configuration
public class LuaConfiguration {@Bean(value = "seckill_stockScript")public DefaultRedisScript<Long> miaosha_stockScript() {//脚本的范型返回值是LongDefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();//放在resources目录下redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("sha_2.lua")));redisScript.setResultType(Long.class);return redisScript;}
}

3 主程序逻辑

import lombok.extern.slf4j.Slf4j;
import org.example.service_a.service_a_App;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import java.util.Arrays;
import java.util.List;@SpringBootTest(classes = {seckillService.class})
@Slf4j
public class Test_2_lua {@Autowiredprivate StringRedisTemplate stringRedisTemplate;@Qualifier(value = "seckill_stockScript")@Autowiredprivate DefaultRedisScript<Long> redisScript;/*** 这里没有写模拟压测的,只写了核心调用逻辑* 可以写个线程池批量压,或是jmeter压*/@Testpublic void executeLuaScriptFromFile() {/*** 准备Lua脚本所需参数,如 KEYS,ARGV* 这个脚本 KEYS 为 用户id,商品id* ARGV 为 订单id* 这些通常是键名,用于在脚本中作为变量使用*/List<String> keys = Arrays.asList("user_1", "goodsId_1");// hutool 工具类雪花算法,默认调用的是getSnowflake()方法,生成id// createSnowflake(long workerId, long datacenterId) 方法,是每次调用都会创建一个新的Snowflake对象,不同的Snowflake对象创建的ID可能会有重复,不推荐String orderId = IdUtil.getSnowflakeNextIdStr();//脚本里返回1说明秒杀成功Long execute = stringRedisTemplate.execute(redisScript, keys,orderId );}
}

4 redis准备测试数据,这里模拟了些数据

#用hash结构存储商品id和库存数量,默认10个库存
hset Seckill:Stock goodsId_1 10
hset Seckill:Stock goodsId_2 10
hset Seckill:Stock goodsId_3 10#用set结构存储某个商品id中,秒杀成功的用户id列表
del Seckill:Result:goodsId_1
del Seckill:Result:goodsId_2
del Seckill:Result:goodsId_3# stream类型的队列,有产品id,用户id,订单id
del Seckill:orders_queuekeys *hget Seckill:Stock goodsId_1
hget Seckill:Stock goodsId_2
hget Seckill:Stock goodsId_3# 查看stream队列中的消息
xrange Seckill:orders_queue - +


 

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

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

相关文章

微信收款码0.2费率开通

很多人想申请低手续费率的收款码不知从何下手&#xff0c;在参考了大量博客教学之后&#xff0c;终于搞懂了详细流程以及注意事项。在此记录一下。我申请的是一个只需要0.2%费率的微信收款码&#xff0c;申请时间是2022年2月12日。申请之前只需要准备营业执照和法人身份z&#…

【iconv】Linux c++ 中文字符串转十六进制 GBK 编码/内码

文章目录 问题描述c 代码CMakeLists.txt参考链接 问题描述 Linux 系统默认使用的是 UTF-8 编码&#xff0c;并且 c 中没有标准库可以直接将中文字符转为 GBK 编码/内码。因此需要借助 iconv 库来实现。 在实现代码之前&#xff0c;可以在一下在线工具网站进行中文字符到各个编…

​可视化大屏C位图:园区鸟瞰

将园区鸟瞰图作为可视化大屏设计的焦点图有以下几个好处&#xff1a; 提供全局视图&#xff1a;园区鸟瞰图可以展示整个园区的布局和结构&#xff0c;提供全局视图。这对于大型园区或复杂的场所来说尤为重要&#xff0c;用户可以一目了然地了解整个园区的规模、分布和关联关系…

使用新版ESLint,搭配Prettier使用的配置方式

概述 ESLint重大更新(9.0.0版本)后,将不再支持非扁平化配置文件,并且移除了与Prettier冲突的规则,也就是说与Prettier搭配使用,不再需要使用插件“eslint-config-prettier”来处理冲突问题。 注:使用新版的前提条件是Node.js版本必须是18.18.0、20.9.0,或者是>=21.1…

ESP-IDF编译系统详解(1)

接前一篇文章&#xff1a;VSCode ESP-IDF安装与配置全过程 本文内容主要参考&#xff1a; 《ESP32-C3物联网工程开发实战》 —— 乐鑫科技 编著 特此致谢&#xff01; 前文已经详述了ESP-IDF开发环境的搭建&#xff0c;包括ESP-IDF的下载与安装完整流程&#xff0c;以及VSCo…

怎么排查K8S容器当中的Java程序内存泄露问题

今天早上发现生产线其中的一个服务在凌晨的时候突然重启了&#xff0c;内存突然从1G升到1.8G&#xff0c;CPU使用量从0.1升到了0.28&#xff0c;说明在这个时间点&#xff0c;内存突增达到了限额以上&#xff0c;服务重启了。因为这个服务布署了多节点&#xff0c;这次重启对业…

故障诊断 | 基于GASF-CNN的状态识别研究

概述 抗蛇行减振器作为高速动车组二系悬挂系统的关键零部件,对改善车辆运动稳定性、提高车辆系统的临界速度具有重要意义。抗蛇行减振器在高级修时需全部进行拆解维修或报废处理,若在高级修中的三、四级修时其性能尚能够满足实际使用要求,将其过早地拆解检修或者报废换新无…

【springboot整合redis】异常处理

这个问题是在使用springboot整合redis时&#xff0c;创建好工程后&#xff0c;测试时所产生的 报错&#xff1a; org.springframework.data.redis.RedisSystemException: Error in execution; nested exception is io.lettuce.core.RedisCommandExecutionException: NOAUTH A…

共享购:融合社交分享与消费返利的创新电商模式

共享购电商模式是一种独特的商业模式&#xff0c;巧妙地将社交分享与消费返利结合&#xff0c;让消费者在购物的同时&#xff0c;也能通过平台资产奖励实现价值的双重增长。该平台资产体系主要由共享值和共享积分两大要素构成&#xff0c;共同构建了一个充满活力的电商生态系统…

树莓派的应用场景都有哪些?

树莓派是一种小型、低功耗、高性能的计算机主板&#xff0c;其广泛的应用场景包括但不限于以下几个方面&#xff1a; 媒体中心&#xff1a;树莓派可以连接到电视或音响系统&#xff0c;并安装如Kodi等媒体播放器软件&#xff0c;成为一个功能完整的家庭媒体中心&#xff0c;用…

部署一个自己的GPT客户端[以ChatGPT-Next-Web为例]

1. 引言 当我们有一个openai的key又想通过客户端进行访问对话功能的时候&#xff0c;chatGPT-next-web是一个选项&#xff08;仅限是一个选项&#xff0c;也有更好的方案&#xff09;。 2. 准备步骤 服务器背景&#xff1a; Ubuntu 20.04 2.1 docker的安装 首先应该保证服…

如何在three.js中画3D圆弧及半圆弧组成圆

在three.js中画圆弧以及画圆&#xff0c;首先会想到的是ArcCurve&#xff0c;这个曲线API&#xff0c;经过使用发现&#xff0c;他是一个二维平面的&#xff0c;也就是说只在X-Y轴组成的平面可以使用&#xff0c;三维坐标使用的时候不生效&#xff0c;比如说&#xff1a;我期望…

巴特沃斯滤波原理及代码实现(matlab详细过程版)

目录 一、算法原理1、原理概述2、参考文献 二、代码实现三、结果展示 本文由CSDN点云侠原创&#xff0c;原文链接。如果你不是在点云侠的博客中看到该文章&#xff0c;那么此处便是不要脸的爬虫与GPT。 一、算法原理 1、原理概述 巴特沃斯滤波器&#xff08;Butterworth filt…

SSH功能及其在网络通信中的应用

SSH功能及其在网络通信中的应用 摘要&#xff1a; SSH&#xff08;Secure Shell&#xff09;是一种网络协议&#xff0c;用于在不安全的网络中提供加密的远程登录和其他网络服务。本文将详细介绍SSH的基本概念、工作原理、常用功能以及在网络通信中的应用。通过阅读本文&#…

SQLite运行时可加载扩展(三十五)

返回&#xff1a;SQLite—系列文章目录 上一篇:SQLite轻量级会话扩展&#xff08;三十四&#xff09; 下一篇:SQLite的DBSTAT 虚拟表&#xff08;三十六) 1. 概述 SQLite 能够在运行时加载扩展&#xff08;包括新的应用程序定义的 SQL 函数、整理序列、虚拟表和 VFS&…

商城数据库88张表结构(十三)

DDL 49.订单ID表 CREATE TABLE wang_orderids (id bigint(11) NOT NULL AUTO_INCREMENT COMMENT 自增ID,rnd float(16,2) NOT NULL COMMENT 毫秒数,PRIMARY KEY (id) ) ENGINEInnoDB DEFAULT CHARSETutf8 COMMENT王——订单ID表; 50.订单表 CREATE TABLE wang_orders (orde…

Mysql-主从复制理解

环境&#xff1a;mysql&#xff0c;主从复制&#xff0c;必须有2个mysql实例&#xff0c;也就是说可以在一台电脑上安装2个msyql&#xff0c;或者2台服务器&#xff0c;一个主服务器&#xff0c;一个从服务器 在实际的生产中&#xff0c;为了解决Mysql的单点故障已经提高MySQL的…

Tomcat安装和配置以及多实例部署(附脚本)

TOMCAT详细部署 Tomcat服务器简介核心组件Tomcat 各组件及关系工作流程 Tomcat server.xml 配置详解serverserviceConnectorEngineHostContextValve 阀门 Tomcat部署与安装部署脚本主要目录说明 Tomcat多实例部署扩展和优化 Tomcat 的 catalina.sh 文件以调整 JVM 参数 Tomcat服…

前端工程化Vue使用Node.js设置国内高速npm镜像源(踩坑记录版)

前端工程化Vue使用Node.js设置国内高速npm镜像源&#xff08;踩坑记录版&#xff09; 此篇仅为踩坑记录&#xff0c;并未成功更换高速镜像源&#xff0c;实际解决方法见文末跳转链接。 1.自身源镜像 自身镜像源创建Vue项目下载速度感人 2.更改镜像源 2.1 通过命令行配置 前提…

K8s容器部署maven项目

最近在整一整套devops自动化持续集成的东西&#xff0c;一开始就做好了踩坑的准备。 failed to verify certificate: x509: certificate signed by unknown authority 今天在执行kubectl get nodes的时候报的证书验证问题&#xff0c;看了一圈首次搭建k8s的都是高频出现的问题…