蘑菇街交易平台服务架构与服务化建设改造历程分享

2024-12-24
来源:网络整理

潘福江,蘑菇街高级研发工程师。 2014年之前,他在阿里巴巴工作,搭建电子商务垂直业务平台,同时也从事中间件相关的研发工作。 2015年加入蘑菇街(现美丽联合集团),负责蘑菇街交易。资金、购物车等电子商务基础平台的服务化建设。

我来自蘑菇街。蘑菇街是一个主要针对女性用户的电子商务平台。男性同胞可能用得比较少。不过蘑菇街的模特女孩很多,而且个个颜值都很高。我建议你下来使用它。当你写代码累了的时候,你可以偷偷打开蘑菇街看看妹子们。感觉非常好。

今天我的主题是蘑菇街交易平台的服务架构,分享一下我们在服务化建设过程中所做的一些改造过程。

蘑菇街导购时期业务结构

蘑菇街最初是做导购的。那时所有的业务都是基于用户和内容两个核心。当时前端业务主要做社交导购,后端业务主要做内容管理。一句话,就是一个小而美的状态,业务相对也不是很复杂。

当时的技术架构是典型的创业公司的技术架构。整个网站采用PHP搭建,系统分层简单,基础设施主要基于现成的开源产品。 2013年,蘑菇街迎来了转型。主要原因是那段时间大量导购网站被封,所以转型为社交电商平台。

社交电商平台分为两部分。一部分是社交的。我们之前作为导购积累了一些经验。电子商务是我们以前从未接触过的东西,基本上是从零开始搭建的。建设电子商务平台,首先要建设交易平台。起初相对简单。我们重写了一个系统。体系结构与以前相比没有根本变化。所有业务都是在一个巨大的项目中编写的,并通过一组代理层与我们的基础设施进行交互。

电商转型面临的问题

蘑菇街转型电商平台后,业务基本上每年都以三倍以上的速度增长。这个时候,问题就开始暴露出来。电商平台在发展过程中,尤其是发展中期遇到的一些问题,不仅仅是蘑菇街遇到的,其他平台也可能会遇到。例如系统代码臃肿、模块耦合度高、依赖关系复杂、业务扩展能力差等。

魔古街当时面临几个主要问题:

一是我们的业务增长很快,系统容量跟不上。当时交易系统只能支持每秒400单的容量(大减价期间的流量是平时的一百倍以上)。

二是电商业务形态变化非常快,业务支持不够灵活、不够快速。

另外,还有历史包袱,系统耦合非常严重。

解决这一系列问题的关键就一个字:“拆”。

系统拆分流程

系统拆分-交易购物车为例

我们以交易购物车为例来说明我们的转换过程。以前我们有一个项目,所有的代码都写在这里。不同的终端或业务维护的模块代码不同。访问数据也比较随意,各自维护一套数据访问代码。那么就有两个非常麻烦的问题:

一方面,由于事务只有一个数据库,所有的内容都存储在里面,所以这些随机、分散的SQL可能会让你的查询突然变慢。其他业务代码造成的不稳定会互相影响,而且很难定位。这个“野SQL”从何而来?使得我们的DB非常不稳定,对后续的改造非常不利。

另一方面是业务支持。产品有需求,必须在各种终端上实现。可复用性很差,业务支撑很不灵活,系统没有可扩展性,开发同学苦不堪言,经常加班。是的,它经常会产生一堆错误。

于是我们就去拆系统。怎么拆呢?其实也是有一定讲究的。

系统分割优先级

如果把DB比作一个木桶,那么各种业务就可以比作往里面倒水。一开始往桶里倒的水可能不多,但是把桶装满是没有问题的。但随着业务的增长,总会有一天桶不够用了。

首先,桶要足够大,可以轻松扩展,这样才不会有后顾之忧。业务量有时很难预测,你可能不知道什么时候量会增加。如果不把底层木桶做得足够强大,优先考虑业务拆分和优化,一旦体量增加,整个系统就会停止。菜准备好了。

所以DB是系统拆分的基础,需要先进行拆分。

在分离DB的时候,也要注意稳定性。前面提到,当时的SQL比较分散,很容易造成DB不稳定。因此,数据访问/模型的统一也至关重要。我们建立了统一的数据访问层。有了这一层,就可以更有效地控制DB后续的改造和扩展。

