首页 > 其他分享 >库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队

时间:2023-08-23 12:07:43浏览次数:45  
标签:方案设计 库存 Redis 预占 logicWarehouseStock md5Key 切量 append

背景介绍

伴随物流行业的迅猛发展,一体化供应链模式的落地,对系统吞吐、系统稳定发出巨大挑战,库存作为供应链的重中之重表现更为明显。近三年数据可以看出:

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_架构升级

接入商家同比增长37.64%、货品种类同比增长53.66%

货品数量同比增长46.43%、仓库数量同比增长18.87%

通过分析过往大促流量,分钟级流量增长率为75%,大促仓内反馈三方订单下传不及时,库存预占吞吐量和性能是导致订单积压因素之一。目前库存使用mysql数据库作为接单预占的扛量手段,随着一体化供应链建设以及重点KA商家不断接入,现有库存架构在业务支撑上存在风险和缺陷。

此外未来3到5年业务增长、流量增长预计增长5-10倍。为避免系统性能和技术架构缺陷导致业务损失,轻量级库存架构势在必行。

// 名词解释:

库存预占:是指消费者拍下商品订单后,库存先为该订单短暂预留,预留的库存即为预占库存。

架构原则

架构:是⾯向问题,解决问题的手段。 库存系统的问题: 非功能性:1.高并发 2.系统稳定性(容灾) 3.数据一致性 功能性: 1.业务复杂 2.数据一致性

系统设计

设计思路

  1. 当前库存系统瓶颈在哪里?:抗写流量,数据库成为瓶颈点。
  2. 如何解决系统瓶颈?:由高并发组件Redis替代数据库。
  3. 利用Redis需要解决哪些问题?:防超卖,异步写数据库保证最终一致性。

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_架构升级_02

总体设计

  • 扛量部分:库存性能瓶颈在预占,传统架构主要依靠数据库事务保持数据一致以及数据读写;新版架构设计将数据扛量部分移植到Redis,利用Redis高性能吞吐解决高并发场景下数据读写。
  • 数据回写:Redis进行扛量削峰,后续数据仅用于记账,最终牺牲数据的短暂一致性达到削峰的目的。
  • 差异部分:老版本库存预占设计仅依靠数据进行数据处理,新版设计依靠切量配置建数据切换到Redis,利用Redis高读写进行削峰操作。

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_交易库存中心_03

详细设计

  • 主流程:

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_架构升级_04

  • 库存初始化:竞态条件利用Redis watch命令来实现锁等待,解决并发场景数据不一致问题。
  • LUA执行器:将原子操作指令/复用指令封装到LUA脚本中以减少网络开销。
  • 补偿机制:i> 执行流程中所有业务异常发生时会同步发起反向操作请求;ii> 反向操作执行异常后会提交异步反向操作任务;\*\iii>\\*异步任务执行异常后,依赖监q控系统扫描异常单据或异常库存并修改异常库存量

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_架构升级_05

  • 回溯回写:任务落库后发出mq组装参数调用数据回写服务,数据回写服务操作库存数量;同时回写redis数据,释放预占量库存数据;更新任务库数据状态

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_数据_06

数据结构

  • 库存记录索引:{deptNo|goodsNo|warehouseNo}|stockStatus|stockType|goodsLevel
  • hashTag:{deptNo|goodsNo|warehouseNo}|stockStatus|stockType|goodsLevel
  • 可售库存数量:usableKey:{库存记录索引}
  • 扣减库存量:usableSubtractKey:{库存记录索引} ,记录Redis到DB执行期间减库存量
  • 预占防重key:operateKey:{库存记录索引:单号} 防重key防并发重复请求
  • 回滚防重:rollbackOperateKey:{库存记录索引}
  • 缺量预占库存量:ullageOperateKey:{库存记录索引}
  • 扣减库存单据记录:hSetrecord: {库存记录索引}

key

预占

缺量预占

回滚

回写

可售库存数量

-

-

+

不变

扣减库存量

+

+

-

-

预占防重key

+

+

-

不变

回滚防重

不变

不变

+

不变

缺量预占库存量

不变

+

反向

不变

扣减库存单据记录

+

+

-

-

Redis&DB

  • 首先进行redis&从库数据比对,若存在差异则对主库进行校验
  • 比对过程中,DB中sku明细行进行锁定(for update),比对逻辑为DB可用库存量==(Redis可用库存量+Redis预占量)
  • 有差异,报警且触发SDK可用量过期,同时矫正预占量

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_回滚_07

容灾方案


库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_架构升级_08

// 对系统容错/降级、监控机制(空间换稳定性,两份redis,故障3次丢数),流量分布材料,618流量大、峰值数据切量。数据不一致,多个商家,不能超过5分。

