电商系统-业务中台

业务中台

订单中心

1)异步化

场景:大促期间新增许多需要获取订单状态的服务,比如应对双11而临时增加的数据中台订单大屏展示等
解决:异步化,并对消息队列调优,多队列分流
问题:注意异步化引发的乱序问题,一是传输阶段,二是消费阶段

rabbitmq传输:队列级别顺序保障,单消费者消费一个队列可以严格保障顺序性,需要扩充队列数提
升性能

kafka传输:分区级别顺序保障,只能保障投放和传输阶段的顺序性

consumer:1对1消费存在性能问题,接收消息后对key做二次分发,放入多个内存队列,开启多线程
消费

2)过期订单

双11抢单是最常见的场景,抢单不支付会占据大批量资源,如商品库存。如何取消过期订单是架构师必须面对的问题。主要有以下几种方案:

扫表实现

原理:通过定时任务轮询扫描订单表,超时的批量修改状态
优点:实现非常简单
缺点:

  • 大量数据集,对服务器内存消耗大。
  • 数据库频繁查询,订单量大的情况下,IO是瓶颈。
  • 存在延迟,间隔短则耗资源,间隔长则时效性差,两者是一对矛盾。
  • 不易控制,随着定时业务的增多和细化,每个业务都要对订单重复扫描,引发查询浪费

java延迟队列实现

原理:通过DelayQueue,每下一单,放入一个订单元素并实现getDelay()方法,方法返回该元素距离失效还剩余的时间,当<=0时元素就失效,就可以从队列中获取到。启用线程池对数据监听,一旦捕获失效订单,取出之后,调用取消逻辑进行处理。

优点:基于jvm内存,效率高,任务触发时间延迟低。
缺点:

  • 存在jvm内存中,服务器重启后,数据全部丢失。
  • 依赖代码硬编码,集群扩展麻烦
  • 依赖jvm内存,如果订单量过大,无界队列内容扩充,容易出现OOM
  • 需要代码实现,多线程处理业务,复杂度较高
  • 多线程处理时,数据频繁触发等待和唤醒,多了无谓的竞争

消息队列实现

原理:设置两个队列,每下一单放一条进延迟队列,设定过期时间。消息一旦过期,获取并放入工作队列,由consumer获取,唤起超时处理逻辑

如果采用的是RabbitMQ,其本身没有直接支持延迟队列功能,可以针对Queue和Message设置 x-
message-ttl,用消息的生存时间,和死信队列来实现,具体有两种手段, A: 通过队列属性设置,队列
中所有消息都有相同的过期时间,粗粒度,编码简单 B: 对消息进行单独设置,每条消息TTL可以不同,细粒度,但编码稍微复杂。

优点:

  • 可以随时在队列移除,实现实时取消订单,及时恢复订单占用的资源(如商品)
  • 消息存储在mq中,不占用应用服务器资源
  • 异步化处理,一旦处理能力不足,consumer集群可以很方便的扩容

缺点:

  • 可能会导致消息大量堆积
  • mq服务器一旦故障重启后,持久化的队列过期时间会被重新计算,造成精度不足
  • 死信消息可能会导致监控系统频繁预警

redis实现

原理:利用redis的notify-keyspace-events,该选项默认为空,改为Ex开启过期事件,配置消息监听。每下一单在redis中放置一个key(如订单id),并设置过期时间。

优点:

  • 消息都存储在Redis中,不占用应用内存
  • 外部redis存储,应用down机不会丢失数据
  • 做集群扩展相当方便
  • 依赖redis超时,时间准确度高

缺点:订单量大时,每一单都要存储redis内存,需要大量redis服务器资源

被动取消

原理:在每次用户查询订单的时候,判断订单时间,超时则同时完成订单取消业务。
优点:

  • 实现极其简单
  • 不会有额外的性能付出
  • 不依赖任何外部中间件,只是应用逻辑的处理

缺点:延迟度不可控,如果用户一直没触发查询,则订单一直挂着,既不支付也未取消,库存也就被占着

支付中心

支付交互流程

1)重复支付

原因:在第一步发起的时候,用户进入支付方式选择页。选第一个支付方式并支付完后因为通知延迟,以为支付失败。在支付又选了第二种,再次支付。

应对方案:

  • 程序屏蔽,前端js触发按钮置灰或者遮罩提示(支付成功?遇到问题?),或者在支付方式选择页直接跳转。
  • 后端处理,发现不同通道下的支付成功回调,抛消息队列或记录日志。

数据修复:
首先查支付日志,确认针对同一笔订单收到了不同支付渠道的回调。
其次,在支付平台管理后端可以查到入账记录,人工介入。
最后对账阶段会发现对方多帐,我方补单时出现重复订单。

问题处理:调取退款接口或者在支付渠道的管理后台操作退款(一定要多次确认无误)。