现在基础的东西已经搭建好了,接下来解决业务支撑难的问题。业务模型需要统一、抽象,以支持定制扩展。流程转型过程中还孵化了SPI业务框架、流程引擎、规则引擎等基础业务框架。实现灵活、可扩展的业务支持。系统的分层也比较合理。每一层只需要关心本层需要的能力。

系统分割结果

交易系统整体拆分完成后,公司SOA的雏形已基本形成,包括基本的面向服务的框架、消息中间件、数据中间件、配置中心。此外,还孵化了一系列基础设施工具。 ,包括监控系统、调度系统、日志采集、链路跟踪系统等。

还有一个背景是,在分拆的过程中,公司的整体战略转向了Java语言。这是从公司综合层面考虑的。 Java人才相对较多,尤其是杭州,技术体系也相对成熟。有专家可以解决这个问题。那时候PHP资源确实比较少。

产能增加

系统拆分改造后,我们会更加关注应用本身的容量、性能和稳定性。我们在这些方面也做了一些修改和尝试。

系统拆分的时候,DB已经按照业务进行了垂直拆分,DB也已经实现了读写(基于)分离。

下面重点介绍分库分表的改造。当时的目的主要是为了增加中央服务的写入能力,因为当时DB读写分离是单一结构,会存在写入瓶颈。

以事务创建为例来说明我们分库分表的流程。交易创建应该算是交易中最复杂的业务场景之一。创建订单时,会同时写入许多其他数据。当时系统容量约为每秒千单。 DB单点存在写入瓶颈,写入过多会导致严重的主从延迟。另外DB磁盘空间已经超过80%,不稳定度很高,随时可能崩溃。

所以我们决定把它分开。当时的背景是中间件还没有建立起来,还没有分库分表相关的组件,所以我们决定先内部做。

当时我们对比了一些业界比较流行的解决方案,比如阿里巴巴的TDDL、谷歌的等,对比之后发现这些组件都比较重,接入和使用成本也比较高。我们的原则是根据我们的业务场景选择一个访问和使用相对简单的组件。所以我们采用了最后一种方法,通过字节码增强来实现分库分表功能。该组件目前是开源的:

行业分库分表解决方案对比如下:

自主开发分库分表组件,完成分库分表

这个组件就叫了,它的特点足够简单,也符合我们的预期。支持分库分表,支持数据源路由,支持事务,支持结果集合并,支持读写分离,满足我们所有的要求。

性能优化

我们在性能优化方面也做了一些尝试,主要针对以下三个场景:

分布式事务处理——以事务创建为例

优化思路:异步消息解耦

在交易创建过程中,订单、优惠券、库存的状态必须保持一致。

营销优惠券服务和库存中心库存服务与订单服务分开部署。

调用优惠券/库存服务超时/失败,异步发送消息通知回滚;复杂度可控

MQ 生成器发送失败重试 + 消费者接受 ACK 机制以确保播种一致

消除两阶段提交等分布式事务框架的侵入性影响

我们先来说说分布式事务处理。这里我们以创建交易为例。交易创建过程会和多个服务交互,有些服务是强依赖的,比如库存抵扣、优惠券锁定服务,必须保持一致性。两阶段/多阶段协议非常严厉,当时没有被采纳。

我们想了一个办法,通过异步消息解耦来解决。具体流程为:

下单时,不要急于曝光订单。我们先创建一个隐形订单(或者也可以认为是先预创建一个订单),然后进行库存削减和优惠券锁定操作。当这些操作出现异常或失败时,点餐系统会发送订单取消消息。它的下游系统(如促销、库存系统)收到订单取消消息后,会帮我们进行回滚操作。这样就解决了我们的分布式事务问题。

分布式事务处理-支付回调为例

支付回调过程中,资金系统回调交易后,会触发订单状态更新、库存减少、优惠券发放等操作。

资金作为发起者,保证重试、消息可达性、交易和下游操作等。

将失败的业务录入任务重试表,并重试异步补偿。

消除两阶段提交等分布式事务框架的侵入性影响

另一种场景是支付回调。订单支付后,支付系统会通知交易系统。交易系统会进行订单状态更新、库存减少、优惠券发放等一系列操作,这也是一个分布式交易问题。

我们的策略是,当业务失败时,请求会进入我们的一张失败补偿表,我们会通过持续的异步补偿重试(逐步)来确保最终的一致性。

单机异步并行——以购物车为例

