可扩展性

可扩展性(Scalability)

可扩展性(Scalability)是用来描述系统应对负载增长能力的术语。系统今天能可靠运行,并不意味未来也能可靠运行。服务 降级(degradation)的一个常见原因是负载增加,例如:系统负载已经从一万个并发用户增长到十万个并发用户,或者从一百万增长到一千万。也许现在处理的数据量级要比过去大得多。

扩展维度

共享与无共享扩展

垂直扩展,共享内存架构

如果你需要的只是扩展至更高的载荷(load),最简单的方法就是购买更强大的机器(有时称为垂直扩展(vertical scaling)或向上扩展(scale up))。许多处理器,内存和磁盘可以在同一个操作系统下相互连接,快速的相互连接允许任意处理器访问内存或磁盘的任意部分。在这种共享内存架构(shared-memory architecture)中,所有的组件都可以看作一台单独的机器。

共享内存方法的问题在于,成本增长速度快于线性增长:一台有着双倍处理器数量,双倍内存大小,双倍磁盘容量的机器,通常成本会远远超过原来的两倍。而且可能因为存在瓶颈,并不足以处理双倍的载荷。并且在大型机中,尽管任意处理器都可以访问内存的任意部分,但总有一些内存区域与一些处理器更接近(称为非均匀内存访问(nonuniform memory access, NUMA))。为了有效利用这种架构特性,需要对处理进行细分,以便每个处理器主要访问临近的内存,这意味着即使表面上看起来只有一台机器在运行,分区(partitioning)仍然是必要的。

共享内存架构可以提供有限的容错能力,高端机器可以使用热插拔的组件(不关机更换磁盘,内存模块,甚至处理器)——但它必然囿于单个地理位置的桎梏。另一种方法是共享磁盘架构(shared-disk architecture),它使用多台具有独立处理器和内存的机器,但将数据存储在机器之间共享的磁盘阵列上,这些磁盘通过快速网络连接。这种架构用于某些数据仓库,但竞争和锁定的开销限制了共享磁盘方法的可扩展性。

水平扩展,无共享架构

相比之下,无共享架构(shared-nothing architecture)(有时称为水平扩展(horizontal scale)或向外扩展(scale out))已经相当普及。在这种架构中,运行数据库软件的每台机器/虚拟机都称为节点(node)。每个节点只使用各自的处理器,内存和磁盘。节点之间的任何协调,都是在软件层面使用传统网络实现的。

无共享系统不需要使用特殊的硬件,所以你可以用任意机器——比如性价比最好的机器。你也许可以跨多个地理区域分布数据从而减少用户延迟,或者在损失一整个数据中心的情况下幸免于难。随着云端虚拟机部署的出现,即使是小公司,现在无需 Google 级别的运维,也可以实现异地分布式架构。

在这一部分里,我们将重点放在无共享架构上。它不见得是所有场景的最佳选择,但它是最需要你谨慎从事的架构。如果你的数据分布在多个节点上,你需要意识到这样一个分布式系统中约束和权衡 ——数据库并不能魔术般地把这些东西隐藏起来。

虽然分布式无共享架构有许多优点,但它通常也会给应用带来额外的复杂度,有时也会限制你可用数据模型的表达力。在某些情况下,一个简单的单线程程序可以比一个拥有超过 100 个 CPU 核的集群表现得更好。另一方面,无共享系统可以非常强大

当负载参数增加时,如何保持良好的性能?适应某个级别负载的架构不太可能应付 10 倍于此的负载。如果你正在开发一个快速增长的服务,那么每次负载发生数量级的增长时,你可能都需要重新考虑架构。

人们经常讨论纵向扩展(scaling up)(垂直扩展(vertical scaling),转向更强大的机器)和横向扩展(scaling out)(水平扩展(horizontal scaling),将负载分布到多台小机器上)之间的对立。跨多台机器分配负载也称为“无共享(shared-nothing)”架构。可以在单台机器上运行的系统通常更简单,但高端机器可能非常贵,所以非常密集的负载通常无法避免地需要横向扩展。现实世界中的优秀架构需要将这两种方法务实地结合,因为使用几台足够强大的机器可能比使用大量的小型虚拟机更简单也更便宜。

