Java实现库存防超卖_高并发场景-订单库存防止超卖
背景
在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付。
过程需要检验库存是否足够,保证库存不被超卖。
场景一:买家需要购买数量可以多件
场景二:秒杀活动,到时间点只能购买一件
目的
防止相同用户重复下单
检查库存准确数量
防止扣错库存数量
扣库存时性能效率提升、不阻塞用户
点赞再看,关注公众号:【地藏思维】给大家分享互联网场景设计与架构设计方案
掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7
主要解决手段
利用redis的incr、decr的原子性做操作
redis的lpush、rpop的原子性做操作,但是这个只能一个一个的扣,但不能原子地同时扣多个
sql乐观锁
交互流程
主要环节:购物车->结清->支付
本文讲述结清时,扣库存环节,分布式系统产生订单环节后续文章再详细分析。
备注:挺推荐使用https://www.processon.com/在线来做流程图的
一、防止重复
利用redis分布式锁
用分布式锁,是为了防刷、防止同一个用户同一秒里面把购物车里的商品进行多次结算,防止前端代码出问题触发两次。
利用Jedis客户端编写分布式锁
String result = jedis.set(lockKey, requestId, SET_IF_NOT_EXIST, SET_WITH_EXPIRE_TIME, expireTime);
lockKey是redis的Key,为用户id+商品id+商品数量组成,这样同一秒中只能有一次处理逻辑。
requestId是redis的value,实际是当前线程id,表示有一条线程占用。
大家要注意这种分布式锁写法,是同时设定超时时间的。有些分布式锁的文章可能是比较旧版的redis不支持同时设置超时时间,他就一条语句先设置key value,另一条语句后设置超时时间。所以大家留意一下。
二、扣减库存
安全扣减库存方案有很多说法,列一下几个方案和我推荐的方案。
方案一:分布式锁
有的文章会用redis分布式锁来做保证扣库存数量准确的环节,让点击结算时,后端逻辑会查询库存和扣库存的update语句同时只有一条线程能够执行,以商品id为分布式锁的key,锁一个商品。但是这样,其他购买相同商品的用户将会进行等待。
优点:这样做虽然安全
缺点:但是失去的是性能问题。
方案二:分布式锁+分段缓存
也有文章会说借鉴ConcurrenthashMap,分段锁的机制,把100个商品,分在3个段上,key为分段名字,value为库存数量。用户下单时对用户id进行%3计算,看落在哪个redis的key上,就去取哪个。
如key1=product-01,value1=33;key2=product-02,value2=33;key3=product-03,value3=33;
其实会有几个问题:
一个是用户想买34件的时候,要去两个片查
一个片上卖完了为0,又要去另外一个片查
取余方式计算每一片数量,除不尽时,让最后一片补,如100/3=33.33。
缺点:
方案复杂
有遗留问题
方案三: redis的lpush rpop
redis队列的lpush、rpop都是只能每次进出一个,对于购买多个数量的情况下不适用,只适用于秒杀情况购买一个的场景、或者抢红包的场景,所以觉得不是很通用。
备注:这个抢红包场景以后再分享。
方案四:推荐使用redis原子操作+sql乐观锁
利用Redis increment 的原子操作,保证库存数安全
先查询redis中是否有库存信息,如果没有就去数据库查,这样就可以减少访问数据库的次数。
获取到后把数值填入redis,以商品id为key,数量为value。
注意要设置序列化方式为StringRedisSerializer,不然不能把value做加减操作。
还需要设置redis对应这个key的超时时间,以防所有商品库存数据都在redis中。
比较下单数量的大小,如果够就做后续逻辑。
执行redis客户端的increment,参数为负数,则做减法。因为redis是单线程处理,并且因为increment让key对应的value 减少后返回的是修改后的值。
有的人会不做第一步查询直接减,其实这样不太好,因为当库存为1时,很多做减3,或者减30情况,其实都是不够,这样就白减。
扣减数据库的库存,这个时候就不需要再select查询,直接乐观锁update,把库存字段值减1 。
做完扣库存就在订单系统做下单。
样例场景:
假设两个用户在第一步查询得到库存等于10,A用户走到第二步扣10件,同时一秒内B用户走到第二部扣3件。
因为redis单线程处理,若A用户线程先执行redis语句,那么现在库存等于0,B就只能失败,就不会出更新数据库了。
public void order(OrderReq req) {
String key = "product:" + req.getProductId();
// 第一步:先检查 库存是否充足
Integer num = (Integer) redisTemplate.get(key);
if (num == null){
// 去查数据库的数据
// 并且把数据库的库存set进redis,注意使用NX参数表示只有当没有redis中没有这个key的时候才set库存数量到redis
//注意要设置序列化方式为StringRedisSerializer,不然不能把value做加减操作
// 同时设置超时时间,因为不能让redis存着所有商品的库存数,以免占用内存。
if (count >=0) {
//设置有效期十分钟
redisTemplate.expire(key, 60*10+随机数防止雪崩, TimeUnit.SECONDS);
}
// 减少经常访问数据库,因为磁盘比内存访问速度要慢
}
if (num < req.getNum()) {
logger.info("库存不足");
}
// 第二步:减少库存
long value = redisTemplate.increment(key, -req.getNum().longValue());
// 库存充足
if (value >= 0) {
logger.info("成功购买");
// update 数据库中商品库存和订单系统下单,单的状态未待支付
// 分开两个系统处理时,可以用LCN做分布式事务,但是也是有概率会订单系统的网络超时
// 也可以使用最终一致性的方式,更新库存成功后,发送mq,等待订单创建生成回调。
boolean res= updateProduct(req);
if (res)
createOrder(req);
} else {
// 减了后小小于0 ,如两个人同时买这个商品,导致A人第一步时看到还有10个库存,但是B人买9个先处理完逻辑,
// 导致B人的线程10-9=1, A人的线程1-10=-9,则现在需要增加刚刚减去的库存,让别人可以买1个
redisTemplate.increment(key, req.getNum().longValue());
logger.info("恢复redis库存");
}
}
update使用乐观锁
updateProduct方法中执行的sql如下:
update Product set count = count - #{购买数量} where id = #{id} and count - #{购买数量} >= 0;
虽然redis已经防止了超卖,但是数据库层面,为了也要防止超卖,以防redis崩溃时无法使用或者不需要redis处理时,则用乐观锁,因为不一定全部商品都用redis。
利用sql每条单条语句都是有事务的,所以两条sql同时执行,也就只会有其中一条sql先执行成功,另外一条后执行,也如上文提及到的场景一样。
简单说一下分布式事务:
分开两个系统处理库存和订单时,这个时候可以用LCN框架做分布式事务,但是因为是http请求的,也是有概率会订单系统的网络超时,导致未返回结果。
其实也可以使用最终一致性的方式,数据表记录一条交互流水记录,更新库存成功后,更新这个交互流水记录的库存操作字段为已处理,订单处理字段为处理中,然后发送mq,等待订单创建生成回调。也要做定时任务做主动查询订单系统的结果,以防没有结果回来。
方案优势
不需要频繁访问数据库商品库存还有多少
不阻塞其他用户
安全扣减库存量
内存访问库存数量,减少数据库交互
高并发额外优化
用户访问下单是,前端ui可以让用户触发结算后,把按钮置灰色,防止重复触发。
可以按照库存数量来选定是否要用redis,因为如果库存数量少,或者说最近下单次数少的商品,就不用放redis,因为少人看和买的情况下,不必放redis导致占用内存。
如果到时间点抢购时,可以使用mq队列形式,用户触发购买商品后,进入队列,让用户的页面一直在转圈圈,等轮到他买的时候再进入结算页面,结算页面的后续流程和本文一致。
欢迎关注
我的公众号 :地藏思维
掘金:地藏Kelvin
简书:地藏Kelvin
Java实现库存防超卖_高并发场景-订单库存防止超卖相关推荐
- java currenttimemillis 效率_高并发场景下System.currentTimeMillis()的性能问题的优化
前言 System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我也不知道,不过听说在100倍左右),然而该方法又是一个常用方法,有时不得不使用,比如生 ...
- java每秒向mysql写一条记录_【Java】mysql一条记录在高并发场景下读写?
CREATE TABLE `user_info` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `balance` decimal(10,2) DE ...
- 高并发解决方案_高并发提交订单的解决方案
商城系统,根据不同的业务需求,归类出几种购买活动.比如:常规商品的购买,稀缺商品的秒杀活动,特价商品的限时优惠活动,加价换购活动,拼团活动,众筹活动,将要发行销售的预售活动.这里,针对预售活动场景,来 ...
- 队列处理高并发_高并发场景下缓存处理的一些思路
在实际的开发当中,我们经常需要进行磁盘数据的读取和搜索,因此经常会有出现从数据库读取数据的场景出现. 但是当数据访问量次数增大的时候,过多的磁盘读取可能会最终成为整个系统的性能瓶颈,甚至是压垮整个数据 ...
- mysql 高并发 优惠券_转 mysql处理高并发,防止库存超卖
今天王总又给我们上了一课,其实mysql处理高并发,防止库存超卖的问题,在去年的时候,王总已经提过:但是很可惜,即使当时大家都听懂了,但是在现实开发中,还是没这方面的意识.今天就我的一些理解,整理一下 ...
- java分布式库存系统_这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!...
前言 之前一直有小伙伴私信我问我高并发场景下的订单和库存处理方案,我最近也是因为加班的原因比较忙,就一直没来得及回复.今天好不容易闲了下来想了想不如写篇文章把这些都列出来的,让大家都能学习到,说一千道 ...
- 用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景?
用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景? 转载 codeing_doc 最后发布于2018-11-23 09:44:41 阅读数 1073 ...
- mysql处理高并发,防止库存超卖
参考:http://blog.csdn.net/caomiao2006/article/details/38568825 今天王总又给我们上了一课,其实mysql处理高并发,防止库存超卖的问题,在去年 ...
- mysql版本号超买_MySQL处理高并发,防止库存超卖
今天王总又给我们上了一课,其实mysql处理高并发,防止库存超卖的问题,在去年的时候,王总已经提过:但是很可惜,即使当时大家都听懂了,但是在现实开发中,还是没这方面的意识.今天就我的一些理解,整理一下 ...
最新文章
- 【怎样写代码】参数化类型 -- 泛型(五):泛型类
- python培训机构推荐-广州有哪些不错的python培训班
- C++ vector多维数组初始化及清零
- 光流 | 图像特征匹配:特征光流与角点特征
- (转)SQL 优化原则
- JavaScript-初识jQuery及公式
- C语言-apache mod(模块开发)-采用apxs开发实战(centos7.2 linux篇)
- 游戏编程精粹学习 - 使用定点颜色插值模拟实时光照
- CSS3盒子模型(CSS3)
- paip.语义分析--单字动词表.txt
- 商业软件太贵?找开源替代品
- 中兴流媒体服务器,中兴通讯增强型MEC边缘服务器,满足5G业务极致体验需求
- marlin固件函数解析
- ES系列:字段类型不对时,如何保存文档到索引
- 你能把我带走吗?python:走吧
- Java-springboot生鲜电商项目(四)商品模块
- 与麻花兄弟诉苦兼讨论欠缺的知识
- html文件只能打印一页,javascript – 使用window.print()打印巨大的表只打印一页
- 一文搞懂G1垃圾回收器
- ES ILM 生命周期管理
热门文章
- 2020最新安卓版本是多少_百度浏览器2020最新版本下载,百度浏览器2020官方最新版本下载安装 v7.19.13.0...
- Mac:sh: /Users/lipan/Downloads/Django/node_modules/.bin/vue-cli-service: Permission denied
- Oracle Study之--Oracle 11g RAC添加节点错误
- twitter下载网络教程_糟糕的主意:喜p Twitter广告网络
- html给input添加边框,html input怎么设置虚线边框样式
- w10计算机管理员权限在哪里设置密码,windows10管理员权限怎么设置_win10电脑设置管理员权限的步骤...
- java表盘时钟的设计思路_java代码实现时钟
- 实时在线游戏服务器客户端交互总结
- NFS配置(RHCE考试题)
- 【二十二】 H.266/VVC | 选择最优的仿射AMVP候选项 | xEstimateAffineAMVP函数