您当前的位置:首页 > 电脑百科 > 程序开发 > 编程百科

万级并发!电商库存扣减如何设计,如何做到不超卖?

时间:2021-04-28 10:31:04  来源:今日头条  作者:Java路人甲
万级并发!电商库存扣减如何设计,如何做到不超卖?

 

前言:

随着中国消费认知的不断升级,网购走近千家万户,越来越被人们所接受。淘宝、唯品会、考拉、京东、拼多多等逐渐成为我们生活的重要组成部分。

除了常规的购物下单外,这些电商平台还经常搞一些双十一活动,秒杀、大促、限时购,各种营销玩法,层出不穷! 今天就来跟大家聊一聊电商技术里的库存扣减。

万级并发!电商库存扣减如何设计,如何做到不超卖?

 

当有很多人同时在买一件商品时(假设库存充足),每个人几乎同时下单成功,给人一种并行的感觉。但真实情况, 库存只是一个数值,无论是存在MySQL数据库还是redis缓存,减值时都要控制顺序,只能串行来扣减,当然为了保证安全性,会设计一些锁控制操作。

palm_tree: 库存扣减关键技术点

  • 同一个SKU,库存数量是共享
  • 剩余库存要大于等于本次扣减的数量,否则会出现 超卖 现象,引发资损
  • 对同一个数量多用户并发扣减时,要注意并发安全,保证数据的一致性
  • 类似于秒杀这样高QPS的扣减场景,要保证性能与高可用
  • 对于购物车下单场景,多个商品库存批量扣减,要保证事务
  • 如果有 交易退款 ,保证库存扣减可以返还
    • 返还的数据总量不能大于扣减的总量
    • 返还要保证幂等
    • 可以分多次返还

palm_tree: 数据库扣减方案

主要是依赖数据库特性来保证扣减的一致性,逻辑简单,开发部署成本很低。

依赖的数据库特性:

  • 依赖数据库的乐观锁(比如:版本号或者库存数量)保证数据并发扣减的强一致性
  • 借助事务特性,针对购物车下单批量扣减时,部分扣减失败,数据回滚
万级并发!电商库存扣减如何设计,如何做到不超卖?

 

最上面会查询当前的剩余库存(可能不准确,但没关系,这里只是第一步粗略校验),前置校验,如果已经没有库存,前置拦截生效,减少对数据库的写操作。毕竟读操作不涉及加锁,并发性能高。数据库包含两张表:库存表、流水表。

1、库存表

字段

说明

sku_id

商品规格id

leaved_amount

剩余可购买数量

  • 当用户进行取消订单、申请退货退款,需要把数量加回来
  • 如果商家补过库存,需要在此基础上额外加上增量库存

2、 流水表

字段

说明

id

主键id

sku_id

商品规格id

order_detail_id

订单明细id

quantity_trade

本次购买扣减的数量

  • 用于查看明细、对账、盘货、排查问题等
  • 在扣减后,某些场景下需要返还也依赖流水

单条商品的扣减SQL大致如下:

update inventory 
set leaved_amount = leaved_amount - #{count} 
where sku_id='123' and leaved_amount >= #{count}

此 SQL 采用 类似乐观锁的方式实现了原子性,在 where 条件里判断此次购买的数量小于等于剩余的数量。在扣减服务的代码里,判断此 SQL 的返回值,如果值为 1 ,表示扣减成功。否则,返回 0 ,表示库存不足,需要回滚。

扣减成功后,需要记录扣减流水,并与订单明细记录做关联。

  1. 当用户归还数量时,需要带回此编号,用来标识此次返还属于历史上的具体哪次扣减。
  2. 进行幂等性控制。当用户调用扣减接口出现超时时,因为用户不知道是否成功,用此编号进行重试或反查。在重试时,使用此编号进行标识防重。

palm_tree: 【数据库扣减方案】第一次升级

举个极端的例子:最新款iphone秒杀,库存只有5件,活动期间峰值QPS预估在10W,活动结束后,上面的流水表最终只会插入5条记录,但是查询的QPS却接近 10W QPS ,读的压力非常大。

所以,数据库扣减方案第一次升级主要是针对 库存前置校验 模块的优化,作为前置拦截器,承载的流量很大,如果将流量全部压到主库上,很容易把数据压垮。我们考虑把数据库架构升级。

万级并发!电商库存扣减如何设计,如何做到不超卖?

 

采用了 读写分离 方式,新增加了一套从库,借助mysql自带的数据同步能力。 库存校验 时读取从数据库。

当然,数据同步有一定的时间延迟,从库的数据新鲜度有一定的滞后性,所以这个 库存校验 结果并不一定准确,但却能拦截大部分的 无效流量 。最终能不能成功购买,由主库的 乐观扣减SQL来控制,并不会影响最终扣减的准确性。大大减轻主库的查询压力。

palm_tree: 【数据库扣减方案】第二次升级

引入了从库,确实能分摊主库很大一部分压力,但是面对秒杀这种万级QPS流量,mysql的 千级TPS 根本支撑不了,需要进一步升级读取的性能。

万级并发!电商库存扣减如何设计,如何做到不超卖?

 

  • 此时引入缓存中间件(如Redis),将mysql的数据定时同步到缓存中
  • 库存校验 模块,从redis中查询剩余的库存数据。由于缓存基于内存操作,性能比数据库高出几个数量级,单台redis实例可以达到10W QPS的读性能

该方案升级后,基本上解决了在前置 库存校验 环节及 获取库存数量接口 的性能问题,提高了系统整体性能,提供较好的用户体验。

补充说明:

如果并发量还是很高的话,可以考虑引入 缓存集群 ,将不同的 秒杀商品sku 尽量均匀分布在多个redis节点中,从而分摊掉整体的峰值QPS压力。(参考缓存热点的解决方案)

数据库方案的优点:

  • ACID 超卖 少买
  • 实现简单,如果项目工期紧张,或者开发资源不足情况下非常适用

数据库方案的不足:

  • 如果参与秒杀的SKU非常多,最后的写操作都是基于 库存主库 ,性能压力会比较大。

palm_tree: 纯缓存扣减方案

Redis采用单线程的事件模型,具有 原子性 的特性。当有多个客户端给Redis发送命令时,Redis会按照接收到的顺序 串行化 执行。对于还未被调度的命令,则放在队列里 排队等待 。

库存扣减为了保证数据并发安全,要求原子性,而 Redis 正好满足扣减类的特殊性要求,是个不错的技术选型。

下面,我们简单来看看基于 Redis 如何来设计库存扣减?

万级并发!电商库存扣减如何设计,如何做到不超卖?

 

首先,设计Redis的数据模型:

剩余库存(k-v结构):
key:sku_leaved_amount_{sku_id}
value:剩余的库存数值

流水(hash结构):
key:inventory_flow_{sku_id}
hash—key:订单明细id(不同业务场景的全局性id,用来做幂等控制)
hash—value:本次购买的数量

对于购物车下单,多个sku批量扣减,我们需要按单个sku循环发起Redis调用。但是多个Redis命令无法保证原子性。我们可以采用 lua脚本 形式,将这些命令打包到一个脚本中,作为一个命令发送给Redis执行,从而保证了原子性。

lua 是一个类似 JAVAScript、Shell 等的解释性语言,它可以完成 Redis 已有命令不支持的功能。用户在编写完 lua 脚本之后,将此脚本上传至 Redis 服务端,服务端会返回一个标识码代表此脚本。在实际执行具体请求时,将数据和此标识码发送至 Redis 即可。Redis 会和执行普通命令一样,采用单线程执行此 lua 脚本和对应数据。

Lua 脚本执行流程:

批量扣减是对单个扣减的循环调用,所以这里介绍的流程只讲单次扣减的处理步骤。

  1. 首先根据 订单明细id 查询扣减流水,是否已经操作过,做幂等性校验
  2. 然后查询sku的剩余库存,并根据 下单购买数 做校验,只要有一个sku 数量不足,则返回失败
  3. 修改所有sku的缓存中的剩余库存数
  4. 缓存中插入扣减流水记录

当Redis扣减成功后,应用程序再将此次扣减 异步化 保存到数据库中,持久化存储,毕竟Redis只是临时性存储,有宕机风险,会丢失数据。

缓存方案利弊分析:

  • Redis 高性能 ACID 少卖
  • 为了避免 少卖 情况发生, 纯缓存方案 需要做大量的对账、异常处理的设计,系统复杂度增加很多。
  • 纯缓存方案 适合一些高并发、大流量场景,但对数据准确度要求不是特别苛刻的业务场景。

风险:

上述 Lua脚本 把多条命令打包在一起,虽然保证了原子性,但不具备 事务回滚 特性。比如,库存扣减成功了,此时 Redis宕机 ,扣减流水并没有插入成功,应用程序认为本次 Redis调用 是失败 的,前台给用户反馈错误提示,但是已经扣减的数量不会回滚。当Redis故障修复后,再次启动,此时恢复的数据已经存在不一致了。需要结合 Redis 和 数据库 做数据核对check,并结合扣减服务的日志,做数据的增量修复。

palm_tree: 基于分库分表的扣减方案

上面提到的数据库方式是基于 单库单表 玩法,虽然借助 ACID 特性能保证数据的一致性,但是单台mysql的并发能力有限,如何提升性能?

除了 纯缓存 化方案外,我们还可以考虑将 库存表 进行 水平拆分 ,分摊洪峰压力。

万级并发!电商库存扣减如何设计,如何做到不超卖?

 

假如库存表的QPS要求是1.6万,经过拆分成16张表后,如果数据分布均匀,每个物理表预计处理 1000 QPS,完全处于mysql单实例的承载范围之内。

另外拆分后,单表的数据量也会相应减少很多,假如分表前有一个亿数据,分表后每张表不到1千万,索引查询性能也会快很多。

注意:

同一次扣减业务,库存扣减和插入流水要放在同一个分库中,通过事务保证一致性,满足同时成功或同时失败。如果数据分布和业务请求足够均匀,理论上经过分库分表设计后,整个系统的吞吐量将会是线性的增长,主要取决于分表实例的数量。

palm_tree: 其他扣减方案

还有其他的一些解决方案,这里只是提供一些思路,方案细节就不展开了

1、如果某个sku_id的库存扣减过热,单台实例支撑不了( mysql官方测评:一般单行更新的QPS在500以内 ),可以考虑将一个sku的大库存拆分成N份,放在不同的库中(也就是说所有子库的库存数总和才是一件sku的真实库存),由于前台的访问流量非常大,按照 均分原则 ,每个子库分到的流量应该差不多。上层路由时只需要在 sku_id 后面拼接 一个范围内的随机数 ,即可找到对应的子库,有效减轻系统压力。

2、单条sku库存记录更新过热,也可以采用批量提交方式,将多次扣减累计计数,集中成一次扣减, 从而实现了将串行处理变成了批处理 ,也可以大大减轻数据库压力。

3、引入 RocketMQ 消息队列,经过前置校验后,如果有剩余库存,则把创建订单的操作封装成消息发送给MQ,订单系统从RocketMQ中以特定的频率消费,创建订单,该方案有一定的延迟性。

原文:
https://mp.weixin.qq.com/s/jJTIBL8unJ-IRbDqgREsCw



