redis rua解决库存问题_如何解决高并发下的库存安全问题,没你想得那么复杂(附源码)...

一、 问题

不知道大家该开发中有没有遇到这样的一个问题,在电影院购票或者去网上买东西的时候,比方说当年哪吒大电影出来的时候,那抢票相当火爆啊,一票难求,那购票系统的后台是如何保证观众能买到自己喜欢的票同时不用担心会被别人抢走呢?还有一个典型的例子,每年双十一的疯狂的时候,准时抢购的时候,同一时间内会有大量的订单涌入,先不说负载的事情,那么后面再说,我们来说一下,他是如何保证库存安全的呢?不会出现已经没有库存但是依旧在页面上显示有库存呢?

a05a5e9036cdf89086e4e66dbb0f4e03.png

那我们首先要解决的就是在用户准备下单以及取消订单的时候,我们后台库存都发生了什么

二、 下单的步骤

(1)下单

(2)下单同时预占库存

(3)支付

(4)支付成功真正减扣库存

(5)取消订单

(6)回退预占库存

那这样在下单的过程中,我们中间对于库存有一个合理的要求:预占。就是说当顾客已经选定了商品,这个时候会从库存中暂时性的去去除掉,为顾客保留一个名额,那什么时候会产生这样的问题呢?

三、 什么时候进行预占库存?

(1)方案一:加入购物车的时候去预占库存

(2)方案二:下单的时候去预占库存

(3)方案三:支付的时候去预占库存

那知道了会产生问题的地方,那接下来是不是就该写“BUG”了啊

四、 分析

(1)方案一:加入购物车并不代表用户一定会购买,如果这个时候开始预占库存,会导致想购买的无法加入购物车。而不想购买的人一直占用库存。显然这种做法是不可取的。

(2)方案二:商品加入购物车后,选择下单,这个时候去预占库存。用户选择去支付说明了,用户购买欲望是比 方案一 要强烈的。订单也有一个时效,例如半个小时。超过半个小时后,系统自动取消订单,回退预占库存。

(3)方案三:下单成功去支付的时候去预占库存。只有100个用户能支付成功,900个用户支付失败。用户体验不好,就像你走了一条光明大道,一路通畅,突然被告知此处不通行。而且支付流程也是一个比较复杂的流程,如果和减库存放在一起,将会变得更复杂。

所以综上所述:选择方案二比较合理。

五、 重复下单问题

(1)用户点击过快,重复提交两次

(2)网络延时,用户刷新或者点击下单重复提交

(3)网络框架重复请求,某些网络框架,在延时比较高的情况下会自动重复请求

(4)用户恶意行为

六、 解决办法

在UI拦截,点击后按钮置灰,不能继续点击,防止用户,连续点击造成的重复下单。

1、在下单前获取一个下单的唯一token,下单的时候需要这个token。后台系统校验这个 token是否有效,才继续进行下单操作。

