You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

12 KiB

19 | 综合案例:电商平台技术架构是如何演变的?

你好,我是王庆友。

在前面的几讲中,我分别和你介绍了技术架构的高可用、高性能、可伸缩等目标,并通过实际的案例说明了如何实现这些目标。今天呢,我会通过一个综合案例,来说明电商平台的技术架构是如何演变的,让你可以全面地理解如何实现这些目标。

一个实际的电商系统很复杂,在案例介绍中,为了简化,我用比较有代表性的交易系统账户系统来代表整体的电商系统,并具体分析这两个系统在电商平台发展过程中,它们都碰到了什么瓶颈,以及我们在技术架构上是如何解决的。

这一讲会包含很多架构图,每一张图都代表了不同时期的架构设计,为了方便你更好地理解它们,在每张架构图中,我都用红色方框圈出了当前架构存在的问题,用绿色实体部分代表了上一个架构所存在问题的解决办法,希望你听完今天的讲解,能够结合这些架构图,加深对技术架构的理解。

单体系统

第一代的电商系统是一个单体架构,所有的代码都打包在一个应用里,部署的时候会有多个实例,我们通过负载均衡,把用户请求分发到具体的实例中。这个时候,所有的数据表还在一个数据库里。

这里的问题是,单体应用的所有代码都放在一起,代码编译需要很长时间,应用启动也需要很长时间,并且代码相互依赖,开发效率低,并行开发困难。随着单体应用的体量越变越大,这些问题也越来越突出。

SOA架构

针对单体应用体量过大带来的问题,我们对系统进行拆分,把整体系统分为多个子系统。比如在图中,我们把系统拆分为交易系统和账户系统,这两个系统(或者说服务)通过内部的负载均衡进行相互调用,这个时候,底层数据库还没有拆分,两个系统还是访问同一个数据库。

通过拆分系统整体就变成了SOA架构这样我们减少了代码冲突系统的开发也变得更加高效部署的时候我们也更容易针对各个系统的处理能力进行水平扩展。

但这里的问题是,内部服务通过中心化的负载均衡进行访问,中心化的负载均衡增加了服务的调用时间。此外,在电商场景下,内部的服务很多,服务调用的频率很高,每秒可能有上百万次,导致了负载均衡的连接能力不够。而且负载均衡是单点,如果它出了问题,很容易引发系统整体的可用性问题(即使负载均衡是多实例,当系统流量很大时,也会因为某台负载有问题,导致其他节点压力增大而引起雪崩效应)。

服务调用去中心化

针对内部服务路由中心化的问题,我们去掉了内部的负载均衡,加入了服务注册中心比如ZooKeeper。

当服务实例启动或退出时,它们会自动在注册中心进行注册或销毁,服务的客户端和注册中心保持长连接,可以实时地获取可用的服务列表;然后在客户端,根据相应的算法选择服务实例,直接调用服务。每次调用无需经过注册中心,如果注册中心有问题,也只是新的服务实例无法注册,或者是已有的服务实例无法注销,这对客户端调用服务的影响是非常有限的。

当然,通过注册中心和更体系化的微服务框架,我们还可以实现完善的服务治理,包括服务隔离、熔断、降级,这些都是对原来简单的负载均衡方式的加强,能够进一步提升服务的可用性。

现在,我们解决了服务调用的问题,但随着业务量逐渐变大,数据表越来越多,数据量也越来越大,单个数据库比如Oracle的性能和储存容量已经无法满足需求了。这个时候,我们就需要对数据库进行改造,提升它的处理能力。

垂直分库

对于单个数据库性能和容量瓶颈,解决的办法就是,我们对数据库进行垂直拆分,按照业务拆分为交易数据库和账户数据库,这样就可以满足它们各自的容量和性能需求,同时也避免了不同业务数据表之间的相互耦合。

你可以认为垂直分库是系统拆分的第二阶段,这样,通过第一阶段的应用代码拆分和这里的数据库表拆分,交易系统和账户系统就可以独立发展。

不过,新的问题又来了,垂直分库后,每个数据库都是单实例。随着业务的发展,和原来系统只有单个数据库类似,现在交易系统也只有一个数据库,它的性能和容量还是有问题,并且数据库单实例也带来了可用性的问题,如果数据库挂了,相应的系统也就不可用。

水平分库及高可用部署

针对单个数据库的可用性问题,我们可以采用 MHA高可用Master High Availability方式部署。比如数据库部署一主多从通过MHA机制我们可以实时检测主库的可用性如果主库有问题系统会自动Failover故障转移到最新的从库。另一方面我们还可以利用多个从库支持读写分离,减轻主库的访问压力。

针对单个数据库的性能和容量问题,首先我们可以引入缓存,在高读写比的场景下,让应用先访问缓存,大大减轻对底层数据库的压力。然后,我们可以对数据库按照某个维度(比如用户维度),进行水平拆分,把数据记录分布到多个实例中,最终分散主库的写压力以及数据存储的瓶颈(在上一讲中,我已经具体介绍过了,你可以点击链接去回顾内容)。

