Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁
订单ID必须是唯一
唯一ID构成:
代码生成唯一ID:
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.time.format.DateTimeFormatter;//基于redis自增长的生成策略
@Component
public class RedisUUID {//起始时间时间秒数private static final long BEGIN_TIMESTAMP=1640995200L;//使用Redis自增策略private StringRedisTemplate stringRedisTemplate;public RedisUUID(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}//参数是业务的类型public long nextid(String keyType){//1,生成时间戳LocalDateTime now = LocalDateTime.now();long nowsecend = now.toEpochSecond(ZoneOffset.UTC);//当前时间的秒数减去起始时间的秒数得到时间戳long nowtime_stamp = nowsecend - BEGIN_TIMESTAMP;//2,生成序列号String nowdate = now.format(DateTimeFormatter.ofPattern("yyyy:mm:dd"));//使得每天都会生成新的一轮IDlong count = stringRedisTemplate.opsForValue().increment("icr:" + keyType + ":" + nowdate);//3,拼接返回return nowtime_stamp << 32 | count;}
}
商品下单操作
业务逻辑:
思路:主要是要了解以及掌握整个业务的流程:①先看商品是不是在秒杀的时间范围内②然后还要去看库存中是否还有该商品③如果有的话就扣减库存④然后就会生成订单,订单ID为唯一ID⑤把订单写入数据库中,再返回数据给前端
代码实现:
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService iSeckillVoucherService;@Autowiredprivate RedisUUID redisUUID;@Override@Transactionalpublic Result seckillVoucher(Long voucherId) {//1,查询商品的信息SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);//2,看是否在秒杀时间范围内if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("尚未开始!");}if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("已经结束啦!");}//3,再看库存是否还有if (voucher.getStock()<1) {return Result.fail("库存不足!");}//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).update();if (!sucess) {return Result.fail("库存不足!");}//5,然后就创建订单信息VoucherOrder voucherOrder = new VoucherOrder();//5.1,订单id----id生成器long order = redisUUID.nextid("order");voucherOrder.setVoucherId(order);//5.2,用户idLong id = UserHolder.getUser().getId();voucherOrder.setUserId(id);//5.3,商品idvoucherOrder.setVoucherId(voucherId);//6,保存进数据库save(voucherOrder);//7,返回数据return Result.ok(order);}
}
库存超卖问题
先看看什么是库存超卖问题:
正常情况:
但是涉及到高并发的时候一定会出问题:
所以我们要想办法去解决这个问题,锁!!!
悲观锁认为一定发生并发问题,所以每一次操作都会加锁,是线程串行进行,不会出现并发问题,但是这样的话就导致性能降低,所以我们使用乐观锁,乐观锁是先让你操作,等你要修改数据库的时候再判断与你查到的数据是否是一样,如果是一样的才可以修改,否则不可以减库存。
乐观锁的两种实现判断法:
第一种:版本号法,就是通过查询两次版本号来判断是否被修改过库存
第二种:CAS法,是在版本号法上做的改进方法,既然要判断两次版本是否相同,为啥不判断库存量是否相同呢,所以CSA法就是去判断前后两次查询到的库存量是否一样,如果一样就可以改
用乐观锁CAS法来解决超卖问题:
//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).eq("stock",voucher.getStock()).update();if (!sucess) {return Result.fail("库存不足!");}
但是这样任然还不能解决超卖问题,因为如果两个线程同时来查到100,线程1做完修改还剩99,线程2查到不是100就会不执行修改,这样也会有问题,所以又要进行改进策略
//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if (!sucess) {return Result.fail("库存不足!");}
一人一单问题
使用悲观锁处理单体服务下的多线程问题:
@Service
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {@Autowiredprivate ISeckillVoucherService iSeckillVoucherService;@Autowiredprivate RedisUUID redisUUID;@Overridepublic Result seckillVoucher(Long voucherId) {//1,查询商品的信息SeckillVoucher voucher = iSeckillVoucherService.getById(voucherId);//2,看是否在秒杀时间范围内if (voucher.getBeginTime().isAfter(LocalDateTime.now())) {return Result.fail("尚未开始!");}if (voucher.getEndTime().isBefore(LocalDateTime.now())) {return Result.fail("已经结束啦!");}//3,再看库存是否还有if (voucher.getStock()<1) {return Result.fail("库存不足!");}//实现单体服务下的一人一单的多线程安全问题Long id = UserHolder.getUser().getId();//先获取锁,再提交事务,保证线程安全synchronized (id.toString().intern()){//获得Spring的代理对象(事务)IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);}}@Transactionalpublic Result createVoucherOrder(Long voucherId) {//一人一单问题Long id = UserHolder.getUser().getId();Integer count = query().eq("user_id", id).eq("voucher_id", voucherId).count();if(count > 0){return Result.fail("你已经购买过!");}//4,如果有就减扣库存boolean sucess = iSeckillVoucherService.update().setSql("stock = stock - 1").eq("voucher_id", voucherId).gt("stock",0).update();if (!sucess) {return Result.fail("库存不足!");}//5,然后就创建订单信息VoucherOrder voucherOrder = new VoucherOrder();//5.1,订单id----id生成器long order = redisUUID.nextid("order");voucherOrder.setVoucherId(order);//5.2,用户idvoucherOrder.setUserId(id);//5.3,商品idvoucherOrder.setVoucherId(voucherId);//6,保存进数据库save(voucherOrder);//7,返回数据return Result.ok(order);}
}
添加依赖:
<dependency><groupId>org.aspectj</groupId><artifactId>aspectjweaver</artifactId></dependency>
在启动类上添加:
@EnableAspectJAutoProxy(exposeProxy = true)
分布式集群模式下的多线程问题:
当我们是处理分布式集群模式下,两个JVM不是共用一把锁,导致每个JVM都有自己的锁导致我们之前的锁锁不住,每个JVM都有一个线程会获得锁。
分布式锁:满足分布式系统或者集群模式下多进程可见并且互斥的锁
基于Redis实现分布式锁:
创建锁对象:
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;private static final String KEY_PREFXY="lock:";public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean trylock(long timeoutSec) {//获取线程ID作为标识long ThreadId = Thread.currentThread().getId();//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFXY + name, ThreadId + "", timeoutSec, TimeUnit.MINUTES);//避免空指针return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {stringRedisTemplate.delete(KEY_PREFXY + name);}
}
代码实现Redis分布式锁的应用:
①先创建锁的对象,然后先是去获取锁②没有获取到锁就直接返回错误③获取到锁就可以进行对数据库的操作④操作完之后进行释放锁
Long id = UserHolder.getUser().getId();//创建锁对象SimpleRedisLock simpleRedisLock = new SimpleRedisLock("order" + id, stringRedisTemplate);//获取锁boolean trylock = simpleRedisLock.trylock(1200);//判断是否获得锁成功if (!trylock) {//获取锁失败return Result.fail("不允许重复下单!");}//获得Spring的代理对象(事务)try {IVoucherOrderService proxy = (IVoucherOrderService) AopContext.currentProxy();return proxy.createVoucherOrder(voucherId);} finally {//释放锁simpleRedisLock.unlock();}
但是就上面的处理还不够严谨,因为如果一个线程发生阻塞的话,其他线程可能会获得锁并且释放锁,导致锁误删问题,
解决锁误删问题:
import cn.hutool.core.lang.UUID;
import org.springframework.data.redis.core.StringRedisTemplate;
import java.util.concurrent.TimeUnit;public class SimpleRedisLock implements ILock{private String name;private StringRedisTemplate stringRedisTemplate;private static final String KEY_PREFXY="lock:";//得到一个唯一锁的标识private static final String ID_PREFXY= UUID.randomUUID(true)+"-";public SimpleRedisLock(String name, StringRedisTemplate stringRedisTemplate) {this.name = name;this.stringRedisTemplate = stringRedisTemplate;}@Overridepublic boolean trylock(long timeoutSec) {//获取线程标识String ThreadId = ID_PREFXY+Thread.currentThread().getId();//获取锁Boolean success = stringRedisTemplate.opsForValue().setIfAbsent(KEY_PREFXY + name, ThreadId, timeoutSec, TimeUnit.MINUTES);//避免空指针return Boolean.TRUE.equals(success);}@Overridepublic void unlock() {//获取线程标识String ThreadId = ID_PREFXY+Thread.currentThread().getId();//判断要来修改的进程跟锁的标识是否一致String s = stringRedisTemplate.opsForValue().get(KEY_PREFXY + name);if(ThreadId.equals(s)){//释放锁stringRedisTemplate.delete(KEY_PREFXY + name);}}
}
相关文章:

Redis---------实现商品秒杀业务,包括唯一ID,超卖问题,分布式锁
订单ID必须是唯一 唯一ID构成: 代码生成唯一ID: import org.springframework.data.redis.core.StringRedisTemplate; import org.springframework.stereotype.Component; import java.time.LocalDateTime; import java.time.ZoneOffset; import java.tim…...

C++之QT文本处理QDir、QFileDialog、QStringList、QFile
一、相应的头文件 #include <QFileDialog> #include <QDir> #include <QStringList> 二、简介 1.QFileDialog 实际效果如下:比如需要选择打开的文件夹或者文件名,通过调用资源管理器的方式进行可视化操作。 代码示例为:…...

24.5.8数据结构|单向循环链表
一、理解原理: 初始状态: 1、对比前两种的不同之处 1)保存到栈空间(局部变量)。静态初始化。 2) 二、代码实现 1、initLinkLoop函数 疑问: 1、地址怎么处理? 注意࿱…...

