关于架构设计:vivo全球商城电商交易平台设计

33次阅读

共计 3917 个字符,预计需要花费 10 分钟才能阅读完成。

作者:vivo 官网商城开发团队 – Cheng Kun、Liu Wei

本文介绍了交易平台的设计理念和关键技术计划,以及实际过程中的思考与挑战。

点击查阅:《vivo 寰球商城》系列文章

一、背景

vivo 官网商城通过了七年的迭代,从单体架构逐渐演进到微服务架构,咱们的开发团队积淀了许多贵重的技术与教训,对电商畛域业务也有相当粗浅的了解。

去年初,团队承接了 O2O 商城的建设工作,还有行将成立的礼品中台,以及官网商城的线上购买线下门店送货需要,都须要搭建底层的商品、交易和库存能力。

为节约研发与运维老本,防止反复造轮子,咱们决定采纳平台化的思维来搭建底层零碎,以通用能力灵便撑持下层业务的个性化需要。

包含交易平台、商品平台、库存平台、营销平台在内的一整套电商平台化零碎应运而生。

本文将介绍交易平台的架构设计理念与实际,以及上线后继续迭代过程中的挑战与思考。

二、整体架构

2.1 架构指标

除了高并发、高性能、高可用这三高外,还心愿做到:

1. 低成本

重视模型与服务的可重用性,灵便撑持各业务的个性化需要,进步开发效率,升高人力老本。

2. 高扩大

零碎架构简略清晰,利用零碎间耦合低,容易程度扩大,业务性能增改方便快捷。

2.2 零碎架构

(1)电商平台整体架构中的交易平台

(2)交易平台零碎架构

2.3 数据模型

三、要害方案设计

3.1 多租户设计

(1)背景和指标

  • 交易平台面向多个租户(业务方),须要可能存储大量订单数据,并提供高可用高性能的服务。
  • 不同租户的数据量和并发量可能有很大区别,要能依据理论状况灵便调配存储资源。

(2)设计方案

  • 思考到交易系统 OLTP 个性和开发人员熟练程度,采纳 MySQL 作为底层存储、ShardingSphere 作为分库分表中间件,将用户标识 (userId) 作为分片键,保障同一个用户的订单落在同一个库中。
  • 接入新租户时约定一个租户编码(tenantCode),所有接口都要带上这个参数;租户对数据量和并发量进行评估,调配至多满足将来五年需要的库表数量。
  • 租户与库表的映射关系:租户编码 -\> {库数量, 表数量, 起始库编号, 起始表编号}。

通过下面的映射关系,能够为每个租户灵便调配存储资源,数据量很小的租户还能复用已有的库表。

示例一

新租户接入前已有 4 库 *16 表,新租户的订单量少且并发低,间接复用已有的 0 号库 0 号表,映射关系是:租户编码 -> 1,1,0,0

示例二

新租户接入前已有 4 库 *16 表,新租户的订单量多但并发低,用原有的 0 号库中新建 8 张表来存储,映射关系是:租户编码 -> 1,8,0,16

示例三

新租户接入前已有 4 库_16 表,新租户的订单量多且并发高,用新的 4 库_8 表来存储,映射关系是:租户编码 -> 4,8,4,0

用户订单所属库表计算公式

库序号 = Hash(userId) / 表数量 % 库数量 + 起始库编号

表序号 = Hash(userId) % 表数量 + 起始表编号

可能有小伙伴会问:为什么计算库序号时要先除以表数量?上面的公式会有什么问题?

库序号 = Hash(userId) % 库数量 + 起始库编号

表序号 = Hash(userId) % 表数量 + 起始表编号

答案是,当库数量和表数量存在公因数时,会存在歪斜问题,先除以表数量就能剔除公因数。

以 2 库 4 表为例,对 4 取模等于 1 的数,对 2 取模也肯定等于 1,因而 0 号库的 1 号表中不会有任何数据,同理,0 号库的 3 号表、1 号库的 0 号表、1 号库的 2 号表中都不会有数据。

路由过程如下图所示:

(3)局限性和应答方法

  • 全局惟一 ID

问题:分库分表后,数据库自增主键不再全局惟一,不能作为订单号来应用。且很多外部零碎间的交互接口只有订单号,没有用户标识这个分片键。

计划:如下图所示,参考雪花算法来生成全局惟一订单号,同时将库表编号隐含在其中(两个 5bit 别离存储库表编号),这样就能在没有用户标识的场景下,从订单号中获取库表编号。

  • 全库全表搜寻

问题:治理后盾须要依据各种筛选条件,分页查问所有满足条件的订单。

计划:将订单数据冗余存储一份到搜索引擎 Elasticsearch 中,满足各种场景下的疾速灵便查问需要。

3.2 状态机设计

(1)背景

  • 之前做官网商城时,因为是定制化业务开发,各类型的订单和售后单的状态流转都是写死的,比方惯例订单在下单后是待付款,付款后是待发货,发货后是待收货;虚构商品订单不须要发货,没有待发货状态。
  • 当初要做的是平台零碎,不可能再为每个业务方做定制化开发,否则会导致频繁改变发版,代码错综冗余。

(2)指标

  • 引入订单状态机,能为每个业务方配置多套差异化的订单流程,相似于流程编排。
  • 新增订单流程时,尽可能不改变代码,实现状态和操作的可复用性。