在实践中,我们还可以提供多套水平分库。比如说,针对交易数据,我们可以同时按照用户维度和商户维度进行水平分库,用户维度的库用于前台用户下单的场景,商户维度的库用于后台商家履单的场景。这里,只有用户维度的分库会支持,我们通过数据同步机制,把用户维度分库的更新同步到商户维度的分库里。

当系统体量发展到了一定程度,我们又碰到了新的问题:单个机房的服务器不够用,无法在同一个机房找到更多的机器部署交易系统和账户系统。

多机房部署

对于单机房服务器不够的问题,我们可以在新的机房部署交易系统和账户系统,为了落地方便,所有服务还是注册到旧机房的注册中心,数据还是存放在旧机房的交易数据库和账户数据库。 这样,我们通过在新机房部署应用,对应用节点进行水平扩展,从而解决了单机房机器不足的问题。

但这里产生了跨机房访问的问题:首先,我们只有一个服务注册中心,服务实例一部分部署在老机房,一部分部署在新机房,对于服务调用者来说,它会同时访问新旧机房的服务实例;其次,数据库部署在老机房,新机房的应用会访问旧机房的数据库。

这两种情况,都会产生大量的跨机房访问,我们知道,根据机房物理距离的不同,跨机房访问的网络延时在数十毫秒到数百毫秒之间,是机房内部通信耗时的上千倍,这会对应用的性能产生很大影响,而且跨机房的网络可用性也经常是一个问题。

服务调用本地化

为了避免服务的跨机房访问,我们在新机房也单独部署了服务注册中心,让每个机房的服务注册到同机房的注册中心。这样,客户端的服务调用会路由到同机房的服务端,实现了服务调用的本地化,大大降低了跨机房通信带来的延时和不可用性问题。

这时,随着业务越来越复杂,新的问题又来了:交易系统会依赖很多周边服务。比如下单后,我们需要给用户送积分,交易系统会同步调用积分服务。但是同步调用积分服务,一方面会影响下单的性能,另一方面如果积分服务不可用,会导致核心的下单功能失败。

依赖分级管理

对于外部服务依赖的可用性问题,我们的解决办法是,针对这些外部依赖进行分级管理,根据依赖功能的重要性不同,把它们分为强依赖和弱依赖。

  • 对于强依赖,我们实时同步调用,比如在用户下单时调用库存服务,由于库存非常重要,必须实时扣减,如果调用库存服务失败,下单也失败。
  • 对于大量的弱依赖,我们以异步消息的方式进行信息同步,比如对于积分服务,可以通过柔性事务来保证数据的最终一致性,这样大大提升了核心系统的性能和可用性。

不过,这里存在的问题是,新机房的交易系统和账户系统都在访问老机房的数据库,有跨机房数据库访问的性能问题,以及老机房整体故障带来的可用性问题。比如说,机房断电,通信光纤有问题或者发生自然灾害,导致老机房整体不可用,这就会导致所有系统都不可用。

多机房独立部署

针对机房整体不可用的问题,解决方案是,我们在多个机房做对等的部署,这样每个机房的系统可以形成内部闭环,包括服务、注册中心和数据库,机房之间不产生直接的相互依赖,从而实现了机房级别的水平部署。

如果系统的单元化做得完善,我们还可以进一步支持虚拟机房的概念,一个物理机房可以部署多个虚拟机房,每个虚拟机房包含了一个完整的系统。通过多机房独立部署,我们极大地提升了系统的可用性、处理能力和可伸缩性,可以应对系统面临的各种异常情况。

另外,最近几年,容器化技术的发展很快,原来很多的电商平台都是基于虚拟机部署,现在也纷纷改造为用Docker+K8s的方式部署,这大大提升了资源的利用率、系统的弹性伸缩能力。在面临资源瓶颈时,你可以考虑用这种方式来优化系统的部署。

总结

今天,我基于一个简化的电商系统模型,与你分享了电商平台的技术架构发展过程,我们是如何通过一步步的架构升级,解决系统各个阶段出现的高可用、高性能和可伸缩问题的,相信你现在对技术架构如何应对各种系统性挑战,有了更深入的认识。

值得注意的是,系统的技术架构变化不一定要完全遵循这个过程,不同的业务、不同的发展阶段,对系统的要求都是不一样的,这里我给出的只是典型的问题和解决手段,希望你在工作中,能够具体情况具体分析,灵活地运用这些手段。

业务在不断发展,新的问题会不断出现,但技术也在不断地进步,解决的手段层出不穷,我们需要不断学习,找到新的手段来解决问题。

最后,给你留一道思考题:你的公司当前的系统架构处于哪个阶段,面临什么样的问题呢?

欢迎在留言区和我互动,我会第一时间给你反馈。如果这节课对你有帮助,也欢迎你把它分享给你的朋友。感谢阅读,我们下期再见。