/**     * 先生成 token 保存到 Redis     * token 作为 key , 并设置过期时间 时间长度 根据任务需求     * value 为数字 自增判断 是否使用过      *     * @param user     * @return   */public String createToken(User user) {  String key = "placeOrder:token:" + user.getId();  String token = UUID.randomUUID().toString();  //保存到Redis  redisService.set(key + token, 0, 1000L); return token; }/**     * 校验下单的token是否有效     * @param user     * @param token     * @return     */public Boolean checkToken(User user, String token) {  String key = "placeOrder:token:" + user.getId();  if (null != redisService.get(key + token)) {    long times = redisService.increment(key + token, 1);    if (times == 1) {      //利用increment 原子性 判断是否 该token 是否使用      return true;    } else {      // 已经使用过了    }    //删除    redisService.remove(key + token);  }  return false; }

2、如何安全的减扣库存?

同一个用户或者多个用户同时抢购一个商品的时候,我们如何做到并发安全减扣库存?

(1)数据库操作商品库存

/**  * Created by Administrator on 2017/9/8.  */public interface ProductDao extends JpaRepository{/**      * @param pid 商品ID      * @param num 购买数量      * @return      */@Transactional@Modifying@Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1")intreduceStock1(Integerpid,Integernum);/**      * @param pid 商品ID      * @param num 购买数量      * @return      */@Transactional@Modifying@Query("update Product set availableNum = availableNum - ?2 , reserveNum = reserveNum + ?2 where id = ?1 and  availableNum - ?2 >= 0")intreduceStock2(Integerpid,Integernum);}

(2)下单

/**     * 下单操作1      *     * @param req     */private int place(PlaceOrderReq req) {  User user = userDao.findOne(req.getUserId());  Product product = productDao.findOne(req.getProductId());  //下单数量  Integer num = req.getNum();  //可用库存  Integer availableNum = product.getAvailableNum();  //可用预定  if (availableNum >= num) {    //减库存    int count = productDao.reduceStock1(product.getId(), num);    if (count == 1) {      //生成订单      createOrders(user, product, num);    } else {      logger.info("库存不足 3");    }    return 1;  } else {    logger.info("库存不足 4");    return -1;  }}/**      * 下单操作2      *     * @param req     */private int place2(PlaceOrderReq req) {  User user = userDao.findOne(req.getUserId());  Product product = productDao.findOne(req.getProductId());  //下单数量 Integer num = req.getNum();  //可用库存  Integer availableNum = product.getAvailableNum();  //可用预定  if (availableNum >= num) {    //减库存    int count = productDao.reduceStock2(product.getId(), num);    if (count == 1) {      //生成订单      createOrders(user, product, num);    } else {      logger.info("库存不足 3");    }    return 1;  } else {    logger.info("库存不足 4");    return -1;  }}

方法1:不考虑库存安全的写法

/**        * 方法 1        * 减可用        * 加预占        * 库存数据不安全        *        * @param req        */   @Override   @Transactional   public void placeOrder(PlaceOrderReq req) {         place1(req);   }

分析:在高并的场景下,假设库存只有 2 件 ,两个请求同时进来,抢购该商品,购买数量都是 2. A请求 此时去获取库存,发现库存刚好足够,执行扣库存下单操作。在 A 请求未完成的时候(事务未提交),B请求 此时也去获取库存,发现库存还有2. 此时也去执行扣库存,下单操作。库存剩 2 ,但是卖出了 4 。最终数据库库存数量将变为 -2 ,所以库存是不安全的(关注公众号Java面试那些事儿,回复关键字面试,领取2020年最新面试题)。

方法2:这个操作可以保证库存数据是安全的

/**      * 方法 2      * 减可用      * 加预占      * 库存数据不安全      *      * @param req      */@Override   @Transactional   public void placeOrder(PlaceOrderReq req) {  place2(req);}

分析:在方法1 的基础上 ,更新库存的语句,增加了可用库存数量 大于 0, availableNum - num >= 0 ;实质是使用了数据库的乐观锁来控制库存安全,在并发量不是很大的情况下可以这么做。但是如果是秒杀,抢购,瞬时流量很高的话,压力会都到数据库,可能拖垮数据库。

方法3:该方法也可以保证库存数量安全

/**   * 方法 3     * 采用 Redis 锁  通一个时间 只能一个 请求修改 同一个商品的数量     * 

* 缺点并发不高,同时只能一个用户抢占操作,用户体验不好! * * @param req */@Override public void placeOrder2(PlaceOrderReq req) { String lockKey = "placeOrder:" + req.getProductId(); Boolean isLock = redisService.lock(lockKey); if (!isLock) { logger.info("系统繁忙稍后再试!"); return 2; } //place2(req); place1(req); //这两个方法都可以 redisService.unLock(lockKey);}

分析:利用Redis 分布式锁, 强制控制 同一个商品,同时只能一个请求处理下单。其他请求返回 ‘系统繁忙稍后再试!’;强制把处理请求串行化,缺点并发不高 ,处理比较慢,不适合抢购等方案 。用户体验也不好,明明看到库存是充足的,就是抢不到。相比方案2减轻了数据库的压力。

方法4 :可以保证库存安全,满足高并发处理,但是相对复杂一点

/**     * 方法 4     * 商品的数量 等其他信息 先保存 到 Redis     * 检查库存 与 减少库存 不是原子性,  以 increment > 0 为准      *     * @param req     */@Override   public void placeOrder3(PlaceOrderReq req) {  String key = "product:" + req.getProductId();  // 先检查 库存是否充足  Integer num = (Integer) redisService.get(key);  if (num < req.getNum()) {    logger.info("库存不足 1");  } else{    //不可在这里下单减库存,否则导致数据不安全, 情况类似 方法1;  }  //减少库存  long value = redisService.increment(key, -req.getNum().longValue());  //库存充足  if (value >= 0) {    logger.info("成功抢购 ! ");    //TODO 真正减 扣 库存 等操作 下单等操作,这些操作可用通过 MQ 或 其他方式    place2(req);  } else {    //库存不足,需要增加刚刚减去的库存    redisService.increment(key, req.getNum().longValue());    logger.info("库存不足 2 ");  }}

分析:利用Redis increment 的原子操作,保证库存安全。事先需要把库存的数量等其他信息保存到Redis,并保证更新库存的时候,更新Redis。

进来的时候 先 get 库存数量是否充足,再执行 increment。以 increment > 0 为准。检查库存 与 减少库存 不是原子性的。检查库存的时候技术库存充足也不可下单;否则造成库存不安全,原来类似 方法1. increment 是个原子操作,以这个为准。

redisService.increment(key, -req.getNum().longValue()) >= 0 说明库存充足,可以下单。

redisService.increment(key, -req.getNum().longValue()) < 0 的时候 不能下单,次数库存不足。并且需要 回加刚刚减去的库存数量,否则会导致刚才减扣的数量 一直卖不出去。数据库与缓存的库存不一致。

次方法可以满足 高并抢购等一些方案,真正减扣库存和下单可以异步执行。

订单时效问题,订单取消等 为保证商家利益,同时把商品卖给有需要的人,订单下单成功后,往往会有个有效时间。超过这个时间,订单取消,库存回滚。

订单取消后,可利用MQ 回退库存等。

这是在开发的过程中遇到的关于订单的库存安全问题的时候几种解决方案,我个人比较喜欢通过redis锁的形式,因为公司的业务不会牵扯太大的并发量,就是一个单纯的网上商城

好了,今天就到这里吧,希望对大家有所帮助,麻烦点赞+关注+转发,给小编一点支持,谢谢

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

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

相关文章

AI 白皮书:赢家、输家

来源&#xff1a;云头条引言&#xff1a;纵观历史&#xff0c;对企业界而言改变游戏规则的始终是技术。制造商取代手艺人&#xff0c;工厂从制造商手里接过接力棒&#xff0c;自动化和遥测技术取代大部分重复性的人力劳动。从个人计算机到互联网和移动商务&#xff0c;在过去的…

python 首次登陆outlook 脚本_记Python“用户环境”的一次完美应用

在之前写过一篇关于虚拟环境使用的文章&#xff0c;但是还没有好好的介绍一下 Python 的用户环境&#xff0c;原因是自己一直没遇到要使用 用户环境 的使用场景&#xff0c;所以就一直懒得写。恰巧这两天&#xff0c;自己遇到了一个使用用户环境的体验可以完爆虚拟环境的案例&a…

今日头条、抖音推荐算法原理全文详解!

来源&#xff1a;运营大叔本次分享将主要介绍今日头条推荐系统概览以及内容分析、用户标签、评估分析&#xff0c;内容安全等原理。一、系统概览推荐系统&#xff0c;如果用形式化的方式去描述实际上是拟合一个用户对内容满意度的函数&#xff0c;这个函数需要输入三个维度的变…

星梦缘陈彦妃_浙江舟山80后女演员,2003年出演偶像剧《星梦缘》,还是专业模特...

陈彦妃&#xff0c;1984年2月4日出生于浙江省舟山市&#xff0c;中国内地影视女演员、流行乐歌手、模特。陈彦妃是一个真性情的女孩&#xff0c;在高中时期拍摄了偶像剧《星梦缘》&#xff0c;在这部戏中&#xff0c;陈彦妃基本上是本色出演&#xff0c;进入大学之后&#xff0…

python hstack_Python小白数据科学教程:NumPy (下)

点击“简说Python”&#xff0c;选择“置顶/星标公众号”福利干货&#xff0c;第一时间送达&#xff01;本文作者&#xff1a;王圣元转载自&#xff1a;王的机器本文偏长(1.8w字)&#xff0c;老表建议先收藏&#xff0c;然后转发朋友圈&#xff0c;然后吃饭、休闲时慢慢看&…

MATLAB并行实现的简单方法

此方法只是利用了matlab的设定&#xff0c;不需要额外知识。 众所周知&#xff0c;matlab是单线程的&#xff0c;但matlab的每个应用窗口都是一个线程&#xff0c;因此可以同时开启多个MATLAB跑程序&#xff0c;占满CPU的所有core。 例如处理多个文件的计算&#xff0c;单线程…

POJ 1631 nlogn求LIS

方法一&#xff1a; 二分 我们可以知道 最长上升子序列的 最后一个数的值是随序列的长度而递增的 &#xff08;呃呃呃 意会意会&#xff09; 然后我们就可以二分找值了&#xff08;并更新&#xff09; //By SiriusRen #include <cstdio> #include <cstring> #incl…

城市大脑与未来超级智能建设规范研究报告即将发布

来源&#xff1a;今日头条21世纪以来&#xff0c;特别是在2010年以来&#xff0c;前沿科技领域出现诸多“大脑”概念&#xff0c;企业界出现谷歌大脑&#xff0c;百度大脑&#xff0c;阿里大脑&#xff0c;360安全大脑&#xff0c;腾讯超级大脑等&#xff0c;产业界出现城市大脑…

练习图200例图纸讲解_【宅家数学课23】经典微课6:苏教版六年级下册比例尺典型例题选讲及练习(含答案)...

(截止日期&#xff1a;3月31日)学习过程1、点击观看经典微课&#xff1a;微课视频《比例尺》2、认真学习典型例题&#xff0c;完成下方练习题3、查看答案&#xff0c;在家长指导下批改&#xff0c;订正错误。苏教版小学数学六年级下册比例尺典型例题选讲及练习【考点分析】【例…

ajax datatype_Ajax的基本使用

Asynchronous javascript and xmlAjax的实现 : 基于一个对象XMLHttpRequest (如何获取?)步骤: 1. 获取ajax对象function getRequestObject() {if (window.XMLHttpRequest) {// 支持Opera, Safari, Mozilla, Chrome,Internet Explorer 7, and IE 8.return(new XMLHttpRequest()…

MATLAB画图详细教程

本文将详细介绍如何用matlab绘图并美化。 关于figure() 创建图窗窗口&#xff1a;figure() figure()的属性&#xff1a; Name&#xff1a;在标题栏显示的名称&#xff0c;接字符串&#xff0c;如Test Position&#xff1a;在电脑屏幕上的位置和大小&#xff0c;后接向量[l…

android 发送广播_从0系统学Android--5.2 发送广播

从0系统学Android--52 发送广播本系列文章目录&#xff1a;更多精品文章分类本系列持续更新中…. 初级阶段内容参考《第一行代码》5.3 发送自定义广播前面已经学习了如何接受广播了&#xff0c;下面来学习如何发送自定义广播&#xff0c;广播类型分为&#xff1a;标准广播和有序…

31页官方PPT,回顾史上最大芯片WSE:科技的壮丽美感!

来源&#xff1a;芯潮2019年8月20日&#xff0c;在Hot Chips顶会上&#xff0c;发布了一款震惊世界的芯片&#xff1a;全球有史以来最大的计算机芯片wafer-scale engine &#xff08;WSE&#xff09;问世&#xff01;这款巨型芯片来自美国创企Cerebras&#xff0c;每边约22厘米…

Node.js npm 详解

一、npm简介 安装npm请阅读我之前的文章Hello Node中npm安装那一部分&#xff0c;不过只介绍了linux平台&#xff0c;如果是其它平台&#xff0c;有前辈写了更加详细的介绍。 npm的全称&#xff1a;Node Package Manager. ####&#xff08;1&#xff09;通俗的理解 其实从字面意…

html怎么在图片上添加文字_Image J基础操作:给图片添加文字和标注

对图片添加标注和文字是科研图片处理中一个非常基础的操作,Image J也可以进行这方面的处理。 01利用描边和填充添加 在绘制好选区(几乎只会用到箭头工具)之后选择:Edit-Draw(描边,快捷键Ctrl+D),使用事先设定好的颜色和粗细进行绘制;也可以填充设定好的颜色Edit-Fill(填充…

怎么更改sql的实例版本_学会复杂一点的SQL语句:Oracle DDL和DML

create&#xff1a;创建表创建用户创建视图创建表create table student(id int,score int) ;student后面与括号之间可以有空格可以没有创建用户create user liuyifei identified by 4852396;drop&#xff1a;删除整个表、删除指定的用户、删除指定的存储空间drop table table_n…

再谈智能

来源&#xff1a;人机与认知实验室1. 智能的产生1.1 智能生成机理有关智能生成的机理&#xff0c;一直是许多领域关注的焦点问题&#xff0c;涉及面之广、深很是少见&#xff0c;初步梳理可能会与这样几个最基本的问题有关&#xff1a;认知生成的机理、知识生成的机理、意…

的图片怎么循环渲染_十分钟教你做个炫酷的图片切换过度效果

做个炫酷的图片切换过度效果首先&#xff0c;今天是520节日。到了520这类为情侣准备的节日&#xff0c;小编都会感到一万点暴击……首先酸一波&#xff0c;搞点事情(蹭波热度)。给大家分享一个520特效页面&#xff1a;看完记得回来为小编点个赞哦&#xff01;这是案例请扫前言老…

《自然》:修复AI神经网络的缺陷

来源&#xff1a;王宏琳科学网博客我在上一篇《海外观察》博客中&#xff0c;介绍了纽约大学马库斯教授和戴维斯教授的新书《重启 AI&#xff1a;构建我们可以信任的人工智能》和图灵奖得主朱迪亚•珀尔的著作《为什么&#xff1a;关于因果关系的新科学》。这两本书都分析了以深…

三. 自动化测试用例设计

1. 主要内容&#xff1a; 2. 手工测试用例与自动化测试用例区别 目前自动化测试更多的时候是定位在冒烟测试和回归测试&#xff1b; 冒烟测试执行的是主体功能点的用例。回归测试执行全部或部分的测试用例。3. 测试类型 4. 异常 5. WebDriver错误截图 get_screenshot_as_f…