有些系统是 弹性(elastic)的,这意味着可以在检测到负载增加时自动增加计算资源,而其他系统则是手动扩展(人工分析容量并决定向系统添加更多的机器)。如果负载极难预测(highly unpredictable),则弹性系统可能很有用,但手动扩展系统更简单,并且意外操作可能会更少。跨多台机器部署**无状态服务(stateless services)**非常简单,但将带状态的数据系统从单节点变为分布式配置则可能引入许多额外复杂度。出于这个原因,常识告诉我们应该将数据库放在单个节点上(纵向扩展),直到扩展成本或可用性需求迫使其改为分布式。

XYZ 三维度扩展

并发的增加对我们的后端架构提出了巨大的挑战,要求我们的系统弹性可扩展。

扩展维度

上图从三个维度概括了一个系统的扩展过程:

  • X 轴即水平复制,即在负载均衡服务器后增加多个 Web 服务器。
  • Z 轴是对数据库的扩展,即分库分表,分库是将关系紧密的表放在一台数据库服务器上,分表是因为一张表的数据太多,需要将一张表的数据通过 hash 放在不同的数据库服务器上。
  • Y 轴是业务方向的扩展,才能将巨型应用分解为一组不同的服务,将应用进一步分解为微服务(分库),例如订单管理中心、客户信息管理中心、商品管理中心等等。

X 轴:代表运行多个负载均衡器之后运行的实例,即通过应用服务器集群提供系统的处理能力和可用性,但数据库层面不支持水平扩展; Y 轴:应用功能分解,将应用层和数据模型层的垂直切分,实现微服务架构。各应用可以独立的进行水平扩展,单一应用内部数据库层面仍然无法实现水平扩展; Z 轴:数据分片,对应用内的数据进行分库分表,实现数据库层面的水平扩展。

这三个维度的划分,在一定意义上也代表了一个单体系统向分布式系统演进的一个路径: 第一阶段:单体应用,通过应用服务器集群来提高系统的可用性,支持应用层级的弹性扩展。但是,随着数据量的不断增大,开发人员已经使用了缓存、读写分离等策略,仍然会达到单一数据库集群处理能力瓶颈,无论再怎么增加应用服务器都无法提高系统的处理能力,这是系统往往会演进到第二阶段; 第二阶段:微服务应用,应用和数据库按照业务领域独立部署,形成多个微服务应用集群。该阶段一定程度上缓解了系统压力,能够提供更好的性能;同时,各微服务应用之间相互独立部署,在交付周期和故障隔离方面能够提供更高的灵活性。但是,每个微服务应用由于还是使用单一数据集群,在系统的容量、高并发等方面,存在着无法逾越的瓶颈; 第三阶段:数据分布式,领域数据库进行分库分表或者读写分离,在数据库层面提供水平扩展能力。基于数据的分布式,可以有效的解决数据库瓶颈问题,让系统处理能力形成进一步的提升;但是,面对数据库连接数限制的问题,在扩展到一定规模之后,单纯的分库分表或读写分离也会遇到扩容瓶颈,这时候就需要逻辑数据中心(LDC)闪亮登场了。

总结而言,垂直伸缩只能通过增加服务器的配置有限度地提升系统的处理能力,而水平伸缩能够仅通过增减服务器数量相应地提升和降低系统的吞吐量;这种分布式系统架构,在理论上为吞吐量的提升提供了无限的可能。因此,用于搭建互联网应用的服务器也渐渐放弃了昂贵的小型机,转而采用大量的廉价 PC 服务器。

并发的增加对我们的后端架构提出了巨大的挑战,要求我们的系统弹性可扩展。

上图从三个维度概括了一个系统的扩展过程:

  • X 轴即水平复制,即在负载均衡服务器后增加多个 Web 服务器。
  • Z 轴是对数据库的扩展,即分库分表,分库是将关系紧密的表放在一台数据库服务器上,分表是因为一张表的数据太多,需要将一张表的数据通过 hash 放在不同的数据库服务器上。
  • Y 轴是业务方向的扩展,才能将巨型应用分解为一组不同的服务,将应用进一步分解为微服务(分库),例如订单管理中心、客户信息管理中心、商品管理中心等等。