(3)计划

  • 在治理后盾为每个租户保护一系列订单类型,数据转化为 JSON 格局存储在配置核心,或存储在数据库并同步到本地缓存中。
  • 每个订单类型的配置包含:初始订单状态,以及每个状态下容许的操作和操作之后的指标状态。
  • 当订单在执行某个动作时,应用订单状态机来批改订单状态。

    订单状态机的公式是:StateMachine(E,S —> A , S’),示意订单在事件 E 的触发下执行动作 A,并从原状态 S 转化为指标状态 S’

  • 每个订单类型配置实现后,生成数据的构造是

/**
 * 订单流程配置
 **/
@Data
public class OrderFlowConfig implements Serializable {
    /**
     * 初始订单状态编码
     **/
    private String initStatus;
    /**
     * 每个订单状态下,可执行的操作及执行操作后的指标状态
     * Map< 原状态编码, Map< 订单操作类型编码, 指标状态编码 >>
     */
    private Map<String, Map<String, String>> operations;
}
  • 订单商品行状态机、售后单状态机,也用同样的形式实现

3.3 通用操作触发器

(1)背景

业务中通常都会有这样的延时需要,咱们之前往往通过定时工作来扫描解决。

  • 下单后多久未领取,主动敞开订单
  • 申请退款后商家多久未审核,主动批准申请
  • 订单签收后多久未确认收货,主动确认收货

(2)指标

  • 业务方有相似的延时需要时,可能有通用的形式轻松实现

(3)计划

设计通用操作触发器,具体步骤为:

  1. 配置触发器,粒度是状态机的流程类型。
  2. 创立订单 / 售后单时或订单状态变动时,如果有满足条件的触发器,发送提早音讯。
  3. 收到提早音讯后,再次判断执行条件,执行配置的操作。

触发器的配置包含:

  1. 注册工夫:可选订单创立时,或订单状态变动时
  2. 执行工夫:可应用 JsonPath 表达式选取订单模型中的工夫,并可叠加延迟时间
  3. 注册条件:应用 QLExpress 配置,满足条件才注册
  4. 执行条件:应用 QLExpress 配置,满足条件才执行操作
  5. 执行的操作和参数

3.4 分布式事务

对交易平台而言,分布式事务是一个经典问题,比方:

  • 创立订单时,须要同时扣减库存、占用优惠券,勾销订单时则须要进行回退。
  • 用户领取胜利后,须要告诉发货零碎给用户发货。
  • 用户确认收货后,须要告诉积分零碎给用户发放购物处分的积分。

咱们是如何保障微服务架构下数据一致性的呢?首先要辨别业务场景对一致性的要求。

(1)强一致性场景

比方订单创立和勾销时对库存和优惠券零碎的调用,如果不能保障强统一,可能导致库存超卖或优惠券重复使用。

对于强一致性场景,咱们采纳 Seata 的 AT 模式来解决,上面的示意图取自 seata 官网文档。

(2)最终一致性场景

比方领取胜利后告诉发货零碎发货,确认收货后告诉积分零碎发放积分,只有保障可能告诉胜利即可,不须要同时胜利同时失败。

对于最终一致性场景,咱们采纳的是本地音讯表计划:在本地事务中将要执行的异步操作记录在音讯表中,如果执行失败,能够通过定时工作来弥补。

3.5 高可用与平安设计

  • 熔断

应用 Hystrix 组件,对依赖的内部零碎增加熔断爱护,避免某个系统故障的影响扩充到整个分布式系统中。

  • 限流

通过性能测试找出并解决性能瓶颈,把握零碎的吞吐量数据,为限流和熔断的配置提供参考。

  • 并发锁

任何订单更新操作之前,会通过数据库行级锁加以限度,防止出现并发更新。

  • 幂等性

所有接口均具备幂等性,上游调用咱们接口如果呈现超时之类的异样,能够释怀重试。

  • 网络隔离

只有极少数第三方接口可通过外网拜访,且都有白名单、数据加密、签名验证等爱护,外部零碎交互应用内网域名和 RPC 接口。

  • 监控和告警

通过配置日志平台的谬误日志报警、调用链的服务剖析告警,再加上公司各中间件和根底组件的监控告警性能,让咱们可能可能第一工夫发现零碎异样。

3.6 其余思考

  • 是否用畛域驱动设计

思考到团队非麻利型组织架构,又短少领域专家,因而没有采纳

  • 高峰期性能瓶颈问题

大促和推广期间,特地是爆款抢购时的流量可能会触发限流,导致局部用户被拒之门外。因为无奈精确预估流量,难以提前扩容。

能够通过被动降级计划减少并发量,比方同步入库切为异步入库、db 查问转为 cache 查问、只能查到最近半年的订单等。

思考到业务复杂度和数据量级还处在初期,团队规模也难以撑持,这些设计有远期打算,但临时还没做。(架构的适合性准则,杀鸡用牛刀,你违心也行)。

四、总结与瞻望

咱们在设计零碎时并没有一味谋求前沿技术和思维,面对问题时也不是间接采纳业界支流的解决方案,而是依据团队和零碎的理论情况来选取最合适的方法。好的零碎不是在一开始就被大牛设计进去的,而是随着业务的倒退和演进逐步被迭代进去的。

目前交易平台已上线一年多,接入了三个业务方,零碎运行安稳,公司内有交易 / 商品 / 库存等需要的新业务,以及存量业务在遇到零碎瓶颈须要降级时,都能够复用这块能力。

上游业务方数量的减少和版本的迭代,对平台零碎的需要源源不断,平台的性能失去逐步欠缺,架构也在一直演进,咱们正在将履约模块从交易平台中剥离进去,进一步解耦,为业务继续倒退做好储备。

正文完
 0