预占任务持久化:mysql需要将核心属性字段数据持久化:事业部,商品编码,仓编码,等级,库存类型,库存状态,预占库存量,任务状态;调度执行完成后需要更新stockTask状态为完成

初始化:

(1) lock db

(2) sum stockTask

(3)使用DB可用库存初始化Redis可用库存,stockTask预占量初始化Redis预占量

(4)Redis库存回滚,如果预占量key不存在,该key不需要回滚

性能结果


库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_交易库存中心_09

23年618大促

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_交易库存中心_10

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_Redis_11

切量细则

切量细则

冷热数据

OMS库存冷热装置

预占架构升级切量重点key监控

库存预占架构升级切量商家

架构升级切量商家明细2

已切量商家

反向切量

原有设计中存在以下名单

禁止切量商家:优先级较高,一旦在名单中,禁止切量

批次库存商家:批次库存管理商家,目前该部分能力尚未建设

动态质押商家:物流金融业务,目前该部分能力尚未建设 切量名单商家:该部分为切量商家

原有切量流程:!禁止切量->!批次库存->!动态质押->切量名单中,通过以上校验为切量商家。

原有流程在增量商家中需要手动将商家配置到切量名单中才可进行切量操作,对于新增商家场景操作不变,且原有流程中逻辑库存名单为痛点:逻辑库存的启用配置在事业部主数据中,不在库存侧。

新版切量流程中对切量名单进行优化,将原来切量名单商家拆分成非逻辑库存名单、逻辑库存两个名单,其中:

非逻辑库存名单:包含可切量商家

逻辑库存名单:逻辑库存商家,该部分不可切量

原流程新流程对切量商家名单进行优化,拆分成非逻辑库存名单、逻辑库存两个名单

构建模型(批次库存&内存模型待续)

库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队_交易库存中心_12

Redis存储数据结构

  • MD生成规则工具集

◦逻辑库存MD5工具

StringBuffer md5Key = new StringBuffer();
     md5Key.append(logicWarehouseStock.getGoodsNo()+"_"+logicWarehouseStock.getWarehouseNo()+"_"+logicWarehouseStock.getOwnerNo()+
             "_"+logicWarehouseStock.getDeptNo()+"_"+logicWarehouseStock.getStockType()+"_"+logicWarehouseStock.getGoodsLevel());
     if(StringUtils.isBlank(logicWarehouseStock.getFactor1())){
         md5Key.append("_0");
     }else {
         md5Key.append("_"+logicWarehouseStock.getFactor1());
     }
     if(StringUtils.isBlank(logicWarehouseStock.getFactor2())){
         md5Key.append("_0");
     }else {
         md5Key.append("_"+logicWarehouseStock.getFactor2());
     }
     if(StringUtils.isBlank(logicWarehouseStock.getFactor3())){
         md5Key.append("_0");
     }else {
         md5Key.append("_"+logicWarehouseStock.getFactor3());
     }
     if(StringUtils.isBlank(logicWarehouseStock.getFactor4())){
         md5Key.append("_0");
     }else {
         md5Key.append("_"+logicWarehouseStock.getFactor4());
     }
     if(logicWarehouseStock.getYn()== null){
      md5Key.append("_1");
     }else {
         md5Key.append("_"+logicWarehouseStock.getYn());
     }
     md5Key.toString().hashCode()
  • 批次库存MD5工具
public void fillMd5Value(){
        StringBuffer md5Key = new StringBuffer();
        md5Key.append(warehouseNo);
        md5Key.append("_");
        md5Key.append(goodsNo);
        md5Key.append("_");
        md5Key.append(goodsLevel);
        md5Key.append("_");
        md5Key.append(stockType);
        //遍历类字段不遍历map是为了控制MD5的组成顺序
        Class clazz = BatchAttrStock.class;
        Field[] fields = clazz.getDeclaredFields();
        try {
            int batchFieldCount = 0 ;
            for (Field field : fields){
                BatchAttrEnum attrEnum = BatchAttrEnum.batchFieldEnumMap.get(field.getName());
                //不是批属性的字段不进入MD5的组成
                if (attrEnum == null){
                    continue;
                }
                batchFieldCount ++;
                field.setAccessible(true);
                Object value = field.get(this);
                if (value == null ){
                    md5Key.append("0");
                    continue;
                }
                if(field.getType().toString().contains("String")){
                    md5Key.append(value);
                    continue;
                }
                if(field.getType().toString().contains("Date")){
                    Date timeField = (Date) value;
                    md5Key.append(timeField.getTime());
                    continue;
                }
                throw new RuntimeException(attrEnum.getField()+"填充MD5异常");
            }
            //默认50个批属性长度,长度不够0补齐
            int remainLength = 50 - batchFieldCount;
            String str = String.format("%0"+remainLength+"d", 0);
            md5Key.append(str);

        }catch (Exception e){
            throw new RuntimeException("填充MD5异常.");
        }

        md5Key.append(yn);
        String md5Value =  MD5Util.md5(md5Key.toString());
        setMd5Value(md5Value);
    }
  • MD&ID&属性保存工具

