前言

之前一直有小伙伴私信我问我高并发场景下的订单和库存处理方案,我最近也是因为加班的原因比较忙,就一直没来得及回复。今天好不容易闲了下来想了想不如写篇文章把这些都列出来的,让大家都能学习到,说一千道一万都不如满满的干货来的实在,干货都下面了!

介绍

前提:分布式系统,高并发场景

商品A只有100库存,现在有1000或者更多的用户购买。如何保证库存在高并发的场景下是安全的。

预期结果:1.不超卖 2.不少卖 3.下单响应快 4.用户体验好

下单思路:

下单时生成订单,减库存,同时记录库存流水,在这里需要先进行库存操作再生成订单数据,这样库存修改成功,响应超时的特殊情况也可以通过第四步定时校验库存流水来完成最终一致性。

支付成功删除库存流水,处理完成删除可以让库存流水数据表数据量少,易于维护。

未支付取消订单,还库存+删除库存流水

定时校验库存流水,结合订单状态进行响应处理,保证最终一致性

(退单有单独的库存流水,申请退单插入流水,退单完成删除流水+还库存)

什么时候进行减库存

方案一:加购时减库存。

方案二:确认订单页减库存。

方案三:提交订单时减库存。

方案四:支付时减库存。

分析:

方案一:在这个时间内加入购物车并不代表用户一定会购买,如果这个时候处理库存,会导致想购买的用户显示无货。而不想购买的人一直占着库存。显然这种做法是不可取的。唯品会购物车锁库存,但是他们是另一种做法,加入购物车后会有一定时效,超时会从购物车清除。

方案二:确认订单页用户有购买欲望,但是此时没有提交订单,减库存会增加很大的复杂性,而且确认订单页的功能是让用户确认信息,减库存不合理,希望大家对该方案发表一下观点,本人暂时只想到这么多。

方案三:提交订单时减库存。用户选择提交订单,说明用户有强烈的购买欲望。生成订单会有一个支付时效,例如半个小时。超过半个小时后,系统自动取消订单,还库存。

方案四:支付时去减库存。比如:只有100个用户可以支付,900个用户不能支付。用户体验太差,同时生成了900个无效订单数据。

所以综上所述:

选择方案三比较合理。

重复下单问题

用户点击过快,重复提交。

网络延时,用户重复提交。

网络延时高的情况下某些框架自动重试,导致重复请求。

用户恶意行为。

解决办法

前端拦截,点击后按钮置灰。

后台:

(1)redis 防重复点击,在下单前获取用户token,下单的时候后台系统校验这个 token是否有效,导致的问题是一个用户多个设备不能同时下单。

//key , 等待获取锁的时间 ,锁的时间

redis.lock("shop-oms-submit" + token, 1L, 10L);

复制代码

redis的key用token + 设备编号 一个用户多个设备可以同时下单。

//key , 等待获取锁的时间 ,锁的时间

redis.lock("shop-oms-submit" + token + deviceType, 1L, 10L);

复制代码

(2)防止恶意用户,恶意攻击 : 一分钟调用下单超过50次 ,加入临时黑名单 ,10分钟后才可继续操作,一小时允许一次跨时段弱校验。使用reids的list结构,过期时间一小时

/**

* @param token

* @return true 可下单

*/

public boolean judgeUserToken(String token) {

//获取用户下单次数 1分钟50次

String blackUser = "shop-oms-submit-black-" + token;

if (redis.get(blackUser) != null) {

return false;

}

String keyCount = "shop-oms-submit-count-" + token;

Long nowSecond = LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));

//每一小时清一次key 过期时间1小时

Long count = redis.rpush(keyCount, String.valueOf(nowSecond), 60 * 60);

if (count < 50) {

return true;

}

//获取第50次的时间

List secondString = redis.lrange(keyCount, count - 50, count - 49);

Long oldSecond = Long.valueOf(secondString.get(0));

//now > oldSecond + 60 用户可下单

boolean result = nowSecond.compareTo(oldSecond + 60) > 0;

if (!result) {

//触发限制,加入黑名单,过期时间10分钟

redis.set(blackUser, String.valueOf(nowSecond), 10 * 60);

}

return result;

}

复制代码

如何安全的减库存

多用户抢购时,如何做到并发安全减库存?

方案1: 数据库操作商品库存采用乐观锁防止超卖:

sql:update sku_stock set stock = stock - num where sku_code = '' and stock - num > 0;

复制代码

分析:

高并发场景下,假设库存只有 1件 ,两个请求同时进来,抢购该商品.

数据库层面会限制只有一个用户扣库存成功。在并发量不是很大的情况下可以这么做。但是如果是秒杀,抢购,瞬时流量很高的话,压力会都到数据库,可能拖垮数据库。

方案2:利用Redis单线程 强制串行处理

