库存领域核心能力--库存预占 建设实践
导读
本文总结库存领域建设库存预占能力时遇到的问题以及解决方案。感谢京东物流金鹏、孙静、陈瑞同学在本文撰写中提供的内容及帮助!
导读
本文总结库存领域建设库存预占能力时遇到的问题以及解决方案。感谢京东物流金鹏、孙静、陈瑞同学在本文撰写中提供的内容及帮助!
01 库存预占业务概述
在今年的敏捷团队建设中,我通过Suite执行器实现了一键自动化单元测试。Juint除了Suite执行器还有哪些执行器呢?由此我的Runner探索之旅开始了!
消费者拍下商品订单后,库存系统先为该订单预留库存,这个预留库存的动作被称为库存预占。
在系统中,库存预占主要是对库存数据进行扣减操作。例:假如一个商品有5个可用库存,订单购买了1个此商品,库存系统需要把可用库存的数量由5扣减为4。库存预占属于物流核心流程。如果预占能力出问题,可能会导致商品无法正常售卖或者出现超卖。02
库存预占能力建设面临的挑战及应对
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
2.1 性能挑战
多个线程并发对同一个数据库商品数据做库存扣减时,数据库中会加锁来保障数据被正确操作。当商品数据足够【热】时,大量的锁等待会导致性能问题,见下图:
2.1.1 解决方案调研
在业务上允许的情况下,减缓预占操作速度,从而降低热点热度,缓解库存系统的性能压力。见下图:
优点:逻辑简单,改造风险较小。从整个调用链路的角度去优化问题,而不是只优化瓶颈点
(1)商品入库时,将数量拆分为N份,放入N个表或者一个表的N行中
假如单条记录支撑的性能是50单/秒,那么拆分成3份以后,支撑的性能就能提升到100+单/秒。见下图:
优点:上游无感知,改造可控制在库存领域;
缺点:
1)逻辑相对复杂,改造风险高;
热点商品预占的耗时主要集中在数据库操作上,使用处理速度更快的redis缓存来替代数据库来提供预占能力,见下图:
优点:上游无感知,改造可控制在库存领域;
2.1.2 各方案对比及选型
方案 | 是否能无损支持业务 | 实现成本 |
异步限流 | 否,仅无损支持与下单方异步交互的场景 | 低 |
商品库存横向拆分 | 否,会出现有库存但无法预占的情况 | 中 |
缓存抗写流量 | 是 | 高 |
2.1.3 性能优化成果
橙色部分为优化后的结果:
2.2 线程同步问题
问题定义:多个线程操作查询、操作同一个商品的库存,使库存数据混乱
DB预占模式
解决方案:利用mysql事务、行锁机制来避免线程之间互相影响,在sql语句中操作变化量
b、操作库存。根据库存id扣减库存,set 当前库存=当前库存+操作量。该步骤mysql会在id上加互斥锁,避免不同线程之间的互相影响。这里使用批量更新,来提升一单操作多商品的性能
UPDATE stock
SET stock_num = stock_num + CASE id
WHEN 1 THEN 'value1'
WHEN 2 THEN 'value2'
WHEN 3 THEN 'value3'
END
WHERE id IN (1,2,3)
c、校验库存。为了防止超卖,根据库存id查询库存,如果订单中任一商品库存被扣减为小于0,则抛出异常,使用数据库事务机制进行回滚
缓存(redis)预占模式
解决方案:将redis操作放入lua脚本中,利用redis单线程执行以及lua脚本执行过程中不会被其他操作语句插入的特性,避免线程间互相影响
2.3 死锁问题
1、预占流程间死锁。多个订单预占商品,包含多个相同商品,多线程并发请求时,线程之间持有对方依赖的锁,然后等待对方释放自己依赖的锁。见下图:
注:当前物流库存平台需要进行操作的库存数据可以分为仓库库存、逻辑库存、批次库存。其中逻辑库存、批次库存可以看作对某一个仓库库存进行不同维度的拆分。
锁排序,保持锁的顺序一致。在多个事务请求资源的情况下,要保持锁的请求顺序一致,从而保障线程顺序执行。伪代码如下:
public Result handleOccupyRequest(List<CalcOccupyRequest> paramList) {
//XX业务逻辑
//Long类型比较器,根据库存id进行排序
Comparator<Long> comparator = new Comparator<Long>() {
@Override
public int compare(Long o1, Long o2) {
return o1.compareTo(o2);
}
};
//对要操作的各类库存进行排序
if(saleableStockIds!=null){
Collections.sort(saleableStockIds, comparator);
}
if (otherStockIds!=null){
Collections.sort(otherStockIds, comparator);
}
//XX业务逻辑
}
2.4 数据一致性问题
这个问题又可以拆解为:
1、如何从流程处理机制上保障redis-db之间的数据最终一致性?
如何从流程处理机制上保障redis-db之间的数据最终一致性?
初始化流程方案。使用 锁定db库存+redis事务来保障数据一致性。见下图:
数据同步流程。使用mq重试+任务系统兜底来保障同步能完成
万一出现了不一致,如何发现及解决?
引入缓存操作量数据,最大可能地消除同步延迟对数据一致性比对的影响。操作缓存时,将操作量增加到【缓存操作量数据】中,db同步完成后,从【缓存操作量数据】中减去操作量。数据一致性比对公式为:【缓存库存数据】+【缓存操作量数据】=【db库存数据】 使用多次比对来尽量消除剩余延迟的影响。设定比对次数阈值,多次比对中只要有一次判断 缓存-db数据是一致的,则判断为通过一致性校验
针对库存低于一定水位线的商品,每次库存变动后都进行一致性比对,避免因为数据不一致影响业务 库存充足的商品,设定比对时间间隔,在间隔内区间内,只进行一次比对,避免影响系统性能
03 库存预占处理流程
理解,首先 MCube 会依据模板缓存状态判断是否需要网络获取最新模板,当获取到模板后进行模板加载,加载阶段会将产物转换为视图树的结构,转换完成后将通过表达式引擎解析表达式并取得正确的值,通过事件解析引擎解析用户自定义事件并完成事件的绑定,完成解析赋值以及事件绑定后进行视图的渲染,最终将
京东中台化底层支撑框架技术分析及随想AIGC在保险场景中的视觉应用
对号入座,快看看你的应用系统用了哪些高并发技术?