本文篇幅有限,余下二期进行分享。

作者:京东物流 金鹏

来源:京东云开发者社区 自猿其说Tech 转载请注明来源

标签:方案设计,库存,Redis,预占,logicWarehouseStock,md5Key,切量,append
From: https://blog.51cto.com/u_15714439/7200773

相关文章

  • Redis 实现库存扣减操作
    在日常开发中有很多地方都有类似扣减库存的操作,比如电商系统中的商品库存,抽奖系统中的奖品库存等。解决方案使用mysql数据库,使用一个字段来存储库存,每次扣减库存去更新这个字段。还是使用数据库,但是将库存分层多份存到多条记录里面,扣减库存的时候路由一下,这样子增大了并发量,但是还......
  • 浅析基于视频汇聚与AI智能分析的新零售方案设计
    一、行业背景近年来,随着新零售概念的提出,国内外各大企业纷纷布局智慧零售领域。从无人便利店、智能售货机,到线上线下融合的电商平台,再到通过大数据分析实现精准推送的个性化营销,智慧零售的触角已经深入各个零售场景。最近在视频汇聚业务项目对接中,关于智慧零售的项目比较多,主要集中......
  • 秒杀库存解决方案
    秒杀库存解决方案电商系统中秒杀是一种常见的业务场景需求,其中核心设计之一就是如何扣减库存。本篇主要分享一些常见库存扣减技术方案,库存扣减设计选择并非一味追求性能更佳,更多的应该考虑根据实际情况来进行架构取舍。在商品购买的过程中,库存的抵扣过程通常包括以下步骤:开启事......
  • 在库存数量的条件约束下,求满足产品需求的材料投入数量矩阵X的所有可行解,即BX=AC,求X
    问题有r种元素,某产品的元素构成比例为矩阵A;有n种材料,元素构成比例为矩阵B;已知该产品的需求量为C,材料的库存数量为矩阵D;在库存数量的条件约束下,求满足产品需求的材料投入数量矩阵X的所有可行解,即BX=AC,求X的所有可行解.提示当元素数量r=材料数量n时,有唯一解当r<n时,有多个......
  • 体育馆可视化安防系统解决方案设计与应用
    体育馆可视化安防系统解决方案设计与应用一、方案背景近日随着成都大运会赛场上的精彩落幕,以及即将到来的杭州亚运会的即将开幕,大型体育赛事一方面给人们带来精神文化的享受,另一方面由于大型赛事观赛人数较多、现场突发情况多变、信息高度密集等原因,导致体育馆现场情况多发,因此可视......
  • odoo 库存科目设置2
    1   ......
  • OpenERP的实时库存价值
    OpenERP的Product的AccountTab页有个字段:库存价值(InventoryValuation),该字段如果选择RealTime(automated),系统会实时计算库存价值。为了实时计算各产品和各库位的实时库存价值,需要设置几个Account的字段:StockInputAccount:140200在途物资StockOutputAccount:14070......
  • java 电商 订单 商品 库存 数据库表设计
    Java电商订单商品库存数据库表设计在一个电商平台中,订单和商品库存是非常重要的概念。订单用于记录用户下单购买的商品信息,而商品库存用于管理商品的数量和状态。在设计数据库表时,我们需要考虑订单和商品库存的关系以及数据的一致性。订单表设计订单表用于存储用户下单购买的商......
  • 消息中间件 MQ 企业级方案设计
    第1部分:异步通信与负载均衡 引言WebsphereMQ是IBM功能强大的消息传送中间件产品,它以其成熟的技术和世界领先的产品向我们提供了的功能丰富、可靠易用的异构平台间实现可靠信息传递的成熟解决方案。使用MQ消息传递产品可以帮助业务应用在不同种类平台上交换信息,以消......
  • 揭秘|来看看袋鼠云数栈内部的资产血缘方案设计与实现
    数据资产现在需要接入数栈内部相关应用的时候,支持查看血缘的类型从表、离线任务增加到需要表、离线任务、实时任务、API任务、指标、标签等,需要支持数栈现有的所有应用任务,最终实现在数据资产平台查看任务的完整应用链路。虽然增加不同的任务,现阶段资产实现的血缘大体上能够满足......