总结而言,垂直伸缩只能通过增加服务器的配置有限度地提升系统的处理能力,而水平伸缩能够仅通过增减服务器数量相应地提升和降低系统的吞吐量;这种分布式系统架构,在理论上为吞吐量的提升提供了无限的可能。因此,用于搭建互联网应用的服务器也渐渐放弃了昂贵的小型机,转而采用大量的廉价 PC 服务器。

在分布式系统的背景下,企业架构也由早期的单体式应用架构渐渐转为更加灵活的分布式应用架构,经历了单体分层架构、SOA 服务化架构、微服务架构、云原生架构等不同架构模式的变迁。后端开发不再局限于单一技术栈,并且为了应对更加庞大的集群规模,单纯的分布式系统已经难于驾驭,因此技术圈开启了一个概念爆发的时代:SOA、DevOps、容器、CI/CD、微服务、Service Mesh 等概念层出不穷,而 Docker、Kubernetes、Mesos、Spring Cloud、gRPC、Istio 等一系列产品的出现,标志着云时代已真正到来。

服务端开发与架构全景图

分布式场景下比较著名的难题就是 CAP 定理。CAP 定理认为,在分布式系统中,系统的一致性(Consistency)、可用性(Availability)、分区容忍性(Partition tolerance),三者不可能同时兼顾。在分布式系统中,由于网络通信的不稳定性,分区容忍性是必须要保证的,因此在设计应用的时候就需要在一致性和可用性之间权衡选择。互联网应用比企业级应用更加偏向保持可用性,因此通常用最终一致性代替传统事务的 ACID 强一致性。

应用演化后重新处理数据

在维护衍生数据时,批处理和流处理都是有用的。流处理允许将输入中的变化以低延迟反映在衍生视图中,而批处理允许重新处理大量累积的历史数据以便将新视图导出到现有数据集上。特别是,重新处理现有数据为维护系统提供了一个良好的机制,演化并支持新功能和需求变更。不需要重新进行处理,模式演化仅限于简单的变化,例如向记录中添加新的可选字段或添加新类型的记录。无论是在写模式还是在读模式中都是如此。另一方面,通过重新处理,可以将数据集重组为一个完全不同的模型,以便更好地满足新的要求。

大规模的“模式迁移”也发生在非计算机系统中。例如,在 19 世纪英国铁路建设初期,轨距(两轨之间的距离)就有了各种各样的竞争标准。为一种轨距而建的列车不能在另一种轨距的轨道上运行,这限制了火车网络中可能的相互连接。在 1846 年最终确定了一个标准轨距之后,其他轨距的轨道必须转换,但是如何在不停运火车线路的情况下进行数月甚至数年的迁移?解决的办法是首先通过添加第三条轨道将轨道转换为双轨距(dual guage)或混合轨距。这种转换可以逐渐完成,当完成时,两种轨距的列车都可以在线路上跑,使用三条轨道中的两条。事实上,一旦所有的列车都转换成标准轨距,那么可以移除提供非标准轨距的轨道。以这种方式“再加工”现有的轨道,让新旧版本并存,可以在几年的时间内逐渐改变轨距。然而,这是一项昂贵的事业,这就是今天非标准轨距仍然存在的原因。例如,旧金山湾区的 BART 系统使用与美国大部分地区不同的轨距。

衍生视图允许渐进演化(gradual evolution)。如果你想重新构建数据集,不需要执行迁移,例如突然切换。取而代之的是,你可以将旧架构和新架构并排维护为相同基础数据上的两个独立衍生视图。然后可以开始将少量用户转移到新视图,以测试其性能并发现任何错误,而大多数用户仍然会被路由到旧视图。你可以逐渐地增加访问新视图的用户比例,最终可以删除旧视图。

这种逐渐迁移的美妙之处在于,如果出现问题,每个阶段的过程都很容易逆转:你始终有一个可以回滚的可用系统。通过降低不可逆损害的风险,你能对继续前进更有信心,从而更快地改善系统。

上一页