2024年,抖音小店开通需要多少钱?一篇详解!
大家好,我是电商糖果 2024年了,想在抖音开店卖货的朋友越来越多。 主要原因还是看到,这几年在抖音上赚到钱的人越来越多。 于是大家在今年比较关心的问题,就是抖音小店开通需要多少钱? 糖果做抖音小店四年了&#…...

2023年全国职业院校技能大赛(高职组)“云计算应用”赛项赛卷1(私有云)
#需要资源(软件包及镜像)或有问题的,可私聊博主!!! #需要资源(软件包及镜像)或有问题的,可私聊博主!!! #需要资源(软件包…...

Python数据可视化------地图
基础地图使用 # 地图基本演示 # 导包 from pyecharts.charts import Map from pyecharts.options import TitleOpts, VisualMapOpts# 准备地图对象 cmap Map() # 准备数据(列表) data [("北京市", 99), ("上海市", 199), ("…...

Rust中的并发性:Sync 和 Send Traits
在并发的世界中,最常见的并发安全问题就是数据竞争,也就是两个线程同时对一个变量进行读写操作。但当你在 Safe Rust 中写出有数据竞争的代码时,编译器会直接拒绝编译。那么它是靠什么魔法做到的呢? 这就不得不谈 Send 和 Sync 这…...

