16

严选库存中心性能优化

 4 years ago
source link: http://mp.weixin.qq.com/s?__biz=MzA5NTE1ODQyOQ%3D%3D&%3Bmid=2247483965&%3Bidx=1&%3Bsn=bf22cf4d541ea8b4ff5ff35504595339
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
NzeqaeJ.gif

性能是技术同学时常关注的问题,但在不同的业务模式下、业务板块中开展的优化手段又不尽相同。本文主要介绍基于严选现有业务模式,在未显著降低数据一致性的情况下如何进行库存的性能优化以满足大促活动的要求。

背景介绍

库存中心服务曾经在2018大促容量评估时遇到性能瓶颈,虽然通过一系列优化降级措施挺过了2018年大促,但仍然担忧在下一年的大促到来之际是否能够继续保持稳定,于是在2019年初开展了性能优化项目,在这里介绍一下严选库存中心性能优化的思路。

VRfU32A.jpg!web

如上图所示,简单展示了前台系统对“库存中心”服务的依赖关系。大促期间来自于前台的流量分为查询和更新两类,其中查询流量主要用于获取实时库存剩余量,一般采用缓存进行优化,在这里不做过多的介绍。库存更新流量以严选主站业务场景为例:

  • 用户点击下单 -> 订单占用库存

  • 用户支付订单 -> 完成订单库存扣减

  • 用户取消订单 -> 将占用库存归还到可售库存中继续售卖

以上三种业务场景中 “用户点击下单” 必须同步调用库存中心,原因是因为抢占库存能否成功是用户下单成功的必要条件。而在电商平台中库存性能往往又容易成为瓶颈。

为什么不能通过扩容来提升性能?

特定的业务场景决定

如下方左图所示,一般电商平台会采用分布式数据库来支撑数据和流量压力,无论是店铺

ID还是用户ID都适合在相应的业务场景下作为数据分片键,并且不太会产生业务数据竞争。 举一个栗子:
用户添加购物车时,后台需要修改购物车中的商品数量信息、计算购物金额等。而该场景下的一次更新请求只会操作一个购物车或者是一个用户ID下的购物车,一般不会存在跨用户操作的场景。因此,基于用户ID进行数据分片时,可以保证相关的数据写操作只落到分布式数据库的单个节点,不会产生XA事务。并且由于每个用户只操作自己的购物车,购物车之间是独立的,也不会存在业务数据的资源抢占。

qQjqQby.jpg!web

而库存的业务场景天然的会产生数据竞争(资源抢占)。在没有活动规则限制的理论情况下,任何一个用户、任何一个订单可以对任何一个商品进行下单,订单下的商品可以随机的组合。

如右图所示,两个订单 order_a 和 order_b,分别需要完成各自列表中的库存扣减,为了保证扣减过程中的数据一致性使用数据库的刚性事务作保护:
  • 场景一:假定在a、b两个订单竞争sku_005的库存时b获得了先机,那么a需要等待b完成sku_005之后所有库存更新操作并提交事务后才能开始自己的库存更新

  • 场景二:假定a在竞争中获胜,那么b也需要等待a完成sku_005之后的数据更新

  • 场景三:场景二中,订单b在等待sku_005时已经完成sku_004的库存扣减,如果此时还有一个订单c在等待sku_004,那么将变成库存扣减顺序关系将变成 c -> b -> a(如果此时c也占用了资源,依赖关系可以类推到d)

如果我们切换到分布式数据库,按照数据分片键的选定规则,skuId是一个比较理想的分片键,在上面的分析中假定以skuId作为分片键带入库存的业务场景会怎么样?

不同sku的库存可能会落到不同的数据库节点上,库存扣减过程引入XA事务。由于XA事务消耗资源较多,上述场景中同样等待库存扣减的步骤等待时间会进一步拉长,性能下降,甚至在集中某几个商品促销时会出现严重的性能问题。

可以发现因为订单和sku互相组合,而sku的库存又是公共资源,很容易形成不同订单之间的库存扣减依赖关系,数据库的锁竞争激烈吞吐量较低。而实际电商业务场景中,这种订单和sku的组合又是无法避免的。

业界有没有可以借鉴的方案?

在这里列举几种用的比较常见的优化方式:

A、在内存中维护库存