2)异常订单

支付但未开单

场景:用户明明支付成功,但未开通订单

问题分析:
一般支付渠道会间隔性多次回调开单链接,如果支付未开单,银行未回调的可能性比较小,着重排查开单接口是否可用。如果可用追查日志是否出现异常记录。

应对措施:

  • 对账阶段可以查漏,程序自动完成补单,但是处理相对延迟,取决于支付渠道的对账文件下发周期
  • 人工补单,人工查询支付渠道后台数据,确认已支付的情况下,介入补单流程人工处理

未支付但已开单

场景:用户未支付,或者财务中心未收到这笔款项,订单状态已开通。这种就问题比较严重了
应对措施:首先排除人为操作因素。其次排查系统是否存在漏洞或者级联开单的情况

3)回调延迟

场景:用户是期望支付完成的同时立马看到结果。但是中间多层远程的调用,可能发生订单状态更新延迟问题。

解决:主动查询。在用户查看订单的时候,如果是类似“支付中”的中间态时,触发远程订单状态查询接口。

4)支付路由

背景:
保障支付可用性及支付分流,支付中心对接多家渠道

方案:

  • 支付中心对接多个支付渠道,支付宝,微信,各银行或第三方支付供应商
  • 对不同用户,进入支付方式选择页时,做支付分流
  • 做好监控统计,一旦某个支付渠道不可用或者延迟较大,切掉,下线,或者降权

营销中心

1)概述

大促和活动不分家,一般营销中心所面对的主要是促销策略、优惠方式等业务上的架构问题。

从促销活动的范围来看,分为单品促销活动、套装促销活动、店铺促销活动,平台促销活动。
从促销类型来看,分为满减、折扣、赠品等。

业务复杂度高,一般遵循 “同类营销仅可选其一,不同类营销可叠加” 的规则。同类叠加意义不大且会造成系统复杂度上升,引发用户困惑。

2)前端设计

用户体验上的设计,比如购物车里商品的排序,按商铺分门别类。优惠总价格及时调整。这些依赖于前端的ui设计和交互体验。

3)赠品设计

(SPU , SKU 基础概念)
赠品有两种设计方案,一种是不做单独的SKU,只有一个空的描述,设计简单,缺点是没有商品详情
页,无法给用户直观的查看和估值。

另一种是单独做SKU,赠品也会作为一个商品存在,与主商品关联,下单的时候将会自动加到商品列
表,价格降为0。这种更为常见。整个商品有完善的详情页,用户可以直接看到价格甚至单独下单购
买。

4)排他与优先级

检查同类别促销,将最大优惠力度的规则应用到订单,并且满足排他性,同类只享受其一。比如满10减3,满20减5,那么用户购买大于20时,只减5即可。
不同类别不做排斥,如购物车整体满减后,不影响单个商品的折扣。在记录数据时,优惠要细化到每个单独的订单明细上。退款也做到明细级别的单独退。

5)价格分摊

满减或平台券等优惠,在多个商品下单时,涉及到金额的分摊。即 优惠总额度/购物车总额 ,得到比例后再按比例均分到每个商品。只有分摊才能在发生部分退款时退回真实金额。
但是这会涉及到一个精度问题。举例如下:满99减9活动,假设用户购买了 30+40+50=120,3件商品
应付111元。按比例折算的话,9/99取4位小数是0.9090,那么分摊后为
30x0.9090+40x0.9090+50x0.9090=109.08与实际支付金额出现偏差。这会造成财务无法平账。
解决方案:记账时在订单明细记录,将误差 111-109.08=1.92计入金额最大的明细,也就是50元商品
上。那么最终记账为:30x0.9090 + 40x0.9090 +(50*0.909+1.92)= 111

6)退单处理

退单后要同时恢复用户的权益,比如优惠券的再次使用,限购次数等。确保用户体验。

商品中心

1)限时商品的下架控制

这个和超时订单设计方案类似

2)库存管理

普通商品可以直接借助数据库锁实现,一般分乐观锁和悲观锁两种方案,如果采用悲观锁(如select语
句带forupdate),会带来很大的性能阻塞,所以更多的采用乐观锁设计。

乐观锁就是在最后执行库存扣减操作时,将事务开始前获取的库存数量带入到SQL语句中作为更新的
where条件,如果数量相等,则该条更新库存的语句成功执行返回update条数为1;如果不相等,则表
示该商品的库存信息已经被其他事务修改,需要放弃该条update的执行,采用重试处理。

库存秒杀商品因为大批量的访问在一瞬间涌入,数据库扛不住。可以采用 redis缓存做decr处理,正常
下单后,再使用mq异步更新到db。(秒杀不超卖课题的库存控制)

posted @ 2021-09-16 17:32  请务必优秀  阅读(622)  评论(0编辑  收藏  举报