当前位置: 首页 > 科技观察

秒杀场景下订单中心的架构设计

时间:2023-03-21 23:10:17 科技观察

不管是普通场景下的订单,还是秒杀场景下的订单,对于订单中心来说,都是订单。关键是能够支持秒杀瞬间的大量订单请求。本文主要从业务划分、订单请求处理流程、核心表分库等方面探讨订单中心的总体架构,不区分普通订单和闪单。系统架构设计完成。有闪购的时候,无非是一些扩容、限流、降级的手段可以应对。服务分部服务说明我不希望整个订单中心是一个庞大的单体服务,也不希望是一个过于精细的微服务。希望订单中心是“中型服务”的合理组合。服务可以根据不同的场景进行扩展。例如,如果订单搜索请求比较大,可以适当增加订单搜索服务的实例数;如果消费速度慢,可以优化订单消费服务,调整服务实例数。上面划分的每一个服务都是一个独立部署运行的服务。服务说明order-core(订单核心服务)负责订单的业务处理,直接与DB交互order-search(订单搜索服务)负责订单索引的维护和搜索,与ESorder-job直接交互(订单调度服务)订单超时取消等调度任务order-consumer(订单消费者服务)消费订单相关的消息,比如下面的订单消息,订单索引更新消息数据来自ES和从库应用架构图服务调用关系图提交订单服务调用关系:链接1.1~1.3提交订单链接2.1~2.4消费订单消息(订单业务处理)链接3.1~3.2查询订单结果BFF(后端)小程序,负责聚合适配)后台服务调用关系:后台连接一个独立的dent专有阅读库,与前台隔离,后台查询不会影响前台的操作。order-core(订单核心服务)来操作,不能直接操作数据库订单搜索或查看订单详情服务调用关系:订单列表或搜索订单可以调用order-search(订单搜索服务)完成店铺订单详情订单列表,可以根据订单号,order-core(订单核心服务)查询从库完成订单调度服务调用关系:订单调度服务查询专有读写操作调用order-core(订单核心服务)查询完成订单流程。通过MQ异步处理订单请求,订单处理结果存储在Redis中,前端轮询订单结果。步骤描述第一步:提交订单描述order-core(订单核心服务)提供了一个提交订单(/order/submit)的接口。该接口接收订单相关参数,如商品id、价格、数量等,收到请求后,做基础参数校验,生成唯一订单号,汇集订单基本信息,存储订单号和订单状态在Redis中创建,发送MQ,然后返回订单号给前端第二步:消费订单请求描述order-consumer(订单消费者服务)收到订单请求消息后,调整库存中心接口抢占库存。如果库存不足,抢占失败,则将订单创建失败状态和失败信息更新到Redis,流程终止。如果库存充足,抢占成功,则将订单创建成功状态更新到Redis,调用order-core(订单核心服务)将订单信息保存到数据库,调用order-search(订单搜索服务)对订单进行索引并发送订单创建结果消息,库存中心根据订单创建结果消息进行订单创建扣减或放行第三步:根据订单号查询轮询订单结果Order-core(订单核心服务)提供接口根据订单号(/order/is-created)查询订单是否已经创建。接口返回信息应该包括,订单号,订单创建状态(创建中,创建成功,创建失败),创建失败的原因。前端定时轮询接口,查看订单是否创建成功。轮询频率可以根据实际情况调整,比如20ms一次轮询,当订单创建成功后,直接调用支付即可。如果失败,则直接提示失败信息。订单主表和订单明细表通过订单号关联。分库要求:一个用户的所有订单都在同库,避免跨库查询(可以根据用户id——buyerId定位分库号)某个商家的所有订单都在同库,避免跨库查询(可以根据商户id——sellerId定位分库号)可以根据订单号查询(分库号可以根据订单号定位)根据以上分库需求,下面分库设计订单主表做冗余,订单主表分为用户订单主表(buyer_order)和商户订单主表(seller_order)。用户订单主表(buyer_order)按buyerId%32分库。商户订单主表(seller_order)按sellerId%32分库。分仓号,分仓号为buyerId%32订单明细表(order_detail)按照订单号分仓,保证同一个订单的明细同步写入用户订单主表(buyer_order)同一个仓库,因为订单是由用户发起的,需要保证实时性。推荐商户订单主表(seller_order)保证最终一致性。根据实际业务,可以选择同步双写或者通过MQ异步写入分库设计图:库存扣减方案采用预占库存方案:创建订单时预占库存Insufficientinventory,failedto预占,下单失败,库存充足,预占成功,创建订单,创建订单成功,扣除库存;创建订单或取消订单失败,发布库存库存扣除时序图库存设置为redis,skuId为key,改变的数量为value,例如:初始化skuId=10086的库存值为100,redis.incrby(10086,100)库存初始化后,只能对库存进行加减操作,不允许进行覆盖操作。Redis如何与数据库中的库存保持一致:Redis的库存与数据库保持最终的一致性。当存货被预留时,产生存货预留流。关键字段包括订单号、skuId、预留数量、流水状态(预留、已扣款、已释放)、预留超时时间。同时,可以在Redis或者数据库中维护一个skuId对应的总预留数量字段。总预留数量+预留数量。订单中心发送库存扣减消息,库存中心消费收到消息时更新库存流转状态为扣减,总预留数量-预留数量订单中心发送库存释放消息,库存中心消耗消息时,更新库存流转状态为放行,将库存返回Redis,总预留数量-预留占用数量/***预占库存伪码*@paramorderNo订单号*@paramskuIdsku标识*@paramquantity预占用数量*/booleanpreOccupy(StringorderNo,StringskuId,intquantity){booleanisPreOccupySuccess=false;intvalue=redis.decrby(skuId,quantity);if(value>=0){//库存充足//生成库存预留记录//(关键字段:orderNo,skuId,数量,状态(0-预留占用;1-扣减;2-释放),timeout(超时)isPreOccupySuccess=true;}else{//库存不足,退货Redis.incrby(skuId,数量);}returnisPreOccupySuccess;}数据库中的库存数量不能被覆盖更新!数据库中的库存数量不能被覆盖和更新!库存扣减伪SQL:updatestocksetstock_num=stock_num-changevaluewheresku_id=10086关于库存下达,对于一些下达异常,库存中心可以派发服务查询库存预售状态占用流程为预占和预占加班记录,根据订单号,向订单中心确认该订单号的库存是否已经扣减或释放,然后进行相应的业务处理。除了以上主要的设计方面,分布式事务、幂等性、补偿、压力测试……这些都是大家在设计系统时需要考虑的,不在本文讨论范围之内。