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

2024-04-27 12:52

本文主要是介绍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 - +


 

这篇关于redis 结合Lua脚本实现 秒杀、防止超卖的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/940555

相关文章

Redis分片集群的实现

《Redis分片集群的实现》Redis分片集群是一种将Redis数据库分散到多个节点上的方式,以提供更高的性能和可伸缩性,本文主要介绍了Redis分片集群的实现,具有一定的参考价值,感兴趣的可以了解一... 目录1. Redis Cluster的核心概念哈希槽(Hash Slots)主从复制与故障转移2.

springboot+dubbo实现时间轮算法

《springboot+dubbo实现时间轮算法》时间轮是一种高效利用线程资源进行批量化调度的算法,本文主要介绍了springboot+dubbo实现时间轮算法,文中通过示例代码介绍的非常详细,对大家... 目录前言一、参数说明二、具体实现1、HashedwheelTimer2、createWheel3、n

使用Python实现一键隐藏屏幕并锁定输入

《使用Python实现一键隐藏屏幕并锁定输入》本文主要介绍了使用Python编写一个一键隐藏屏幕并锁定输入的黑科技程序,能够在指定热键触发后立即遮挡屏幕,并禁止一切键盘鼠标输入,这样就再也不用担心自己... 目录1. 概述2. 功能亮点3.代码实现4.使用方法5. 展示效果6. 代码优化与拓展7. 总结1.

Mybatis 传参与排序模糊查询功能实现

《Mybatis传参与排序模糊查询功能实现》:本文主要介绍Mybatis传参与排序模糊查询功能实现,本文通过实例代码给大家介绍的非常详细,感兴趣的朋友跟随小编一起看看吧... 目录一、#{ }和${ }传参的区别二、排序三、like查询四、数据库连接池五、mysql 开发企业规范一、#{ }和${ }传参的

Docker镜像修改hosts及dockerfile修改hosts文件的实现方式

《Docker镜像修改hosts及dockerfile修改hosts文件的实现方式》:本文主要介绍Docker镜像修改hosts及dockerfile修改hosts文件的实现方式,具有很好的参考价... 目录docker镜像修改hosts及dockerfile修改hosts文件准备 dockerfile 文

基于SpringBoot+Mybatis实现Mysql分表

《基于SpringBoot+Mybatis实现Mysql分表》这篇文章主要为大家详细介绍了基于SpringBoot+Mybatis实现Mysql分表的相关知识,文中的示例代码讲解详细,感兴趣的小伙伴可... 目录基本思路定义注解创建ThreadLocal创建拦截器业务处理基本思路1.根据创建时间字段按年进

微信公众号脚本-获取热搜自动新建草稿并发布文章

《微信公众号脚本-获取热搜自动新建草稿并发布文章》本来想写一个自动化发布微信公众号的小绿书的脚本,但是微信公众号官网没有小绿书的接口,那就写一个获取热搜微信普通文章的脚本吧,:本文主要介绍微信公众... 目录介绍思路前期准备环境要求获取接口token获取热搜获取热搜数据下载热搜图片给图片加上标题文字上传图片

SpringBoot3实现Gzip压缩优化的技术指南

《SpringBoot3实现Gzip压缩优化的技术指南》随着Web应用的用户量和数据量增加,网络带宽和页面加载速度逐渐成为瓶颈,为了减少数据传输量,提高用户体验,我们可以使用Gzip压缩HTTP响应,... 目录1、简述2、配置2.1 添加依赖2.2 配置 Gzip 压缩3、服务端应用4、前端应用4.1 N

SpringBoot实现数据库读写分离的3种方法小结

《SpringBoot实现数据库读写分离的3种方法小结》为了提高系统的读写性能和可用性,读写分离是一种经典的数据库架构模式,在SpringBoot应用中,有多种方式可以实现数据库读写分离,本文将介绍三... 目录一、数据库读写分离概述二、方案一:基于AbstractRoutingDataSource实现动态

Python FastAPI+Celery+RabbitMQ实现分布式图片水印处理系统

《PythonFastAPI+Celery+RabbitMQ实现分布式图片水印处理系统》这篇文章主要为大家详细介绍了PythonFastAPI如何结合Celery以及RabbitMQ实现简单的分布式... 实现思路FastAPI 服务器Celery 任务队列RabbitMQ 作为消息代理定时任务处理完整