介绍

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

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

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

下单思路:

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

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

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

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

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

什么时候进行减库存

方案一:加购时减库存。

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

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

方案四:支付时减库存。

分析:

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

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

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

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

所以综上所述:

选择方案三比较合理。

重复下单问题

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

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

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

4.用户恶意行为。

解决办法

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

2.后台:

(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 uid

* @return true 可下单

*/

public boolean judgeUserCanContinue(long uid) {

try {

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

log.info("用户分钟下单次数防刷校验 uid={}", uid);

String blackUser = "XXXX-pay-black-" + uid;

//判断用户是否已被限制

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

log.error("用户分钟下单次数防刷校验,用户已被限制 uid={}", uid);

return false;

}

String keyCount = "XXXX-pay-count-" + uid;

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

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

//设置过期时间 2分钟

redis.expire(keyCount, 2 * 60);

log.info("用户分钟下单次数防刷校验,目前缓存中总次数 count={} uid={}", count, uid);

if (count < 20) {

//不足1分钟20次直接返回

return true;

}

//获取第20次的时间

String secondString = redis.lindex(keyCount, -19);

log.info("用户分钟下单次数防刷校验 uid={} , 第20次时间:{}秒, 当前时间:{}秒 ", uid, secondString, nowSecond);

Long oldSecond = Long.valueOf(secondString);

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

boolean result = nowSecond - oldSecond > 60;

if (!result) {

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

log.info("用户分钟下单次数防刷校验 触发限制,加入黑名单,过期时间10分钟 uid={} , 第20次时间:{}秒, 当前时间:{}秒 ", uid, secondString, nowSecond);

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

}

return result;

} catch (Exception e) {

log.error("用户支付单防刷异常 uid={}", uid, e);

}

return true;

}

如何安全的减库存

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

方案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)修改 + 补偿机制

java 订单减库存_订单和库存处理方案相关推荐

  1. java 订单减库存_下单减库存

    1. 减库存 一般下单减库存的流程大概是这样的: 1.查询商品库存.这里直接查的Redis中的库存. 2.Redis中的库存减1.这里用到的Redis命令是:incrby -1 3.扣减数据库中的库存 ...

  2. java 订单减库存_下单减库存(示例代码)

    1. 减库存 一般下单减库存的流程大概是这样的: 1.查询商品库存.这里直接查的Redis中的库存. 2.Redis中的库存减1.这里用到的Redis命令是:incrby -1 3.扣减数据库中的库存 ...

  3. java订单实现的_订单的处理原理及代码实现.

    上一篇博文我们已经介绍了购物车的原理及实现, 那么购物车再往后就是提交订单了, 订单的实现方式是怎么样的呢? 那么下面就让我们来一起学习下. 提交订单有几个关键点: 1, 用户必须登录 2, 购物车必 ...

  4. 电商扣减库存_电商库存体系设计

    库存是什么? 百度百科:库存(inventory)是以支持生产,维护,操作和客户服务为目的而存储的各种物料,包括原材料和在制品,维修件和生产消耗品,成品和备件等: 狭义:"仓库里存放东西&q ...

  5. python订单管理系统功能_订单管理系统有哪些功能?

    订单管理系统的工作流程是首先对客户的订单信息进行接收,然后对订单进行处理,及时掌握交易动态,在有突发状况发生时及时反馈.订单管理系统是物理管理系统中不可缺少的一部分,那么它有哪些功能呢? 1.业务流程 ...

  6. python订单管理系统功能_订单管理系统的基本功能有哪些?

    订单管理系统是通过统一的订单管理和分配,给用户提供整合的一站式供应链服务,让仓储,运输和订单形成一个有机的整体,从而满足物流信息化的需求,今天德米萨就给大家具体介绍下订单管理系统的基本功能有哪些. 1 ...

  7. 分布式锁和mysql事物扣库存_浅谈库存扣减和锁

    先说场景: 物品W现在库存剩余1个,  用户P1,P2同时购买.则只有1人能购买成功.(前提是不允许超卖) 秒杀也是类似的情况, 只有1件商品,N个用户同时抢购,只有1人能抢到.. 这里不谈秒杀设计, ...

  8. java控制订单过期时间_订单自动过期实现方案

    需求分析:24小时内未支付的订单过期失效. 解决方案被动设置:在查询订单的时候检查是否过期并设置过期状态. 定时调度:定时器定时查询并过期需要过期的订单. 延时队列:将未支付的订单放入一个延时队列中, ...

  9. python订单管理系统功能_订单管理系统的功能模块有哪些?

    订单管理系统能够动态的了解订单的进展和实施状况,它通过对订单进行实时的管理,对于节省运作时间,降低作业成本具有非常重要的作用,它是企业的管理系统中重要的组成成分,今天德米萨就给大家具体介绍下订单管理系 ...

  10. java日期减天数_使用Java中的Calendar.DATE从当前日期减去天数

    为Java中的Calendar类导入以下软件包.import java.util.Calendar; 首先,创建一个Calendar对象并显示当前日期.Calendar calendar = Cale ...

最新文章

  1. 原创jQuery移动设备弹出框插件——msgalert.js
  2. ***测试技巧总结更新篇3
  3. 期末复习、化学反应工程科目(第五章)
  4. JIL 编译与 AOT 编译
  5. 如何判断注册用户是否已经存在(membership验证)
  6. [转载]用消息队列和消息应用状态表来消除分布式事务
  7. C - 思考使用差分简化区间操作
  8. [数据库]---nosql,非关系型数据库整理
  9. 33 SD配置-销售凭证设置-定义销售凭证的号码范围
  10. VC++6.0 内存泄露(Memory Leaks)征兆1
  11. 萌新的Python练习菜鸟100例(八)输出 9*9 乘法口诀表
  12. Interesting Finds: 2008.04.24
  13. PostgreSQL and SQLAlchemy [ubuntu]
  14. su sudo gksudo-转
  15. java中action类是干嘛的_java中action的作用
  16. scala.的Enumeration枚举示例(转)
  17. Pytorch安装教程
  18. 新遇到的问题 , 进程退出代码是 '0xffffffff'
  19. ts中的never类型
  20. 宇视科技设备SDK获取方式

热门文章

  1. 用join实现交集,并集,差集,补集的效果
  2. 面对面快传文件在服务器有痕迹,QQ面对面快传的文件存储在哪
  3. 【随问】网址中的www是什么意思?HTTPS和HTTP的区别是什么?
  4. SQL基础(一):安装MySQL以及一些简单操作
  5. 快门光圈感光度口诀_只要三杯水,就能搞懂光圈/快门/感光度的关系!
  6. 英雄联盟lol鼠标突然不能a兵了
  7. linux cups用户名密码,Linux下使用CUPS提供打印服务
  8. 国内IT界女神程序员!和她们一样漂亮的还有谁?
  9. 什么叫断章取义,什么叫曲解——你被骗了多少年?
  10. 高琪飞机项目飞机爆炸图片_爆炸式的工作机会和多项目同步