库存预占架构升级方案设计-交易库存中心 | 京东物流技术团队
背景介绍
伴随物流行业的迅猛发展,一体化供应链模式的落地,对系统吞吐、系统稳定发出巨大挑战,库存作为供应链的重中之重表现更为明显。近三年数据可以看出:
接入商家同比增长37.64%、货品种类同比增长53.66%
货品数量同比增长46.43%、仓库数量同比增长18.87%
通过分析过往大促流量,分钟级流量增长率为75%,大促仓内反馈三方订单下传不及时,库存预占吞吐量和性能是导致订单积压因素之一。目前库存使用mysql数据库作为接单预占的扛量手段,随着一体化供应链建设以及重点KA商家不断接入,现有库存架构在业务支撑上存在风险和缺陷。
此外未来3到5年业务增长、流量增长预计增长5-10倍。为避免系统性能和技术架构缺陷导致业务损失,轻量级库存架构势在必行。
// 名词解释:
库存预占:是指消费者拍下商品订单后,库存先为该订单短暂预留,预留的库存即为预占库存。
架构原则
架构:是⾯向问题,解决问题的手段。 库存系统的问题: 非功能性:1.高并发 2.系统稳定性(容灾) 3.数据一致性 功能性: 1.业务复杂 2.数据一致性
系统设计
设计思路
- 当前库存系统瓶颈在哪里?:抗写流量,数据库成为瓶颈点。
- 如何解决系统瓶颈?:由高并发组件Redis替代数据库。
- 利用Redis需要解决哪些问题?:防超卖,异步写数据库保证最终一致性。
总体设计
- 扛量部分:库存性能瓶颈在预占,传统架构主要依靠数据库事务保持数据一致以及数据读写;新版架构设计将数据扛量部分移植到Redis,利用Redis高性能吞吐解决高并发场景下数据读写。
- 数据回写:Redis进行扛量削峰,后续数据仅用于记账,最终牺牲数据的短暂一致性达到削峰的目的。
- 差异部分:老版本库存预占设计仅依靠数据进行数据处理,新版设计依靠切量配置建数据切换到Redis,利用Redis高读写进行削峰操作。
详细设计
- 主流程:
- 库存初始化:竞态条件利用Redis watch命令来实现锁等待,解决并发场景数据不一致问题。
- LUA执行器:将原子操作指令/复用指令封装到LUA脚本中以减少网络开销。
- 补偿机制:i> 执行流程中所有业务异常发生时会同步发起反向操作请求;ii> 反向操作执行异常后会提交异步反向操作任务;**iii>**异步任务执行异常后,依赖监q控系统扫描异常单据或异常库存并修改异常库存量
- 回溯回写:任务落库后发出mq组装参数调用数据回写服务,数据回写服务操作库存数量;同时回写redis数据,释放预占量库存数据;更新任务库数据状态
数据结构
- 库存记录索引:{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可用量过期,同时矫正预占量
容灾方案
// 对系统容错/降级、监控机制(空间换稳定性,两份redis,故障3次丢数),流量分布材料,618流量大、峰值数据切量。数据不一致,多个商家,不能超过5分。
预占任务持久化:mysql需要将核心属性字段数据持久化:事业部,商品编码,仓编码,等级,库存类型,库存状态,预占库存量,任务状态;调度执行完成后需要更新stockTask状态为完成
初始化:
(1) lock db
(2) sum stockTask
(3)使用DB可用库存初始化Redis可用库存,stockTask预占量初始化Redis预占量
(4)Redis库存回滚,如果预占量key不存在,该key不需要回滚
性能结果
23年618大促
切量细则
切量细则
冷热数据
OMS库存冷热装置
预占架构升级切量重点key监控
库存预占架构升级切量商家
架构升级切量商家明细2
已切量商家
反向切量
原有设计中存在以下名单
禁止切量商家:优先级较高,一旦在名单中,禁止切量
批次库存商家:批次库存管理商家,目前该部分能力尚未建设
动态质押商家:物流金融业务,目前该部分能力尚未建设 切量名单商家:该部分为切量商家
原有切量流程:!禁止切量->!批次库存->!动态质押->切量名单中,通过以上校验为切量商家。
原有流程在增量商家中需要手动将商家配置到切量名单中才可进行切量操作,对于新增商家场景操作不变,且原有流程中逻辑库存名单为痛点:逻辑库存的启用配置在事业部主数据中,不在库存侧。
新版切量流程中对切量名单进行优化,将原来切量名单商家拆分成非逻辑库存名单、逻辑库存两个名单,其中:
非逻辑库存名单:包含可切量商家
逻辑库存名单:逻辑库存商家,该部分不可切量
原流程新流程对切量商家名单进行优化,拆分成非逻辑库存名单、逻辑库存两个名单
构建模型(批次库存&内存模型待续)
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 转载请注明来源

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
视频监控集中存储如何做?它的难点在哪?
视频监控集中存储是指将多个视频监控摄像头所捕捉到的视频信号集中存储于一个中央设备,这个中央设备可以是服务器、网络存储设备或其他专用设备。通过集中存储,可以避免因为存储设备分散而导致的管理不便和难以有效地管理和检索视频数据,同时也可以大幅度降低视频存储成本。 视频监控集中存储方案一般包括两个主要的组成部分:视频存储设备和管理软件(EasyCVR)。视频存储设备通常采用高性能硬盘阵列或网络存储设备,可以根据需要进行扩展。管理软件(EasyCVR)可以根据不同的需求定制,一般包括视频数据的录制、存储、查看、回放和管理等功能,还可以实现对视频数据的远程访问和控制。 以我们的安防监控视频汇聚平台EasyCVR为例子,视频集中存储EasyCVR平台可拓展性强、视频能力灵活、部署轻快,可支持的主流标准协议有国标GB28181、RTSP/Onvif、RTMP等,以及支持厂家私有协议与SDK接入,包括海康Ehome、海大宇等设备的SDK等。 安防监控视频云存储平台EasyCVR既具备传统安防视频监控的能力,比如:视频汇聚管理、视频监控直播、云端录像、视频云存储、视频集中存储与管理、录像与回看、智能告警、...
- 下一篇
千万级数据深分页查询SQL性能优化实践 | 京东云技术团队
一、系统介绍和问题描述 如何在Mysql中实现上亿数据的遍历查询?先来介绍一下系统主角:关注系统,主要是维护京东用户和业务对象之前的关注关系;并对外提供各种关系查询,比如查询用户的关注商品或店铺列表,查询用户是否关注了某个商品或店铺等。但是最近接到了一个新需求,要求提供查询关注对象的粉丝列表接口功能。该功能的难点就是关注对象的粉丝数量过多,不少店铺的粉丝数量都是千万级别,并且有些大V粉丝数量能够达到上亿级别。而这些粉丝列表数据目前全都存储在Mysql库中,然后通过业务对象ID进行分库分表,所有的粉丝列表数据分布在16个分片的256张表中。同时为了方便查询粉丝列表,同一个业务对象的所有粉丝都会路由到同一张表中,每个表的数据量都能够达到 2 亿+。 二、解决问题的思路和方法 数据库表结构示例如下: CREATE TABLE follow_fans_[0-255] ( id bigint(11) NOT NULL AUTO_INCREMENT COMMENT '自增id', biz_content VARCHAR(50) DEFAULT NULL COMMENT '业务对象ID', ...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- Red5直播服务器,属于Java语言的直播服务器
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7,8上快速安装Gitea,搭建Git服务器
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- SpringBoot2全家桶,快速入门学习开发网站教程
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合Redis,开启缓存,提高访问速度
- SpringBoot2编写第一个Controller,响应你的http请求并返回结果
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- CentOS8,CentOS7,CentOS6编译安装Redis5.0.7