/**

* 缺点并发不高,同时只能一个用户抢占操作,用户体验不好!

*

* @param orderSkuAo

*/

public boolean subtractStock(OrderSkuAo orderSkuAo) {

String lockKey = "shop-product-stock-subtract" + orderSkuAo.getOrderCode();

if(redis.get(lockKey)){

return false;

}

try {

lock.lock(lockKey, 1L, 10L);

//处理逻辑

}catch (Exception e){

LogUtil.error("e=",e);

}finally {

lock.unLock(lockKey);

}

return true;

}

复制代码

分析:

利用Redis 分布式锁,强制控制同一个商品处理请求串行化,缺点并发不高 ,处理比较慢,不适合抢购,高并发场景。用户体验差,但是减轻了数据库的压力。

方案3 :redis + mq + mysql 保证库存安全,满足高并发处理,但相对复杂。

/**

* 扣库存操作,秒杀的处理方案

* @param orderCode

* @param skuCode

* @param num

* @return

*/

public boolean subtractStock(String orderCode,String skuCode, Integer num) {

String key = "shop-product-stock" + skuCode;

Object value = redis.get(key);

if (value == null) {

//前提 提前将商品库存放入缓存 ,如果缓存不存在,视为没有该商品

return false;

}

//先检查 库存是否充足

Integer stock = (Integer) value;

if (stock < num) {

LogUtil.info("库存不足");

return false;

}

//不可在这里直接操作数据库减库存,否则导致数据不安全

//因为此时可能有其他线程已经将redis的key修改了

//redis 减少库存,然后才能操作数据库

Long newStock = redis.increment(key, -num.longValue());

//库存充足

if (newStock >= 0) {

LogUtil.info("成功抢购");

//TODO 真正扣库存操作 可用MQ 进行 redis 和 mysql 的数据同步,减少响应时间

} else {

//库存不足,需要增加刚刚减去的库存

redis.increment(key, num.longValue());

LogUtil.info("库存不足,并发");

return false;

}

return true;

}

复制代码

分析:

利用Redis increment 的原子操作,保证库存安全,利用MQ保证高并发响应时间。但是事需要把库存的信息保存到Redis,并保证Redis 和 Mysql 数据同步。缺点是redis宕机后不能下单。

increment 是个原子操作。

综上所述:

方案三满足秒杀、高并发抢购等热点商品的处理,真正减扣库存和下单可以异步执行。在并发情况不高,平常商品或者正常购买流程,可以采用方案一数据库乐观锁的处理,或者对方案三进行重新设计,设计成支持单订单多商品即可,但复杂性提高,同时redis和mysql数据一致性需要定期检查。

订单时效问题

超过订单有效时间,订单取消,可利用MQ或其他方案回退库存。

设置定时检查

Spring task 的cron表达式定时任务

MQ消息延时队列

订单与库存涉及的几个重要知识

TCC 模型:Try/Confirm/Cancel:不使用强一致性的处理方案,最终一致性即可,下单减库存,成功后生成订单数据,如果此时由于超时导致库存扣成功但是返回失败,则通过定时任务检查进行数据恢复,如果本条数据执行次数超过某个限制,人工回滚。还库存也是这样。

幂等性:分布式高并发系统如何保证对外接口的幂等性,记录库存流水是实现库存回滚,支持幂等性的一个解决方案,订单号+skuCode为唯一主键(该表修改频次高,少建索引)

乐观锁:where stock + num>0

消息队列:实现分布式事务 和 异步处理(提升响应速度)

redis:限制请求频次,高并发解决方案,提升响应速度

分布式锁:防止重复提交,防止高并发,强制串行化

分布式事务:最终一致性,同步处理(Dubbo)/异步处理(MQ)修改 + 补偿机制

写在最后的话

大家看完有什么不懂的可以在下方留言讨论,也可以私信问我一般看到后我都会回复的。也欢迎大家关注我的公众号:前程有光,金三银四跳槽面试季,整理了1000多道将近500多页pdf文档的Java面试题资料,文章都会在里面更新,整理的资料也会放在里面。最后觉得文章对你有帮助的话记得点个赞哦,点点关注不迷路,每天都有新鲜的干货分享!

关于找一找教程网

本站文章仅代表作者观点,不代表本站立场,所有文章非营利性免费分享。

本站提供了软件编程、网站开发技术、服务器运维、人工智能等等IT技术文章,希望广大程序员努力学习,让我们用科技改变世界。

[这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!]http://www.zyiz.net/tech/detail-143993.html

