水平扩展与垂直扩展:哪种更适合调整应用程序资源消耗并优化云成本?
译自 How to Avoid Overprovisioning Java Resources,作者 Pratik Patel。
开发人员是奇怪的动物;我们会在应用程序中使用闪亮的新工具或库,而不会过多考虑,但在部署到生产环境时却很谨慎。没有人希望他们的寻呼机在半夜开始嗡嗡作响,更不用说保持应用程序以 高 9s 的可靠性运行的压力了。开发人员在构建和编码应用程序时很有冒险精神,但在操作元素方面却非常保守。
出现了一种称为过度配置的现象:在云环境中为应用程序的部署添加额外的马力(通常是 CPU 和 RAM),以确保应用程序有足够的启动空间,并为应用程序运行时发生的峰值提供空间。
幸运的是,有一些方法可以减少您对度配置的需求,从而节省大量的云支出。我将专门研究 Java 应用程序中的过度配置。
正如任何开发人员或 DevOps 人员都会告诉您的那样,应用程序的流量几乎从未在一天或一周内保持一致,并且绝大多数应用程序在一段时间内负载不均衡。每个应用程序都有低谷,在这些低谷中,它不会处理许多用户请求或处理数据,并且有峰值,在这些峰值中,应用程序利用率非常高。只要应用程序的实例没有被推到应用程序出现问题的程度,这些峰值就没有问题,例如:
- 响应延迟异常长,无法满足服务级别协议 (SLA)。
- 内存过度使用会导致 Java 虚拟机 (JVM) 中的垃圾收集器 (GC) 抖动。
- 资源不足(CPU 线程、文件/网络句柄)会导致拒绝传入请求,无法进行处理。
最后两个问题会导致应用程序完全无响应,看起来好像没有进行任何处理。在测试期间,开发人员会注意到此上限,并调整应用程序实例所需的 CPU 内核和内存数量。然后,他们会添加通常是任意数量的 CPU 和内存,以适应峰值——过度配置应用程序的可用资源。过度配置是开发团队的安全网,以确保一切顺利运行,用户对响应时间感到满意。
但是,过度配置会给运行应用程序增加大量成本。运行中的云 VM 通常具有固定的 CPU(内核或虚拟 CPU)和内存,并且不被认为是弹性的。这意味着您正在为已配置的容量付费,无论您是否完全利用它。这种额外的空间可能占您配置的云计算的 5% 到 50%,具体取决于开发团队认为需要多少额外容量来适应峰值。
为了帮助您处理过度配置并节省云支出,您可以使用某些策略,具体取决于您是进行垂直扩展还是水平扩展。我将描述这两种扩展模型以及每种模型的策略。无论您是在云中运行还是在本地运行,都可以使用这些策略和技术。
垂直扩展是扩展应用程序以处理更多负载的更简单的策略,但它不如水平扩展灵活。垂直扩展意味着在物理或虚拟服务器上为应用程序添加更多 CPU 内核和更多内存(或者如果您的应用程序是 I/O 密集型,则添加更快或更多 SSD 存储)。更改这些内容需要停止并重新启动应用程序,这会造成中断。但是,有一种方法可以减少这种类型的扩展的过度配置。
性能测试被认为是最困难的测试类型——它需要深入了解 整个 应用程序和所有连接的服务。设置性能测试环境需要大量工作,并且使其与生产环境的特征保持一致也是一项挑战。生成模拟生产的负载以及拥有应用程序数据(生产数据的规模和形状)都需要思考和努力才能做到正确。
因此,开发团队经常会做出一些假设并采取一些捷径。这很好,但会导致高估和过度配置应用程序生产实例的大小。
开发人员可以做些什么来获得更好的性能数据,以便正确调整其 Java 应用程序的大小?以下是可以执行的三项主要操作,以确定应用程序的峰值容量需求。
1. 测量服务器和 JVM 的 CPU 和内存利用率
通常,开发人员只关注服务器(或虚拟机)的 CPU 和内存使用情况,以确定处理峰值负载所需的 CPU 和内存数量。使用在 JVM 内监控这些内容的工具将有助于将这些设置在正确的级别:
- JVM GC 监控:这可以帮助检测内存不足,这会导致高 CPU 利用率,因为 JVM 会陷入 GC 垃圾场景。这也有助于检测分配了太多内存的情况,从而导致长时间的 GC 暂停,进而导致比预期更长的延迟。减少不必要的内存也可以节省资金。
- JVM 线程监控:这可以帮助检测何时 CPU 不足,这会导致响应时间过长或无响应。这可以帮助检测太多空闲线程,通过减少分配的内核数量,您也可以节省资金。
2. 新的 JVM 版本比旧版本提供更好的性能
在我们从 JDK 11 到 17 到 21 的测试中,我们看到 JVM 的每次发布都提高了 CPU 使用率。当然,您的应用程序代码可能需要一些调整,尤其是在您的应用程序基于比 Java 11 更早的版本的情况下。
还有一些不同的 GC 算法可以让您从云虚拟机中获得更高的效率;但是,这高度依赖于您的应用程序的内存使用情况。例如,进行大量数据处理和转换的应用程序将具有与 RESTful 应用程序不同的 GC 配置文件。您可以查看 Azul 博客的 GC 部分 以获取更多信息。
3. 了解 JVM 的工作原理
下图显示了典型 Java 应用程序从 JVM 启动到随时间推移执行的方式。启动时 CPU 使用率很高;这是 JVM 启动、加载类等。然后您的应用程序框架(例如 Spring Boot)启动、初始化并进入“准备服务请求”状态。
请注意峰值上方的线,它显示了此应用程序的 VM 部署的 CPU 过度配置程度(用于突发高负载的安全网)。随着 JVM 的即时 (JIT) 编译器优化代码路径,应用程序变得 更 效率 - 它使用更少的 CPU 来服务相同数量的负载。最终发生的是,在您为应用程序提供的额外空间之上,JVM 由于 JIT 编译器优化而达到更低的 CPU 利用率基线。因此,过度配置的量会增加!这意味着您现在在分配的 CPU 中有更多浪费 - 并且有机会节省更多资金。
使用高性能 JVM 意味着您可以减少(或完全消除)过度配置。了解此曲线及其对应用程序的影响可以帮助您减少为应用程序的 VM 实例分配的安全网。如果您知道长尾峰值将在哪里,就可以降低顶线(“过度配置”),您将能够分配更少的 CPU 内核并节省云支出。
多年来,弹性计算一直被吹捧为可扩展应用程序开发的圣杯,而水平扩展是弹性计算的基础。水平扩展意味着通过添加更多服务器(具有自己的 CPU 和内存)来为应用程序添加容量,而不是向现有服务器添加更多 CPU 内核和内存。
但是,水平扩展比垂直扩展更复杂,需要更多规划和更多外部(对应用程序)设置。并且它不如垂直扩展效率高,因为您必须引入路由层,这意味着更多处理和网络开销。
在 Java 应用程序的水平扩展部署中减少过度配置是通过根据需要添加和删除容量来完成的,通常以自动方式检测负载并启动或关闭应用程序节点实例。因此,您将拥有一些过度配置的容量,但数量很少,持续时间很短(取决于您的配置方式)。
随着我们从将应用程序构建为单体应用程序转变为微服务(甚至更小的云函数),我们使应用程序越来越小。这些不同架构有优点和权衡,但在应用程序的 云成本优化 的背景下,特别是使用水平扩展来获得弹性计算更小(或小到中等规模)是最好的。
减少应用程序大小会减少您需要分配给应用程序每个实例的 CPU 和内存量。这允许更增量式的扩展和更有效地使用资源,进而意味着对云成本的更细粒度控制。部署单元越小,您在扩展和缩减时支付的费用就越多(或更少)。当然,这只有在您使用自动扩展时才有可能。
自动缩放是指应用程序根据负载增加或减少添加或删除应用程序实例节点的能力。在云成本优化方面,我们更感兴趣的是更积极地缩减规模,或者停止应用程序实例节点。根据您用于构建应用程序集群的环境,您将获得不同的自动缩放选项。最流行的自动缩放平台是 Kubernetes,它支持自动缩放。Kubernetes 的主要权衡是它为标准的固定分布式集群部署引入了高度的复杂性。
Kubernetes 的更简单替代方案是容器即服务 (CaaS),例如 AWS Fargate、Google Cloud Run 或 Microsoft Azure 容器。这些部署服务提供了一种更轻松的方式来部署您的应用程序。您将应用程序(在一个 Docker 容器中)提供给服务,它会处理向上和向下自动缩放。CaaS 解决方案的权衡是它们的成本比标准 VM 更高,可能比托管 Kubernetes 部署更高。
减少过度配置可以帮助您节省云成本。最终,您可以实现什么在很大程度上取决于您的应用程序及其性能配置文件。了解应用程序启动和运行时发生的情况对于您使用哪种策略来减少过度配置都很有用。了解 Java 应用程序的 CPU 和内存配置文件将有助于您了解应用程序在运行时的性能。
考虑使用更有效的高性能 JVM,例如 Azul Platform Prime,用于小型到大型 Java 应用程序部署。Azul Platform Prime:
- 由于其先进的 C4 GC、低级优化和先进的 Falcon JIT 编译器,它比其他 JVM 更好地处理峰值负载。
- 可以使用 ReadyNow 避免 JIT 爬坡(以及高 JIT CPU 利用率)。
- 由于它处理峰值负载的方式,它在负载下提供更低的延迟,同时处理更高的峰值。
要了解更多信息,请下载 IDC 关于 优化 Java 应用程序性能以提高业务成果和云成本效率 的白皮书。