使用缓存是性能优化中比较常见的方案,缓存能够获得比传统数据库高出很多倍的性能。但是由于缓存服务没有事务特性,库存数据的一致性需要由应用服务自己来保证,因此在库存扣减过程中任何异常造成的应用服务宕机或重启都有可能造成库存数据异常。某些平台在这种场景下会通过订单状态来校准库存状态,很遗憾的是在严选目前的体系中还没有能够完整、准确的维护所有订单的状态(这里指的是跟库存扣减相关的状态)。如果想要采用此方案,必须先推动全网的订单库存状态管理,因此,该方案在严选落地难度较大。

B、以非事务的方式运行

以非事务方式运行,由于没有事务维度的资源抢占,从性能上来说会比有事务的方案要好。但是其实与缓存方案仍然具有同样的问题,只不过以非事务方式直接运行在关系型数据库上,可以避免内存中数据丢失的风险。

C、使用性能更好的数据库

有些电商平台会使用Orcale,还有些公司会采用自研的数据库以获得更好的性能,但目前没有听说过这方面的计划。

D、根据业务分库

在一些C2C的电商平台中,sku是店铺下的概念,不同店铺之间不会公用sku。所以这种业务模式下,可以按照店铺天然的去拆分访问压力。可是这个方案不适用于严选,严选本身就相当于一个店铺,用户可以在一个订单中购买严选平台上的任何一个商品,无法进行拆分。

库存中心是怎么优化的?

从前面的介绍可以看出数据一致性非常重要,甚至是比性能更重要的指标。在库存业务场景下,如果用户下单、取消产生库存数据问题就会造成电商平台的少卖、超卖,进而影响严选的口碑。那有没有兼顾一致性和性能的方案呢?

答案是肯定的。从上一节中的“B”进行延伸思考,非事务状态下行锁竞争比较弱但不能保证数据一致性,那么可以考虑在多个sku库存扣减时构建一个柔性事务,用这个柔性事务去保证最终数据一致。

AvmM3uz.jpg!web

如上图所示,将刚性事务拆到sku粒度,在每个sku进行库存扣减完成时立即提交这个事务、释放锁资源,而整个订单的一致性由全局的柔性事务来保证。如果在执行到sku_003时因库存不足造成扣减失败,那么事务组件会自动回滚之前已经扣减库存的sku_001、sku_002。这种回滚是基于业务场景来实现,即将之前已经扣减完成的库存重新加回去。

RnEnmii.jpg!web

再来看一下前面提到的三种场景

  • 场景一:假定在a、b两个订单竞争sku_005的库存时b获得了先机,那么a只需要等b完成sku_005之后即可完成自己的sku_005库存扣减

  • 场景二:假定a在竞争中获胜,那么b也只需要等待a完成sku_005的库存扣减

  • 场景三:场景二中,订单b在等待sku_005时已经完成sku_004的库存扣减,并且完成sku_004的资源释放,如果此时还有一个订单c在等待sku_004,么订单c无需等待可以直接扣减sku_004的库存(也不会存在因为订单c已抢占资源进而影响订单d的问题,除非订单d刚好也在扣减sku_004)

无论用户订单列表有多大,库存扣减的资源等待只取决于当前这个sku有没有同时被其他订单扣减。

完成上面的内容已经能够解决单节点DB内的资源抢占问题,但是如果客户端压力很大,单节点很快就会暴露出新的性能问题。将库存数据模型按照skuId做分片键,迁移到DDB中。由于刚性事务已经被拆分到skuId粒度,故对于库存数据的更新不会跨越DDB节点,即不会有XA产生。但仍然需要解决一个问题:柔性事务的控制组件是否会产生XA?

jiqYRrZ.jpg!web

解决这个问题是要将事务组件设计为可分片式,每一个小事务中的事务组件数据都需要与业务数据落到同一个DDB节点(所以这个事务组件本身也有业务耦合性),这样在没有达到爆品sku性能瓶颈的情况下,库存服务的性能可以随着DDB的扩容而提升。

总结

库存的性能优化很多种思路,实际项目中需要选择一个适合业务现状的方案才能顺利的实施和落地。在未来的业务增长中或许我们也会考虑库存的缓存扣减方案,但这意味着需要投入更多的资源,并且会引入库存数据一致性风险。在选定这样的方案前需要确保业务增长已经到了必须要牺牲一定资源的地步,才会考虑采用。一个“合适”的方案往往比“单一指标”的最优方案更有实际价值。

作者简介

浩然,网易资深开发工程师。2017年加入网易严选,早期从事仓储物流系统的设计和研发,2018年初从无到有参与了严选库存中心的搭建,之后一直致力于库存版块的模型设计和性能优化。

本文由作者授权严选技术团队发布

AzURRnZ.png!web

jyAn6vA.jpg!web


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK