成本和伸缩性之间的关系
系统进行伸缩的核心原则之一是能够便捷地增添新资源以应对增长的负载。就许多系统来说,一种简单且有效的方法是部署多个无状态服务器实例,并利用负载均衡器在这些实例之间分配请求,如下图所示。
图片
倘若这些资源部署在云平台上,其成本包括:每个服务器实例的虚拟机部署成本,以及由新请求和活跃请求的数量、所处理的数据量决定的负载均衡器成本。
在此场景中,随着请求负载的增加,已部署的虚拟机需具备更大的处理能力,这便导致了更高的成本。同时,负载均衡器的开销也会随请求负载和数据大小成比例增长。所以,成本与规模相互关联,可伸缩性的设计决策必然会影响部署成本。若忽视这一点,可能会收到意想不到的巨额部署账单。
那么,该如何降低成本呢?主要有两种方式。其一,使用弹性负载均衡器,依据实时请求负载来调整服务器实例的数量。其二,增加每个服务器实例的容量,这通常是通过调整服务器部署参数(如线程数、连接数、堆大小等)来实现。精心选择参数设置能够显著提升性能,进而提高容量。
注意系统瓶颈
对一个系统进行伸缩,本质上是要提升它的容量。在上述示例中,我们通过部署更多的服务器实例来增强请求处理能力。然而,软件系统是由多个相互依存的处理元素或微服务构成的,所以在增加一部分微服务容量时,不可避免地会受到其他一些微服务的牵制。在我们的负载均衡示例里,假设服务器实例都连接至同一个共享数据库。随着部署的服务器数量增多,数据库的请求负载也随之增加(如下图)。
图片
在某个阶段,数据库会达到饱和状态,数据库访问将开始出现较大的延迟。此时,数据库成为瓶颈 —— 即便增加更多的服务器处理能力也于事无补。若要进一步进行伸缩,就需要以某种方式增加数据库的容量。可以尝试优化查询,或者增添更多的 CPU 或内存,也可以对数据库进行复制或分片。除此之外,还有许多其他解决方案。系统中的共享资源都有可能成为瓶颈。
当在架构的某些部分增加容量时,需要仔细考量下游的容量,确保不会突然对系统造成冲击。因为这样会迅速引发级联故障(参见下一条规则),并致使整个系统崩溃。数据库、消息队列、长延迟网络连接、线程和连接池以及共享微服务都是潜在的瓶颈。可以肯定的是,高流量负载会很快使这些瓶颈显露出来。关键在于当瓶颈暴露时,能够防止系统突然崩溃,并迅速部署更多的能力。
慢服务比故障服务更有害
在正常情况下,系统应当能够为微服务和数据库提供稳定且低延迟的通信。当系统负载处于正常的配置水平时,性能是可预测、一致且快速的,如下图所示。
图片
一旦客户端负载超出正常水平,微服务之间的请求延迟便会开始增加。特别是在传入的请求负载持续超过容量(如服务 B)的情况下,未完成的请求会在微服务 A 中堆积,由于下游延迟变慢,该微服务此时接收的请求比已完成的请求更多。
图片
当一个服务因抖动或资源耗尽而不堪重负时,服务将无法响应客户端,客户端也会陷入停滞状态。其直接结果就是级联故障 —— 缓慢的服务会致使请求沿着请求路径不断累积,直至整个系统崩溃。一些架构模式(如回路断路器和隔板)可用于防止级联故障。若服务的延迟超过指定值,断路器会调节请求负载,甚至将其断开。当仅有一个下游依赖项发生故障时,隔板能够保护上游的微服务不发生故障。这些措施可用于构建具有弹性且高度可伸缩的架构。