某生鲜平台面试题:如何保证库存在高并发的场景下是安全的?
一、 问题
一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品。
如何保证库存在高并发的场景下是安全的?
(1)不多发
(2)不少发
二、 下单的步骤
(1)下单
(2)下单同时预占库存
(3)支付
(4)支付成功真正减扣库存
(5)取消订单
(6)回退预占库存
三、 什么时候进行预占库存?
(1)方案一:加入购物车的时候去预占库存
(2)方案二:下单的时候去预占库存
(3)方案三:支付的时候去预占库存
四、 分析
(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();//保存到RedisredisService.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<Product,Integer>{
/** * @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 锁 通一个时间 只能一个 请求修改 同一个商品的数量
* <p>
* 缺点并发不高,同时只能一个用户抢占操作,用户体验不好! *
* @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 回退库存等。
来源:https://www.cnblogs.com/jurendage/p/11240172.html
公众号后台回复【2T】有惊喜礼包!
关注微信公众号:互联网架构师,在后台回复:2T,可以获取我整理的教程,都是干货。
猜你喜欢
1、GitHub 标星 3.2w!史上最全技术人员面试手册!FackBoo发起和总结
2、如何才能成为优秀的架构师?
3、从零开始搭建创业公司后台技术栈
4、程序员一般可以从什么平台接私活?
5、37岁程序员被裁,120天没找到工作,无奈去小公司,结果懵了...
6、滴滴业务中台构建实践,首次曝光
7、不认命,从10年流水线工人,到谷歌上班的程序媛,一位湖南妹子的励志故事
8、15张图看懂瞎忙和高效的区别
9、2T架构师学习资料干货分享
某生鲜平台面试题:如何保证库存在高并发的场景下是安全的?相关推荐
- 商城系统学习总结(1)——订单与库存在高并发场景下案例解析
一. 问题 一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品.如何保证库存在高并发的场景下是安全的? (1)不多发 (2)不少发 二. 下单的步骤 ...
- Redis面试 - 如何保证 redis 的高并发和高可用?
面试题 如何保证 redis 的高并发和高可用?redis 的主从复制原理能介绍一下么?redis 的哨兵原理能介绍一下么? 面试官心理分析 其实问这个问题,主要是考考你,redis 单机能承载多高并 ...
- Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战
Java生鲜电商平台-SpringCloud微服务架构高并发参数优化实战 一.写在前面 在Java生鲜电商平台平台中相信不少朋友都在自己公司使用Spring Cloud框架来构建微服务架构,毕竟现在这 ...
- oom 如何避免 高并发_【面试题】如何设计一个高并发系统?
面试题 如何设计一个高并发系统? 原文链接:https://github.com/doocs/advanced-java/blob/master/docs/high-concurrency/high- ...
- 面试题:如何设计一个高并发系统?
面试官心理分析 说实话,如果面试官问你这个题目,那么你必须要使出全身吃奶劲了.为啥?因为你没看到现在很多公司招聘的 JD 里都是说啥,有高并发就经验者优先. 如果你确实有真才实学,在互联网公司里干过高 ...
- 在高并发的情况下,利用redis来处理库存超卖和遗留问题
在高并发的情况下,利用redis来处理库存超卖和遗留问题 首先现在redis中放上商品的库存数量为100间商品,在初始化一个set集合用于放秒杀成功的用户id,本用例先放进去一个id=10000的用户 ...
- 最全Java架构师130面试题:微服务、高并发、大数据、缓存等中间件
一.数据结构与算法基础 · 说一下几种常见的排序算法和分别的复杂度. · 用Java写一个冒泡排序算法 · 描述一下链式存储结构. · 如何遍历一棵二叉树? · 倒排一个LinkedList. · 用 ...
- 面试题:如何设计一个高并发的系统?
这道面试题涉及的知识点比较多,主要考察的是面试者的综合技术能力.高并发系统的设计手段有很多,主要体现在以下五个方面. 1.前端层优化 ① 静态资源缓存:将活动页面上的所有可以静态的元素全部静态化,尽量 ...
- 高并发写场景:秒杀系统库存扣减
前言 本文中的伪代码示例均以 PHP 作为编程语言 在设计秒杀系统的库存扣减逻辑时,可能一开始想到的代码是: /*** 商品库存扣减** @param int $skuId 商品ID* @param ...
- 【面试题】如何设计一个高并发的系统?
每个行业都一样,人才都是分层次的,从事技术行业的程序员们更是如此,按照技术能力分为三六九等. 每个层次的人出去面试,面试官考察的方向是不一样的. 刚入职场的小白,会问你很多基础性的知识,有过几年经验的 ...
最新文章
- Repeater嵌套
- day26 re正则表达式
- java.sql.SQLSyntaxErrorException: Unknown database ‘spring‘
- 深度学习的实用层面 —— 1.6 Dropout正则化
- 思科集成管理控制器IMC爆出任意代码执行漏洞CVE-2017-6616 绿盟科技发布安全威胁通告...
- HTTPS反向代理嗅探
- 新冠全球蔓延,AI+大数据拿什么拯救全人类? | AI 技术生态论
- putty远程登录linux无ssh,收集的linux远程ssh连接putty失败解决办法!
- linux普通用户id一般是,实际用户ID和有效用户ID (一) *****
- 使用 软件 vscode 对比文件内容的方法
- 地图数据下载方法与商业地图下载器介绍
- 上班摸鱼的模拟经营文字游戏(管理后台页面,老板都看不出来)
- 中小型企业选择企业邮箱的好处
- 真正的Windows XP SP2上海政府版
- Vue+ant-design-pro(2)动态路由
- 青龙面板搭建及记录踩过的坑
- 小学计算机集体听课评课,徐州市苏山小学开展《好的故事》听评课集体教研活动...
- GNN(一)走进GNN
- Facebook商业模式分析
- 深度对话 | V 神纵论区块链和以太坊
热门文章
- 《C#并发编程经典实例》学习笔记—2.1 暂停一段时间
- 搭建Yum服务器及编译安装Httpd实验
- Kafka 分布式消息系统详解
- 找不到 org/springframework/dao/support/PersistenceExceptionTranslator
- 「leetcode」513.找树左下角的值(详解)
- 二叉树:层序遍历登场!
- Final Cut Pro X for Mac(fcpx视频剪辑)中文版
- Illustrator 教程,如何在 Illustrator 中修改形状?
- 如何解决苹果Mac安装Axure首次打开报错的问题?
- php 串行化与json(转)--很不错的文章