背景

在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付。

过程需要检验库存是否足够,保证库存不被超卖。

场景一:买家需要购买数量可以多件

场景二:秒杀活动,到时间点只能购买一件

目的

防止相同用户重复下单

检查库存准确数量

防止扣错库存数量

扣库存时性能效率提升、不阻塞用户

点赞再看,关注公众号:【地藏思维】给大家分享互联网场景设计与架构设计方案

掘金:地藏Kelvin https://juejin.im/user/5d67da8d6fb9a06aff5e85f7

主要解决手段

利用redis的incr、decr的原子性做操作

redis的lpush、rpop的原子性做操作,但是这个只能一个一个的扣,但不能原子地同时扣多个

sql乐观锁

交互流程

主要环节:购物车->结清->支付

本文讲述结清时,扣库存环节,分布式系统产生订单环节后续文章再详细分析。

一、防止重复

利用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先执行成功,另外一条后执行,也如上文提及到的场景一样。

LUA脚本保持库存原子性

其实用方案四的时候,扣减redis的库存时,最好用lua脚本处理,因为如果剩余1个时,用户买100个,这个时候按照方案四,其实会先把key increase -100就会变负99。

所以用lua脚本先查询数量剩余多少,是否够减100后,再去减100。

替换“库存不足”那个判断到incre的那几行代码,没在这里详细描述。

简单说一下分布式事务:

分开两个系统处理库存和订单时,这个时候可以用LCN框架做分布式事务,但是因为是http请求的,也是有概率会订单系统的网络超时,导致未返回结果。

其实也可以使用最终一致性的方式,数据表记录一条交互流水记录,更新库存成功后,更新这个交互流水记录的库存操作字段为已处理,订单处理字段为处理中,然后发送mq,等待订单创建生成回调。也要做定时任务做主动查询订单系统的结果,以防没有结果回来。

方案优势

不需要频繁访问数据库商品库存还有多少

不阻塞其他用户

安全扣减库存量

内存访问库存数量,减少数据库交互

高并发额外优化

用户访问下单是,前端ui可以让用户触发结算后,把按钮置灰色,防止重复触发。

可以按照库存数量来选定是否要用redis,因为如果库存数量少,或者说最近下单次数少的商品,就不用放redis,因为少人看和买的情况下,不必放redis导致占用内存。

如果到时间点抢购时,可以使用mq队列形式,用户触发购买商品后,进入队列,让用户的页面一直在转圈圈,等轮到他买的时候再进入结算页面,结算页面的后续流程和本文一致。

欢迎关注

我的公众号 :地藏思维

掘金:地藏Kelvin

简书:地藏Kelvin

订单减库存 java_高并发场景-订单库存防止超卖相关推荐

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

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

  2. Java实现库存防超卖_高并发场景-订单库存防止超卖

    背景 在电商系统中买商品过程,先加入购物车,然后选中商品,点击结算,即会进入待支付状态,后续支付. 过程需要检验库存是否足够,保证库存不被超卖. 场景一:买家需要购买数量可以多件 场景二:秒杀活动,到 ...

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

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

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

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

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

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

  6. 用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景?

    用分布式锁来防止库存超卖,但是是每秒上千订单的高并发场景,如何对分布式锁进行高并发优化来应对这个场景? 转载 codeing_doc 最后发布于2018-11-23 09:44:41 阅读数 1073 ...

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

    Java生鲜电商平台-高并发核心技术订单与库存实战 一. 问题 一件商品只有100个库存,现在有1000或者更多的用户来购买,每个用户计划同时购买1个到几个不等商品. 如何保证库存在高并发的场景下是安 ...

  8. MySQL调优篇:单机数据库如何在高并发场景下健步如飞?

    在当前的IT开发行业中,系统访问量日涨.并发暴增.线上瓶颈等各种性能问题纷涌而至,性能优化成为了现时代中一个炙手可热的名词,无论是在开发.面试过程中,性能优化都是一个常谈常新的话题.而MySQL作为整 ...

  9. Redis高并发场景下秒杀超卖解决

    目录 1 什么是秒杀 2 为什么要防止超卖 3 单体架构常规秒杀 3.1 常规减库存代码 3.2 模拟高并发 3.3 超卖现象 3.4 分析原因 4 简单实现悲观乐观锁解决单体架构超卖 4.1 悲观锁 ...

  10. 万字干货 | Python后台开发的高并发场景优化解决方案

    嘉宾 | 黄思涵 来源 | AI科技大本营在线公开课 互联网发展到今天,规模变得越来越大,也对所有的后端服务提出了更高的要求.在平时的工作中,我们或多或少都遇到过服务器压力过大问题.针对该问题,本次公 ...

最新文章

  1. 安装Mysql与nginx结合的小型服务
  2. TF:tensorflow框架中常用函数介绍—tf.Variable()和tf.get_variable()用法及其区别
  3. linux c之fdopen(int fd, const char *type)使用总结
  4. 连续两年入选Gartner公共云容器,阿里云在边缘容器方面做了什么?
  5. Java线程—如何解决Swing的单线程问题-----------Swing线程机制
  6. D-Link 不止暴露固件镜像密钥,还被曝5个严重0day
  7. linux之vi,vim命令
  8. Skyline开发1-环境搭建
  9. Css单位px,rem,em,vw,vh的区别
  10. 目前为止微型计算机,2017年计算机一级考试题库及答案
  11. 求职必看!大厂面试中遇到了发散性问题..... ,怎么办?
  12. 10月18号、19号、20号三天PC端云音乐项目总结
  13. GridView 栏位宽度自由拖动
  14. 自建服务器解网络锁,跟断刀学越狱】10分钟掌握iPhone1-4代刷机技巧
  15. 推荐系统之GBDT+LR
  16. 一文彻底搞懂加密、数字签名和数字证书,看不懂你打我!
  17. 浅析运输管理系统(TMS)
  18. 都2020年了,别再迷信啤酒与尿布!数据分析的真相在这
  19. 21和22端口 在java中的使用
  20. nRF24l01无线传输

热门文章

  1. 珊瑚橙怎么配色配色?橙色优学教你如何玩转2019年度流行色
  2. SQL简体转繁体互换(常用汉字词组)
  3. 【论文笔记】ASNet:基于生成对抗网络(GAN)的无监督单模和多模配准网络(范敬凡老师)
  4. outlook邮箱备份步骤
  5. CAD-CASS免费增强测绘工具CASS++
  6. 编译时内核栈溢出:the frame size of 1928 bytes is larger than 1024 bytes
  7. MacBook Air开启CPU虚拟化支持(Windows10)
  8. 我爱淘二次冲刺阶段5
  9. 「杂谈」Nanopore组装的拟南芥基因组效果如何?
  10. android 阻尼函数,数学的 H5 应用:拖动阻尼