java分布式库存系统_这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!...相关推荐

  1. 分布式锁和mysql事物扣库存_这个是真的厉害,高并发场景下的订单和库存处理方案,讲的很详细了!...

    前言 之前一直有小伙伴私信我问我高并发场景下的订单和库存处理方案,我最近也是因为加班的原因比较忙,就一直没来得及回复.今天好不容易闲了下来想了想不如写篇文章把这些都列出来的,让大家都能学习到,说一千道 ...

  2. 高并发 统计对账Java_高并发场景下强一致预算/库存扣减方案

    场景描述 对于预算扣减/库存扣减类场景,我们需要根据业务对已有预算/库存做减法,拿发券的场景来举例: 需要满足不同的发券需求,运营可配置预算扣减业务 每次请求扣减一定数量的金额,比如发10元券给用户 ...

  3. java currenttimemillis 效率_高并发场景下System.currentTimeMillis()的性能问题的优化

    前言 System.currentTimeMillis()的调用比new一个普通对象要耗时的多(具体耗时高出多少我也不知道,不过听说在100倍左右),然而该方法又是一个常用方法,有时不得不使用,比如生 ...

  4. 读数据库遇到空就进行不下去_如何解决高并发场景下缓存+数据库双写不一致问题?...

    推荐阅读: 一只Tom猫:手撕分布式技术:限流.通讯.缓存,全部一锅端走送给你!​zhuanlan.zhihu.com 一只Tom猫:MySQL复习:20道常见面试题(含答案)+21条MySQL性能调 ...

  5. 本地缓存需要高时效性怎么办_缓存在高并发场景下的常见问题

    缓存一致性问题 当数据时效性要求很高时,需要保证缓存中的数据与数据库中的保持一致,而且需要保证缓存节点和副本中的数据也保持一致,不能出现差异现象.这就比较依赖缓存的过期和更新策略.一般会在数据发生更改 ...

  6. java高并发(二十一)高并发场景下缓存常见问题

    缓存一致性 当数据实时性要求很高时,需要保证缓存中的数据与数据库中的数据一致,缓存节点与副本中的数据一致,不能出现差异现象,这就比较依赖缓存的过期和更新策略了.一般会在数据发生更改的时候,主动跟新缓存 ...

  7. 商城系统学习总结(1)——订单与库存在高并发场景下案例解析

    一. 问题 一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品.如何保证库存在高并发的场景下是安全的? (1)不多发 (2)不少发 二. 下单的步骤 ...

  8. java每秒向mysql写一条记录_【Java】mysql一条记录在高并发场景下读写?

    CREATE TABLE `user_info` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT, `balance` decimal(10,2) DE ...

  9. 高并发场景下秒杀系统的设计思路

    点击上方「蓝字」关注我们 1. 概述 秒杀系统之所以难做,是因为在极短的时间内涌入大量的请求,来同时访问有限的服务资源,从而造成系统负载压力大,甚至导致系统服务瘫痪以及宕机的可能.本文会介绍秒杀系统中 ...

最新文章

  1. 养成良好的学习习惯-浅谈学习方法(1)
  2. php程序监听node.js程序和go程序
  3. java项目中使用的jar包出错怎么办
  4. Intel qsv + ffmpeg 硬解h264
  5. LAMP(linux下apache+mysql+php)平台编译安装的实现
  6. 快速排序的三种实现方法
  7. c语言试卷浙江理工大学杀人案件追踪,浙江理工大学c语言期末考试模拟试卷6 .pdf...
  8. UnitTest in .NET(Part 2)
  9. 【HDU - 5700】【51nod - 1672】 区间交(贪心,STLset 或线段树第k大)
  10. Python:Tensorflow中两个稀疏张量相乘
  11. 虚拟机安装MySQL教程
  12. [王垠系列]GTF - Great Teacher Friedman
  13. in packet sniffer
  14. 国二c语言选择题要对一半,计算机二级考试C语言程序设计题有步骤分吗
  15. access查询设计sol视图_access查询类型分哪几种?
  16. 利用python进行身份证号码大全_身份证号码设置显示格式,我用了最笨的办法,你有什么好办法吗?...
  17. Liang-Barsky直线段裁剪算法
  18. NOIP2017酱油记
  19. 向量空间、内积空间、欧式空间以及希尔伯特空间的关系
  20. python非线性可分支持向量机模型(实现iris分类)

热门文章

  1. 【云平台】与【物联网云平台】
  2. 这份2018年终总结PPT模板,你值得使用
  3. 谷歌、亚马逊、IBM和微软的云计算平台
  4. matlab 求系统码的校验矩阵,数字通信系统matlab仿真
  5. CSS水平、垂直布局、盒子
  6. HTML+CSS第二部分_01(html进阶)
  7. EnterNet 500中PPPoE故障
  8. 爱数oracle rac备份,客户案例 |爱数AnyStorage打造宁乡县人民医院RAC+虚拟化混合架构容灾...
  9. sina 微博 签名档~~~
  10. 2022-08-06 乐理知识(四) 认识五线谱——延音线和连音线,简谱与五线谱的区别,认识五线谱:谱号,高音谱号,低音谱号,中音谱号和和次中音谱号