|Python新手小白中级教程|第二十七章:面向对象编程(示例操作)(3)使用turtle库与类结合
文章目录 前言一、项目:使用类Circle画出圆形(不调用turtle库)1.基础指令class2.使用turtle画出大圆与小圆3.使用其他功能画一只眼睛 二、使用turtle库画正方形总结 前言 hello,我是BoBo仔,welcome来看我的文章 这节课…...

Android OpenMAX(五)高通OMX Core实现
上一节了解了OMX Core提供的内容,这一节我们看看高通OMX Core是如何实现的。本节代码参考自: omx_core_cmp.cpp registry_table_android.c qc_omx_core.h 1、OMX_Init/OMX_Deinit OMX_API OMX_ERRORTYPE OMX_APIENTRY OMX_Init() {DEBUG_PRINT(...

XXE漏洞
一、概述 1、XXE:XML外部实体注入攻击 2、XML:可扩展标记语言。 (1)没有固定标签,所有标签都可以自定义,但有限制规则。 (2)用于数据对的传输与存储,常被用于充当配置文件 推荐教程:XML 教程 (3)后缀…...

[华为OD]C卷 BFS 亲子游戏 200
题目: 宝宝和妈妈参加亲子游戏,在一个二维矩阵(N*N)的格子地图上,宝宝和妈妈抽签决定各自 的位置,地图上每个格子有不同的Q糖果数量,部分格子有障碍物。 游戏规则Q是妈妈必须在最短的时间&a…...

大模型微调实战之强化学习 贝尔曼方程及价值函数(五)
大模型微调实战之强化学习 贝尔曼方程及价值函数(五) 现在, 看一下状态-动作值函数的示意图: 这个图表示假设首先采取一些行动(a)。因此,由于动作(a),代理可能会被环境转换到这些状…...

初探MFC程序混合使用QT
一、背景 随着操作系统国产化替代的趋势越发明显,软件支持国际化、跨平台,已然是必须做的一件事情。原有的软件UI层用的是MFC,将其换成QT,想必是一种较好的方案。对于大型软件,特别是已发布,但还处于不断迭…...

【LeetCode题库】1068. 产品销售分析 I —— MySQL 性能提升,using()关键字
文章目录 原题题解解题笔记 —— JOIN USING()关键字对性能的提升 我是一名立志把细节都说清楚的博主,欢迎【关注】🎉 ~ 原创不易, 如果有帮助 ,记得【点赞】【收藏】 哦~ ❥(^_-)~ 如有错误、疑惑,欢迎【评论】指正…...

leetcode 1 ~ 100
文章目录 1. 两数之和(用哈希表减少查找的时间复杂度)2. 两数相加(高精度加法)3.无重复字符的最长子串:(模板:经典的滑动窗口算法)5. 最长回文子串(枚举)6. Z…...

分享6个免费下载电子书的网站
着急看书的宝子们看这里! 收藏了一堆电子书网站终于能派上用场了~ 01/Z-Library https://zh.zlibrary-be.se/ 世界上最大的电子图书馆,拥有超千万的书籍和文章资源,99%的书籍资料都能在这里找到。 我给的这个网址现在还能正常打开使用&…...

typescript的入门到吐槽:看了typescript,发现前端真的卷,
typescript TypeScript 是一种由微软开发的自由和开源的编程语言。它是 JavaScript 的一个超集,而且本质上向这个语言添加了可选的静态类型和基于类的面向对象编程。 TypeScript 与 JavaScript 的区别 其实就是对JavaScript的封装,把一个弱类型语言封…...

抖店商品详情API接口,商品上架(主图,价格,sku等属性,)item_get-获得抖店商品详情
抖店商品详情API接口,商品上架(主图,价格,sku等属性,)item_get-获得抖店商品详情 {"code": 0,"msg": "调用成功","time": "1715166889","data&quo…...

STM32使用ADC单/多通道检测数据
文章目录 1. STM32单片机ADC功能详解 2. AD单通道 2.1 初始化 2.2 ADC.c 2.3 ADC.h 2.4 main.c 3. AD多通道 3.1 ADC.c 3.2 ADC.h 3.3 main.c 3.4 完整工程文件 1. STM32单片机ADC功能详解 STM32单片机ADC功能详解 2. AD单通道 这个代码实现通过ADC功能采集三脚电…...

Unity 性能优化之动态批处理(四)
提示:仅供参考,有误之处,麻烦大佬指出,不胜感激! 文章目录 前言一、动态合批是什么?二、使用动态批处理1.打开动态合批2.满足条件 三、检查动态合批是否成功五、动态合批弊端总结 前言 动态批处理是常用优…...

Windows 11 系统安装时如何跳过联网和逃避微软账号登录
问题描述 Windows 11 是从 22H2 版本之后开始强制联网何登录微软账号的。 这就带来两个问题: 1、如果我的电脑没有网络或者网卡驱动有问题,那就无法继续安装系统了。 2、如果我有强怕症,就是不想登录微软账号,害怕个人信息泄露…...

uniapp + vue3 使用axios
场景 uniapp自带的uni.request不太好用,也有可能是自己用axios用的太熟悉了,所以还是用axios趁手点,所以尝试在uniapp中使用axios。 操作 因为uniapp项目没有package.json,所以先在项目根目录下执行 npm init, 执行完毕后直接…...

关于前后端的参数传递
以前端javascript,后端nodejsexpress为例,后端可以从前端发来的request里获取这些属性:header、query、url、params、body,其中params和query是从url中解析获得,不过express已帮我们做好了,就不用我们自己再…...

华火电焰灶,科技打造“新”厨房
家里最大的空气污染源其实来自厨房里的燃气灶!——斯坦福大学发表的《科学进展》期刊 厨房在家庭中占有举足轻重的地位,它不仅是一个烹饪美食的场所,更是家人情感交流的重要空间。厨房大致经历了两次变革,分别是以柴火灶为主体的厨…...

普通人副业要趁早,5种靠谱且持久的赚钱副业
中年危机、35岁被裁,这些听起来就让人焦虑的词汇,是否也让你感到不安?别担心,只要你早早开启副业之旅,这些都不是问题。 今天,我要为你介绍的这5种副业,不仅能帮你赚钱,还能让你的能…...

【文献解析】3D高斯抛雪球是个什么玩意
论文地址:https://arxiv.org/abs/2308.04079 项目:3D Gaussian Splatting for Real-Time Radiance Field Rendering 代码:git clone https://github.com/graphdeco-inria/gaussian-splatting --recursive 一、文章概述 1.1问题导向 辐射…...

嘎嘎好用的虚拟键盘第二弹之中文输入法
之前还在为不用研究输入中文而暗自窃喜 这不新需求就来了(新需求不会迟到 它只是在路上飞一会儿) 找到了个博主分享的代码 是好使的 前端-xyq 已经和原作者申请转载了 感谢~~ 原作者地址:https://www.cnblogs.com/linjiangxian/p/16223681.h…...

vue3使用el-autocomplete请求远程数据
服务器端 RestController RequestMapping("/teacher") public class TeacherController {Resourceprivate TeacherService teacherService;GetMapping({"/v1/getTop10TeacherByName/","/v1/getTop10TeacherByName/{name}"})public ResultBean&l…...

【学习笔记】HarmonyOS 4.0 鸿蒙Next 应用开发--安装开发环境
开发前的准备 首先先到官网去下载Devco Studio 这个开发工具,https://developer.harmonyos.com/cn/develop/deveco-studio/#download 提供了WIndows和Mac的开发环境,我自己是Windows的开发环境。 所以下载之后直接点击exe进行安装即可。 如果之前安装过…...

【PHP】计算两个时间戳之间相差的时间
目录 一、span方法概述 二、输出格式解析 三、方法执行流程 四、应用场景 五、注意事项 六、完整代码示例 时间跨度计算:PHP中的span方法解析 在Web开发中,我们经常需要对时间进行各种计算,尤其是在用户界面中展示时间差或倒计时等功能…...