Java生鲜电商平台-高并发核心技术订单与库存实战

Java生鲜电商平台-高并发核心技术订单与库存实战

一、 问题

一件商品只有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();

//保存到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<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 ,所以库存是不安全的。

方法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

时间: 2024-12-11 16:03:31

Java生鲜电商平台-高并发核心技术订单与库存实战的相关文章

Java生鲜电商平台-缓存架构实战

Java生鲜电商平台-缓存架构实战 说明:在Java生鲜电商中,缓存起到了非常重要的作用,目前整个项目中才用的是redis做分布式缓存. 缓存集群 缓存集群存在的问题 1.热key 缓存集群中的某个key瞬间被数万甚至十万的并发请求打爆. 2.大value 某个key对应的value可能有GB级的大小,导致查询value的时候导致网络相关的故障问题. 缓存集群作用 在缓存里放一些平时不怎么变动的数据,然后用户在查询大量的平时不怎么变动的数据的时候,可以直接从缓存里走了.缓存集群的并发能力是很强的

Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析

Java生鲜电商平台-电商中海量搜索ElasticSearch架构设计实战与源码解析 生鲜电商搜索引擎的特点 众所周知,标准的搜索引擎主要分成三个大的部分,第一步是爬虫系统,第二步是数据分析,第三步才是检索结果.首先,电商的搜索引擎并没有爬虫系统,因为所有的数据都是结构化的,一般都是微软的数据库或者 Oracle 的数据库,所以不用像百度一样用「爬虫」去不断去别的网站找内容,当然,电商其实也有自己的「爬虫」系统,一般都是抓取友商的价格,再对自己进行调整. 第二点,就是电商搜索引擎的过滤功能其实比

Java生鲜电商平台-电商虚拟币的充值与消费思考

Java生鲜电商平台-电商虚拟币的充值与消费思考 项目背景 最近由于项目业务原因,需要为系统设计虚拟币的充值及消费功能.公司内已经有成熟的支付网关服务,所以重点变成了如何设计项目内虚拟币的充值流程,让整个充值流程都实现幂等,确保用户的虚拟币余额不会重复增加或扣减. 商品购买及支付流程 微信支付时序图 (1)用户购买商品,商户后台请求生成支付订单并返回相关信息到客户端. (2)客户端根据返回的信息唤起支付SDK,用户确认支付. (3)用户完成支付后,支付系统会异步通知商户后台支付结果. (4)商户

Java生鲜电商平台-生鲜售后系统的退款架构设计与代码分享

说明:任何一个电商行业都涉及到退货与退款的问题,但是生鲜电商行业还设有一个显著的特点,那就是换货.在人性面前,各种各样的退货,退款,换货的售后问题,层出不穷,那么应该如何架构与设计呢?请看下文. 由于涉及到的东西比较多,目前只讲退款的架构设计与代码分享. 退款,是一个易造成负体验的业务产品.原因是商户对于退款的要求务必退款成功.高效.快,而且又得很好地支撑业务,否则就容易招来吐槽. 退款,一个看似简单,但充满复杂性的产品. 要想做好退款系统,我们必须深入的了解业务发展趋势,将客户诉求与现状业务结

Java生鲜电商平台-生鲜供应链(采购管理)

Java生鲜电商平台-生鲜供应链(采购管理) 在生鲜供应链系统中采购中心这一模块,它是电商公司管理采购的模块,包含供应商管理,采购订单管理,采购商品管理,在该模块中采购订单是采购中心的核心模块.在其他的比如供应商的管理,采购商品的管理,都始终围绕着订单管理为中心进行.本文将围绕电商后台中采购中心的供应商管理,采购订单管理,采购商品管理三个模块进行讲解. 在设计电商后台尤其需考虑公司的组织架构,考虑公司的个部门之间的业务流转,在设计后台尤其需要结合公司自身的组织架构和自身的业务需求,尽量精简业务流

Java生鲜电商平台-微服务架构概述

Java生鲜电商平台-微服务架构概述 单体架构存在的问题 在传统的软件技术架构系统中,基本上将业务功能集中在单一应用内,或者是单一进程中.尽管现代化的软件架构理论以及设计原则已推广多年,但实际技术衍化的速度迟缓并且变革动力不足. 其中的原因存在着复杂性以及多样性,我想主要的原因是没有一套整体的解决方案能够让工程师在面临稳定性风险下,毅然决然地实施系统重构.当系统应用规模随着业务的迅速发展时,系统的重要性愈发突出,开发人员将对系统的改造尤为敏感,从之前的徘徊犹豫,随之变得更加保守,只能延续过去的技

Java生鲜电商平台-电商订单系统全解析

Java生鲜电商平台-电商订单系统全解析 今天分享将会分为以下三个环节来阐述: 1.订单系统的介绍 2.订单系统的解构 3.垂直电商订单系统设计思路 一.什么是订单系统? 订单管理系统(OMS)是物流管理系统的一部分,通过对客户下达的订单进行管理及跟踪,动态掌握订单的进展和完成情况,提升物流过程中的作业效率,从而节省运作时间和作业成本,提高物流企业的市场竞争力.顾名思义,电商系统就是用户.平台.商户等对于订单的管控.跟踪的系统,衔接着商品中心.wms.促销系统.物流系统等,是电子商务的基础模块:

Java生鲜电商平台-APP/小程序接口传输常见的加密算法及详解

说明:Java生鲜电商平台-APP/小程序接口传输常见的加密算法及详解,加密算法,是现在每个软件项目里必须用到的内容. 广泛应用在包括了用户登入.数字签名.数据传输等多个场合.今天我把常见的加密算法全部整理在这里,供大家学习参考. 首先,大家要知道加密算法能干什么,利用加密算法来对数据通信的过程进行加密传输是一种最常见的安全手段.利用该手段能够达到一下三个目的: 1.数据保密性,防止用户数据被窃取或泄露: 2.数据完整性,防止用户传输的数据被篡改: 3.通信双方身份确认,确保数据来源合法: 常见

Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战

Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战 说明:Java生鲜电商平台-小程序或者APP优惠券的设计与源码实战,优惠券是一种常见的促销方式,在规定的周期内购买对应商品类型和额度的商品时,结算时满足一定条件会减免一定金额.通过发放优惠券,引导用户购买相应的商品,在下单的时候抵扣一定的费用,达到促销.提高客单价的目标. 优惠券不论在线上还是线下,适用范围都比较广泛.例如滴滴发的专车券.外卖平台发的外卖券.京东淘宝的优惠券等. 1.优惠券的类型和应用场景 优惠券有多种分类方式,按照使