分析思路

购物车是典型的IO密集型应用

代码串行执行,同步等待时间较长。

CPU利用率低

经过分析,购物车本身其实就是一个典型的IO密集型应用。类似这样的应用还有很多,都会有大量的网络IO请求。还有一点就是我们习惯串行编写代码,所以存在大量的同步等待时间。

由于每次购物车查询都会经过这么多节点,如果两个节点之间没有依赖关系,是否可以并行进行呢?分析一下,其实每个查询都会对应一个查询依赖树。同层节点之间没有依赖关系。我们在这一层其实是可以进行并行操作的,所以基于这个思路我们当时就做了优化,效果还是不错的。

具体的优化就是添加这个概念。检查的时候,我们先等待其他查询,大家一起检查,最后做一个总结,查询结果就出来了。这是这样一个过程。那么效果也不错,整个RT基本上可以降低到一半以上。

预处理和缓存——以营销定价服务为例

预处理和缓存其实是一种比较常见的优化方法。我们采用多级缓存策略,本地缓存+分布式缓存。首先读取本地缓存。如果本地缓存不可用,则转到分布式缓存。如果分布式缓存获取不到,就去DB获取。当数据发生变化时,我们会有系统异步刷新缓存,及时更新缓存中的数据。

服务SLA保证

SLA: ,是对服务提供商的要求。 SLA体现在对容器(QPS)、性能(RT)、范围(分布、可用性、错误率)的约束。提高SLA的一些方法如下:

这是我们的内部监控系统。我们会对每个应用的一些关键指标进行监控,观察整个链路的情况。

总结和下一步计划

总结

目前正在做

下一步

问答

问题:如果消费者收到一条消息,那么我告诉系统删除​​该消息。那么当我后端在执行消息的时候,比如我做了一些入库操作或者其他操作,但是这个服务死了,那么大家有没有遇到过类似的情况,是怎么处理的呢?

潘富江:目前还没有,因为这实际上是一个需要合作的过程。我们需要下游系统配合,保证业务正常后再ACK消息。这主要是几个系统之间的协作问题。

问:电子商务中的分布式事务解决方案大多采用消息队列机制。有更通用的解决方案吗?我们开发了一套分布式组件,比如两阶段协议,来更高效地解决这个分布式事务。

潘富江:分布式事务的问题其实要看场景。支付宝(原支付宝)也有类似的框架,但比较重,需要一定的接入成本和一定的配合。个案分析问题会比较好。比如有些场景的一致性要求不是那么高。不需要使用两阶段协议来处理它。主要还是看业务场景。

问:数据库迁移时能否实现平滑迁移?因为我看到你之前切换了两次中间件。这时候你一定遇到过一些数据库顺利在线迁移的情况。你是怎么做到的?

潘富江:我们内部会有一套数据同步工具,还有一个切换系统来完成灰度切换。您可以动态地将一些值推送到您的应用程序中,然后动态更改这些值。此外,我们的数据同步工具支持回溯。遇到紧急情况可以快速切换回溯数据。

问题:当你制作了分支库后,你需要将旧库保留在新库中,因为你上传之后,你要一步步释放它,但你的旧库仍然在运行。这时候,有人正在使用你的旧图书馆。但因为你再次发布,新库的流量被削减了一半。如果此时有人正在修改数据怎么办?

潘富江:如上所述,旧数据库和新数据库之间建立了通道(数据同步通道),数据同步工具一直在工作。旧数据库的数据会实时同步到新数据库,我们的灰度是通过切换系统动态推送的,实时生效。

问题:分库分表后,如果有表,如何处理相关查询?

潘富江:我们好像没有相关询问,也不推荐。相关查询对于后续的水平拆分非常不利,也不利于DB扩展。可以单独检查,然后在应用层做相关的事情。

问:如果单独检查,性能是否会受到影响?

潘富江:性能可能会受到一定的影响,数据库的访问次数会多一些,但是DB扩展性能会得到很大的提升。互联网正在玩转大数据。与此相比,DB的可扩展性更为重要。应用层可以有很多种优化方式(比如缓存)。如果使用JOIN的话,横向拆分是非常困难的。

问:拆仓库之前,有没有考虑过什么好的后备方案?

潘富江:我们的数据同步工具支持回溯。如果出现问题,可以通过切换系统立即切换回来,并且可以回溯数据,将影响降低到可控范围内。

分享