阿里数据库是这样应对高并发库存高峰场景的在表inventory_detail中,第二步是在库存扣减表inventory中扣减一条记录。这两个步骤通常在一个事务中实现。数据库业务架构图如下,所有的请求都发送到同一个Database。从上面的架构图不难看出,所有商品的库存信息都存在于一个表和库中。当商品种类繁多或业务请求并发激增时,单实例数据库显然会成为容量或性能瓶颈。数据库架构一般只是功能实现,主要用于微库存系统或测试使用。一个高并发库存系统的数据库实现为了解决单实例的容量和性能上限问题,阿里巴巴所有的库存系统在十年前就已经实现了分库分表的设计,主要是通过横向数据拆分,实现不同商品的库存扣款请求路由到不同的数据库。基本的数据库架构图如下。从上图不难看出,无论是库存扣减表还是扣减明细表,一般都是以productid作为分片键,这样可以保证整个系统能够满足高并发的扣减请求,同时,同一个商品的库存扣减操作和添加明细操作是在同一个事务中实现的。如果数据分布和业务请求足够统一,理论上分库分表设计后,整个系统的吞吐量会线性增长,主要取决于分实例的数量。热线更新在电商业务中,闪购等商家活动在所难免。秒杀活动将给电商库存系统带来巨大的挑战,尤其是在数据库层面。因为一个产品id往往对应数据库中的一行记录,所以在DB架构上按照产品维度分库分表也是无效的。在更新这行记录时,需要给这行记录加X锁。爆款的库存扣压,本质上就是更新热排的能力。高并发的peer更新会造成严重的行锁等待,从而导致数据库的threads_running和rt飙升,造成雪崩。目前官方的mysql,单行更新的QPS一般在500以内,对于热门产品的秒杀需求往往达不到标准。基于以上场景需求,阿里巴巴PolarDB-X数据库团队引入了先进的水车模型进行内核优化。识别热点SQL后,实现内核级优化处理,比官方MySQL快10倍以上。热点线推演能力广泛应用于集团电商库存集群、资金平台、股权发行平台等核心数据库集群。其主要核心思想是:对应用层SQL进行轻量化改造,并打上“热行SQL”的标签。(一般是同一个商品id)hash到同一个地方合并请求,经过一段时间(默认100us)统一提交,从而实现串行处理到批处理的转换,让每个热行都更新请求无需扫描和更新btree。与原理类似,阿里云RDS数据库团队在内核层面也对热行更新做了很多优化。核心思想是引入SQL语句的排队机制,将可能有行锁冲突的语句归为一组进行排序,从而减少行数。锁冲突导致的额外系统开销。StatementQueque和InventoryHint可以结合使用,但是在一个事务中,热点行更新必须是事务的最后一条记录,因为存在commitonsuccess机制,一旦SQL执行成功,就会自动commit或自动回滚。一个简单的用法示例如下:begin;insertintoinventory_detail(inventory_id,user_id)values(1,1);update/*+ccl_queue_value(1)commit_on_successrollback_on_failtarget_affect_row(1)*/inventorysetinventory_count=inventory_count+1whereinventory_id=1更多文档参考库存提示业务statementqueue架构优化幂等性在库存数据库系统中实现。更新库存记录后,一般会写一条库存扣减明细的流水记录,以备后续可能存在的查询需求。例如,在集团的股权发行平台中,库存流水记录主要是为了实现库存抵扣的幂等性,即同一用户只能领取一次股权。在系统实际运行过程中,由于网络故障等其他原因,当底层数据库扣款成功但未返回给用户时,用户可以重试操作。这时候就需要避免库存记录的重复。扣除。因此,针对这些情况,应用程序在设计应用程序时会首先考虑查询库存流转记录。如果用户已经领取过福利,则不会重复扣款,直接退还。为了实现这种强幂等性要求,库存扣除和插入流必须在同一个事务中,同时满足成功或失败。基于缓存的桶扣扣方案在更大规模、超高并发扣扣库存集群中可能无法满足基于数据库内核改造优化的业务需求。单个产品的超高并发扣费可能会影响同一个数据库实例上其他产品的扣费,也可能有多个热点产品在同一个数据库实例上相互影响。这个时候就需要考虑业务和数据库架构的升级,我们引入基于缓存的桶扣方案。下图为该方案的数据库架构图。基于缓存的分桶扣款方案主要思路是1.普通非热销商品,或者并发不足的热销商品,加强幂等分库分表+数据库内核改造优化2.针对超-大而热的产品,针对这个产品做多key拆分,先使用弱幂等缓存推算,缓存推算后,异步写一个库存记录到DB,后面做缓存和数据库总库存需要的同步在分桶方案的详细实施中要考虑。比较重要的有以下几点:1.桶管理更通俗直观的描述,缓存集群的一个key对应一个“桶”。要实现基于缓存分桶方案的高可扩展库存系统,分桶的设计非常重要,比如一个热门商品应该对应多少个分桶,分桶的数量是否可以根据当前业务的变化灵活伸缩1.按桶预分配库存:当桶初始化时,每个桶应该保存多少库存。预分配库存阶段不需要将数据库中商品的所有库存数量都分配到缓存中。可能是渐进的分配策略,DB作为总库存池,扩缩容操作本质上是调整桶图管理中的信息,增加或减少桶,一旦增加或减少桶信息,就会秒检测扣减链接,进而引导用户流量或删除。从上面的DB架构图可以看出,比较简单的实现方式是根据当前热销商品的桶数取模。该场景主要用于子桶中库存接近扣减时,系统自动从MySQL库存集群总池中捞出一部分放入桶中。缩水场景的主要场景是在桶下线后,将桶中的剩余库存回收到总库存池中。前端界面的展示也会带来挑战,需要做库存整合直接返回前端库存扣,避免超卖。3.碎片化问题在一些库存系统的设计中,考虑到系统的兼容性和支持的扣款类型,扣款的可能是商品的库存数量,或者是红包的数量(换算成红包的数量)带小数的红包变成整数扣)。所谓分片问题,比如如果扣的是红包的金额,假设红包的金额至少是1元,换算成一个整数,就是100。在这种情况下多次扣除的,最后一部分分桶的存货剩余价值可能小于100,而所有桶的总和大于100,如果不处理,会造成资金损失。针对这种极端场景,当系统检测到分片时,需要自动将有分片的桶下线并计入总库存池,然后从总DB池中分配少量缓存键进行扣减,以及重复循环直到不再存在碎片为止。或者出现这种情况后,由于总库存已经基本扣除,所以计入总DB池后直接在DB端扣除。