Tags:电商库存扣减   点击:()  评论:()
声明:本站部分内容及图片来自互联网,转载是出于传递更多信息之目的,内容观点仅代表作者本人,如有任何标注错误或版权侵犯请与我们联系(Email:2595517585@qq.com),我们将及时更正、删除,谢谢。
▌相关推荐
前言:随着中国消费认知的不断升级,网购走近千家万户,越来越被人们所接受。淘宝、唯品会、考拉、京东、拼多多等逐渐成为我们生活的重要组成部分。除了常规的购物下单外,这些电...【详细内容】
2021-04-28  Tags: 电商库存扣减  点击:(270)  评论:(0)  加入收藏
▌简易百科推荐
摘 要 (OF作品展示)OF之前介绍了用python实现数据可视化、数据分析及一些小项目,但基本都是后端的知识。想要做一个好看的可视化大屏,我们还要学一些前端的知识(vue),网上有很多比...【详细内容】
2021-12-27  项目与数据管理    Tags:Vue   点击:(1)  评论:(0)  加入收藏
程序是如何被执行的  程序是如何被执行的?许多开发者可能也没法回答这个问题,大多数人更注重的是如何编写程序,却不会太注意编写好的程序是如何被运行,这并不是一个好...【详细内容】
2021-12-23  IT学习日记    Tags:程序   点击:(9)  评论:(0)  加入收藏
阅读收获✔️1. 了解单点登录实现原理✔️2. 掌握快速使用xxl-sso接入单点登录功能一、早期的多系统登录解决方案 单系统登录解决方案的核心是cookie,cookie携带会话id在浏览器...【详细内容】
2021-12-23  程序yuan    Tags:单点登录(   点击:(8)  评论:(0)  加入收藏
下载Eclipse RCP IDE如果你电脑上还没有安装Eclipse,那么请到这里下载对应版本的软件进行安装。具体的安装步骤就不在这赘述了。创建第一个标准Eclipse RCP应用(总共分为六步)1...【详细内容】
2021-12-22  阿福ChrisYuan    Tags:RCP应用   点击:(7)  评论:(0)  加入收藏
今天想简单聊一聊 Token 的 Value Capture,就是币的价值问题。首先说明啊,这个话题包含的内容非常之光,Token 的经济学设计也可以包含诸多问题,所以几乎不可能把这个问题说的清...【详细内容】
2021-12-21  唐少华TSH    Tags:Token   点击:(9)  评论:(0)  加入收藏
实现效果:假如有10条数据,分组展示,默认在当前页面展示4个,点击换一批,从第5个开始继续展示,到最后一组,再重新返回到第一组 data() { return { qList: [], //处理后...【详细内容】
2021-12-17  Mason程    Tags:VUE   点击:(14)  评论:(0)  加入收藏
什么是性能调优?(what) 为什么需要性能调优?(why) 什么时候需要性能调优?(when) 什么地方需要性能调优?(where) 什么时候来进行性能调优?(who) 怎么样进行性能调优?(How) 硬件配...【详细内容】
2021-12-16  软件测试小p    Tags:性能调优   点击:(19)  评论:(0)  加入收藏
Tasker 是一款适用于 Android 设备的高级自动化应用,它可以通过脚本让重复性的操作自动运行,提高效率。 不知道从哪里听说的抖音 app 会导致 OLED 屏幕烧屏。于是就现学现卖,自...【详细内容】
2021-12-15  ITBang    Tags:抖音防烧屏   点击:(23)  评论:(0)  加入收藏
11 月 23 日,Rust Moderation Team(审核团队)在 GitHub 上发布了辞职公告,即刻生效。根据公告,审核团队集体辞职是为了抗议 Rust 核心团队(Core team)在执行社区行为准则和标准上...【详细内容】
2021-12-15  InfoQ    Tags:Rust   点击:(24)  评论:(0)  加入收藏
一个项目的大部分API,测试用例在参数和参数值等信息会有很多相似的地方。我们可以复制API,复制用例来快速生成,然后做细微调整既可以满足我们的测试需求1.复制API:在菜单发布单...【详细内容】
2021-12-14  AutoMeter    Tags:AutoMeter   点击:(20)  评论:(0)  加入收藏
相关文章
    无相关信息
最新更新
栏目热门
栏目头条