作者: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)计划
设计通用操作触发器,具体步骤为:
- 配置触发器,粒度是状态机的流程类型。
- 创立订单 / 售后单时或订单状态变动时,如果有满足条件的触发器,发送提早音讯。
- 收到提早音讯后,再次判断执行条件,执行配置的操作。
触发器的配置包含:
- 注册工夫:可选订单创立时,或订单状态变动时
- 执行工夫:可应用 JsonPath 表达式选取订单模型中的工夫,并可叠加延迟时间
- 注册条件:应用 QLExpress 配置,满足条件才注册
- 执行条件:应用 QLExpress 配置,满足条件才执行操作
- 执行的操作和参数
3.4 分布式事务
对交易平台而言,分布式事务是一个经典问题,比方:
- 创立订单时,须要同时扣减库存、占用优惠券,勾销订单时则须要进行回退。
- 用户领取胜利后,须要告诉发货零碎给用户发货。
- 用户确认收货后,须要告诉积分零碎给用户发放购物处分的积分。
咱们是如何保障微服务架构下数据一致性的呢?首先要辨别业务场景对一致性的要求。
(1)强一致性场景
比方订单创立和勾销时对库存和优惠券零碎的调用,如果不能保障强统一,可能导致库存超卖或优惠券重复使用。
对于强一致性场景,咱们采纳 Seata 的 AT 模式来解决,上面的示意图取自 seata 官网文档。
(2)最终一致性场景
比方领取胜利后告诉发货零碎发货,确认收货后告诉积分零碎发放积分,只有保障可能告诉胜利即可,不须要同时胜利同时失败。
对于最终一致性场景,咱们采纳的是本地音讯表计划:在本地事务中将要执行的异步操作记录在音讯表中,如果执行失败,能够通过定时工作来弥补。
3.5 高可用与平安设计
- 熔断
应用 Hystrix 组件,对依赖的内部零碎增加熔断爱护,避免某个系统故障的影响扩充到整个分布式系统中。
- 限流
通过性能测试找出并解决性能瓶颈,把握零碎的吞吐量数据,为限流和熔断的配置提供参考。
- 并发锁
任何订单更新操作之前,会通过数据库行级锁加以限度,防止出现并发更新。
- 幂等性
所有接口均具备幂等性,上游调用咱们接口如果呈现超时之类的异样,能够释怀重试。
- 网络隔离
只有极少数第三方接口可通过外网拜访,且都有白名单、数据加密、签名验证等爱护,外部零碎交互应用内网域名和 RPC 接口。
- 监控和告警
通过配置日志平台的谬误日志报警、调用链的服务剖析告警,再加上公司各中间件和根底组件的监控告警性能,让咱们可能可能第一工夫发现零碎异样。
3.6 其余思考
- 是否用畛域驱动设计
思考到团队非麻利型组织架构,又短少领域专家,因而没有采纳
- 高峰期性能瓶颈问题
大促和推广期间,特地是爆款抢购时的流量可能会触发限流,导致局部用户被拒之门外。因为无奈精确预估流量,难以提前扩容。
能够通过被动降级计划减少并发量,比方同步入库切为异步入库、db 查问转为 cache 查问、只能查到最近半年的订单等。
思考到业务复杂度和数据量级还处在初期,团队规模也难以撑持,这些设计有远期打算,但临时还没做。(架构的适合性准则,杀鸡用牛刀,你违心也行)。
四、总结与瞻望
咱们在设计零碎时并没有一味谋求前沿技术和思维,面对问题时也不是间接采纳业界支流的解决方案,而是依据团队和零碎的理论情况来选取最合适的方法。好的零碎不是在一开始就被大牛设计进去的,而是随着业务的倒退和演进逐步被迭代进去的。
目前交易平台已上线一年多,接入了三个业务方,零碎运行安稳,公司内有交易 / 商品 / 库存等需要的新业务,以及存量业务在遇到零碎瓶颈须要降级时,都能够复用这块能力。
上游业务方数量的减少和版本的迭代,对平台零碎的需要源源不断,平台的性能失去逐步欠缺,架构也在一直演进,咱们正在将履约模块从交易平台中剥离进去,进一步解耦,为业务继续倒退做好储备。