<audio id="audio" title="19 | 综合案例：电商平台技术架构是如何演变的？" controls="" preload="none"><source id="mp3" src="https://static001.geekbang.org/resource/audio/f8/35/f8fc29ff4a50970918b5dc849c7f7635.mp3"></audio>

你好，我是王庆友。

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

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

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

## 单体系统

<img src="https://static001.geekbang.org/resource/image/cf/cf/cf1a7a16f3a09eb85c7dd4c1c172c6cf.jpg" alt="">

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

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

## SOA架构

<img src="https://static001.geekbang.org/resource/image/84/c9/84da834d04bd838a6c76b7a535340bc9.jpg" alt="">

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

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

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

## 服务调用去中心化

<img src="https://static001.geekbang.org/resource/image/5c/a8/5cc8cddc0c25a847fce084ff890db1a8.jpg" alt="">

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

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

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

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

## 垂直分库

<img src="https://static001.geekbang.org/resource/image/84/18/8440763ab5e043a4099c1424ba595018.jpg" alt="">

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

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

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

## 水平分库及高可用部署

<img src="https://static001.geekbang.org/resource/image/d6/70/d6b81b2d5317e872632e1d09ce2ac170.jpg" alt="">

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

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

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

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

## 多机房部署

<img src="https://static001.geekbang.org/resource/image/d6/cf/d618f33b2ac8799bf6bab3949ed344cf.jpg" alt="">

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

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

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

## 服务调用本地化

<img src="https://static001.geekbang.org/resource/image/bf/07/bfbf070729b23e11308dc8ed19cbc607.jpg" alt="">

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

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

## 依赖分级管理

<img src="https://static001.geekbang.org/resource/image/f0/91/f010a6bb35627923566a0f3484447091.jpg" alt="">

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

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

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

## 多机房独立部署

<img src="https://static001.geekbang.org/resource/image/63/ca/6301edac111c2f9f4dbd5492cbf0aaca.jpg" alt="">

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

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

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

## 总结

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

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

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

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

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