作者:vivo官网商城开发团队 - Xu Yi、Yan Chao
本文是vivo商城系列文章,主要介绍vivo商城库存系统发展历程、架构设计思路以及应对业务场景的实践。
库存系统是电商商品管理的核心系统,本文主要介绍vivo商城库存中心发展历程、架构设计思路及应对各种业务场景的实践。
vivo商城原库存系统耦合在商品系统,考虑到相关业务逻辑复杂度越来越高,库存做了服务拆分,在可售库存管理的基础上新增了实物库存管理、秒杀库存、物流时效 、发货限制、分仓管理等功能,满足了商城库存相关业务需求。
本文将介绍vivo商城库存系统架构设计经验以及一些问题的解决方案。
根据vivo大电商的销售渠道与业务场景可以将库存业务架构分为3个层级:仓库层、调度层以及销售层。
仓库层对应实体仓库,包括自营仓库、顺丰仓等第三方仓库以及WMS系统、ERP系统等;调度层负责库存调度与订单发货管理;销售层包含多个服务终端,vivo官方商城、vivo门店、第三方电商分销渠道等。其分层结构如图所示:
本文探讨的vivo官方商城库存架构设计,从整个vivo大电商库存架构来看,vivo官方商城库存系统涉及销售层内部架构以及销售层与调度层的交互。
早期商城的库存冗余在各业务系统中,如可售库存在商品系统、活动库存在营销系统等,库存流转也只有扣减与释放,无法针对库存进行整合与业务创新,存在诸多限制:
基于上述限制与产品期望,21年库存系统完成初版架构设计,此后系统不断迭代完善,形成当前的系统架构:
库存系统提供两个核心能力:交易能力和库存管理。上层业务方可以调用提供的API完成库存查询、库存扣减等操作;管理台可以按成分仓策略、库存同步等操作。
库存系统一共包含4类库存:可售库存、实物库存、预占库存、活动库存。
基于不同类型库存,可以构建一个简单的库存分层体系:
库存中心还维护了仓库信息、分仓策略、仓库实物库存信息等等:
商品库存流转涉及两个主要操作:正向库存扣减、逆向库存回退,整套库存变更流程如下:
对于库存扣减,目前常见有两种库存扣减方案:
(1)下单时扣库存。
(2)支付时扣库存。
从用户体验考虑,我们采用的是下单时扣库存 + 回退这种方案。
下单时扣减库存,但只保留一段时间(比如15分钟),保留时间段内未支付则释放库存,避免长时间占用库存。
库存回退基于库存变更日志逐个回退。
库存回退基本流程:订单出库前用户申请退款,回退可售库存、回退预占库存、软删除扣减日志、增加回退日志;一旦商品出库,用户申请退货走处理机流程,可售库存和实物库存均不回退。
库存系统还提供了一系列定制辅助功能:分仓策略、发货限制、物流时效等等。
(1)分仓策略
为了给用户更快的发货,我们采用的是分仓策略,即由最近的仓库(存在优先级)给用户发货;同时存在备选仓库,当所有仓库无实物库存时可走备选仓库。
发货限制分地区限制时间限制。
根据用户收货地址,基于分仓策略确定发货地址,再基于发货时效确定发货时间,提升用户体验。
订单重复提交会导致库存重复扣减,比如用户误提交、系统超时重试等,针对此类问题有如下常见解决方案:
本系统采用的是保证接口幂等性的方案。
在库存扣减接口入参中增加订单序列号作为唯一标识,库存扣减时增加一条扣减日志。当接口重复请求时,会优先校验是否已经存在扣减记录,如果已存在则直接返回,避免重复扣减问题,具体流程如下:
常规下单渠道流量小且对超卖风险厌恶度极高,常用的防超卖方案有:
方案一: 直接数据库扣减。通过sql判断剩余库存是否大于等于待扣库存,满足则扣减库存。该方案利用乐观锁原理即update的排他性确保事务性,避免超卖。
伪代码sql:
sql:update store set store = store - #{deductStore } where (store-#{deductStore }) >= 0
该方案的优点是:
该方案的缺点是:
方案二: 利用分布式锁,强制串行化扣减同一商品库存。
该方案的优点是:
减轻数据库压力,同时还能确保不会超卖。
该方案的缺点是:
每次只能有一个请求抢占锁,不能应对高并发场景。
对于常规渠道,库存扣减是后置逻辑,流量不高,我们采用的是直接数据库扣减,且针对弊端做了一些措施:
针对高并发库存扣减,比如秒杀,一般采用的是缓存扣减库存的方式(redis+lua脚本实现单线程库存更新)作为前置流程,代替数据库直接更新。
在redis中扣减库存虽然性能高,可以大大减轻数据库压力,但需要保证缓存数据能完整、正确的入库,以保证最终一致性。
针对缓存数据更新至数据库,目前主流方案有两种:
方案一:Redis数据直接异步更新至数据库。
优点:简单、没有复杂的流程。
缺陷:redis宕机或者故障,可能会造成缓存内库存数据的丢失。
方案二:Redis扣减库存时,同步在业务数据中insert库存信息。
这里大家可能会有两个疑问:
对于疑问1:由于数据库insert比update性能优,insert是在表的末尾直接插入,没有寻址的过程,可以保证性能比较快。
对于疑问2:方案2不同于缓存直接扣减,而是把缓存扣减放在数据库insert的事务内,通过数据库的事务保证整体的事务。
insert的表被称为库存任务表,其中保存了库存扣减的信息,库存任务表结构可以设计的非常简单,主键 + 库存信息(json字符串)就可以了。
后续通过异步任务,从库存任务表表中查询出库存更新信息,将其同步到具体的库存表中,实现最终一致性,这种方案可以避免数据的丢失。
对于疑问3:库存回退是根据业务库中扣减记录进行回退的,由于异步更新业务库必定存在延迟(延迟极低,数秒以内),所以极端场景会存在走退款逆向流程时业务库的库存扣减记录还未更新。
针对这种情况库存回退设置延迟重试机制,如果再极端点达到重试阈值依旧没有扣减记录,则返回回退成功,不做阻断。
目前我们针对秒杀库存扣减,采用的是方案2。但毕竟涉及数据库的更新,为了避免风险,在前置流量校验上做了限制,保证流量的可控:
什么是热点问题?热点问题就是因热点商品导致的redis、数据库等性能瓶颈。在库存系统中,热点问题主要存在:
对于第1种热点问题,在vivo商城常见的场景是:新发的爆品手机,在准点售卖时会有抢购效应,容易造成库存数据库单行的瓶颈问题。针对这种热点问题,我们的解决方案是“分而治之”:
对于潜在的热点爆款手机,我们会将库存平均分为多行(比如M行),扣减库存时,随机在M行中选取一行库存数据进行扣减。该方案突破了数据库单行锁的瓶颈限制,解决了爆款商品的热点问题。
对于第2种redis单片热点问题,解决方案也是分而治之。将数据库中的库存数据同步到redis时,把key值打散,分散在多个redis单片中。注:我们目前线上的流量峰值还达不到会造成redis单片瓶颈的问题,为避免过度设计,只做了前置限流,没有进行key值的打散。
库存系统存在一些库存同步场景:
(1)实物库存同步:
实物库存同步,对接的是仓储系统,通过接口来获取商品的实际库存。实物库存同步分成两种:定时全量同步、指定单品更新。
(2)商品系统库存同步:
由于库存系统多个场景涉及库存变更,运营手动编辑、用户下单退款导致库存扣减回退,还有商品系统内编辑库存数据也会导致库存变更(以前库存系统未独立,库存数据维护在商品系统)。同时很多业务在查询库存时,参考的依旧是商品系统的库存数据。
这里有一个问题:库存系统已经独立出来,为什么还会依赖商品系统的库存数据?
这有两点原因:
因此,我们需要保证商品系统和库存系统两边库存数据的一致。
库存变更场景多,为了降低业务复杂度、采用简单的方式实现库存同步,我们利用了团队自研的CDC系统(鲁班平台),整体流程如下:
库存数据库发生变更后,鲁班平台通过binlog采集获取库存变更日志,再通过自定义规则筛选,然后发送mq变更消息,最后商品系统消费消息完成库存同步变更。
最后对库存系统进行一个总结:
库存系统完成服务拆分,在单一的可售库存扣减功能基础上拓展了很多功能,赋能业务的发展。 完成库存架构分层,抽象多个库存类型,更灵活地满足当前业务需求。 针对库存扣减防重、高并发场景下的库存扣减、库存热点问题、库存同步等技术问题,我们根据业务实际情况设计合理方案。
展望
目前vivo商城库存系统平台化能力不足,部分能力分散在其他系统中,未来我们希望能为vivo新零售提供一体化的库存管理方案。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。