每个开发者都希望确保自己的应用程序有足够的资源余量来启动,并应对应用程序运行时出现的峰值。为云环境中的应用程序部署添加额外的算力资源可以确保应用程序以最佳方式运行,但随之而来的成本可能非常昂贵。在这篇博文中,Azul 开发者关系副总裁 Pratik Patel 将探讨过度预配置 Java 资源的问题。所幸有一些方法可以减少您对于过度预配置的需求,从而节省大量云成本。在本文中,作者将专注 Java 应用程序背景下的过度预配置问题。
开发者处事似乎十分矛盾:我们虽然会不假思索地在应用程序中使用最新工具或库,但在部署到生产环境中时却小心谨慎。谁也不想在深夜被传呼机的震动声惊醒,更不用说为了维持应用程序近乎完美的高可靠性(即达到 99.9% 的稳定运行率)所面临的压力了。因此,开发者在构建应用程序并为其编写代码时敢于冒险,但在操作层面非常保守。
在上述原因的驱使下,出现了一种称为“过度预配置”的现象:为云环境中的应用程序部署添加额外的算力资源(通常是 CPU 和 RAM),以确保应用程序有足够的资源余量来启动,并应对应用程序运行时出现的峰值。
所幸有一些方法可以减少您对于过度预配置的需求,从而节省大量云成本。在本文中,作者将专注 Java 应用程序背景下的过度预配置问题。
应用程序负载从来不是稳定的
As a任何开发者或 DevOps 人员都会告诉您,应用程序的流量在一天或一周中几乎从来都不是恒定的,并且随着时间的推移,绝大多数应用程序的负载都不均匀。每个应用程序都有性能低谷和性能峰值:在低谷时无法处理多用户请求或数据,在峰值时应用程序利用率极高。只要应用程序的实例没有让应用程序出现以下问题,您便无需担心这些峰值:
- 响应的延迟极长且不满足服务级别协议 (SLA) 的要求。
- 内存的过度利用导致 Java 虚拟机 (JVM) 中的垃圾收集器 (GC) 崩溃。
- 缺乏资源(CPU 线程、文件/网络句柄)导致传入请求被拒绝并且无法进行处理。
最后两个问题可能会使应用程序完全无响应,看起来好像没有在处理任何任务。在测试过程中,开发者会注意这个上限并调整所需的 CPU 核心数量和内存大小。然后,他们通常会针对峰值添加任意量的 CPU 和内存,因此会过度预配置应用程序的可用资源。过度预配置是开发团队的“定心丸”,可确保一切顺利运行并让用户对响应时间感到满意。
数百万 Java 开发人员、数亿设备以及世界上最受认可的企业都信赖 Azul 的 Java 平台,并相信 Azul 能为他们的应用程序提供卓越的功能、性能、安全性、价值和成功。
然而,过度预配置 Java 资源会大幅增加应用程序的运行成本。正在运行的云虚拟机通常具有固定的 CPU(核心或虚拟 CPU)和内存,无法弹性扩缩。这意味着无论您是否满负载使用云虚拟机,您都需要为已配置的容量付费。这种资源余量可能是预配置云计算的 5% 到 50%,具体取决于开发团队认为需要多少额外容量来应对峰值。
为了帮助您解决过度预配置问题并节省云支出,您可以根据垂直扩展或水平扩展使用相应的策略。我将在下文中详细说明这两种扩展模型以及每种模型对应的策略。无论您是在云端还是在本地运行应用程序,都可以使用这些策略和技术。
垂直扩展
在为处理更多负载而扩展应用程序方面,垂直扩展是更简单的策略,但不如水平扩展灵活。垂直扩展的意思是向物理或虚拟服务器上的应用程序添加更多 CPU 核心和更多内存(如果您的应用程序是 I/O 密集型,则添加更快或更多的 SSD 存储)。做出这些更改时需要停止并重新启动应用程序,这会使服务中断。但有一种方法可以减少此类扩展的过度预配置。
更好的负载测试和估计
性能测试是公认最困难的测试类型,因为它需要测试者对整个应用程序和所有连接的服务有深入的了解。设置性能测试环境非常耗费精力,而使测试环境与生产环境的特征保持一致也是一项挑战。正确生成可模拟生产环境的负载并拥有应用程序数据(生产环境数据的大小和格式)需要边思考边努力才能实现。
正因为如此,开发团队经常会做出一些假设并采取一些捷径。这虽然可行,却会导致高估和过度预配置应用程序生产实例的大小。
开发者可以采取哪些措施来获得更好的性能数据,以便合理调整其 Java 应用程序的大小?以下是为确定应用程序的峰值容量要求,您可以采取的三项主要措施。
1.衡量服务器和 JVM 的 CPU 和内存利用率
通常,开发者仅通过服务器(或虚拟机)的 CPU 和内存利用率来确定处理峰值负载所需的 CPU 和内存量。使用在 JVM 中监控 CPU 和内存的以下工具将有助于将这两者设置在正确的级别:
- JVM 垃圾收集监控:这可以帮助检测内存过低的情况。当 JVM 进行垃圾收集时,内存太少会导致 CPU 利用率高。该工具还可以帮助检测何处分配了过多内存。分配过多内存会导致垃圾收集暂停时间过长,从而导致延迟时间比预期更长。减少不需要的内存也可以节省成本。
- JVM 线程监控:这可以帮助检测何时没有足够的 CPU。CPU 不足会导致响应时间过长或无响应。该工具可以帮助检测空闲线程过多的情况,并且通过减少分配的核心数量,您也可以节约成本。
2.新 JVM 版本比旧版本性能更佳
在从 JDK 11 到 17 再到 21 的测试中,我们看到每个新 JVM 版本的 CPU 使用率都依次提高。当然,您的应用程序代码可能需要一些调整,特别是基于 Java 11 之前版本的应用程序。
其他垃圾收集算法也可以让您的云虚拟机获得更高效率。但是,这很大程度上取决于应用程序的内存利用率。例如,进行大量数据处理和转换的应用程序具有与 RESTful 应用程序不同的垃圾收集配置文件。您可以查看 Azul 博客的垃圾收集器专题,了解更多信息。
3.了解 JVM 的工作原理
下图显示了典型的 Java 应用程序如何运行,涵盖从 JVM 的启动到它如何随着时间的推移执行任务的过程。启动时 CPU 占用率高,这是因为 JVM 预热、加载类等。然后您的应用程序框架(例如 Spring Boot)启动,初始化并达到“可以开始处理请求”状态。
请注意峰值上方的横线,它显示出为此应用程序的 VM 部署过度预配置了多少 CPU(针对突发高负载,能让开发者放心的预配置资源)。由于 JVM 的即时 (JIT) 编译器优化了代码路径,应用程序变得更加高效(使用更少的 CPU 处理同样的负载)。最终,除了您为应用程序提供的额外资源余量之外,由于 JIT 编译器优化,JVM 还达到了较低的 CPU 利用率基线。因此,过度预配置量会增加!这表示您当前在已分配的 CPU 上有更多的资源浪费,但同时这也说明您有机会节省更多成本。
使用高性能 JVM 意味着您可以减少(或完全消除)过度预配置。看懂这条曲线以及它如何影响您的应用程序可以帮助您减少分配给应用程序 VM 实例的预配置资源。如果您能在确定长尾峰值后降低最上方的那条横线(“过度预配置”),就能分配更少的 CPU 核心并节省云支出。
水平扩展
多年来,弹性计算一直被视为可扩展应用程序开发的终极目标,而水平扩展正是弹性计算的基础。水平扩展的意思是,通过添加更多自带 CPU 和内存的服务器来增加应用程序的容量,而不是向现有服务器添加更多 CPU 核心和内存。
然而,水平扩展比垂直扩展更复杂,并且需要更多的规划和更多应用程序外部设置。此外,水平扩展的效率低于垂直扩展,因为您必须引入路由层,这会增加处理负担和网络开销。
为减少 Java 应用程序水平扩展部署中的过度预配置,您需要按需添加和删除容量,通常的做法是自动检测负载并启动或关闭应用程序节点实例。如此一来,在一段短时间内(取决于您的配置方式),您可以将过度预配置的容量维持在少量。
缩减应用程序的大小
阅读《云成本优化指南》
随着我们将应用程序的架构从单体转为微服务(甚至更小的云功能),我们已使应用程序变得越来越小。这些不同的架构各有利弊,但在应用程序云成本优化的背景下,专门使用水平扩展来使弹性计算规模更小(或维持在中小规模)是最佳做法。
缩减应用程序大小可以减少需要分配给应用程序每个实例的 CPU 和内存量。这可以使您进行更多的增量扩展,更有效地利用资源,进而对云成本进行更精细的控制。部署单元越小,您在纵向扩展和缩减时支付的费用就相应地越多或越少。当然,这只有在您使用自动扩缩时才能实现。
使用自动扩缩
自动扩缩是指应用程序随着负载增加或减少而添加或删除应用程序实例节点的能力。通过云成本优化,我们想要更大幅度地缩小规模或停用应用程序实例节点。根据用于构建应用程序集群的环境,您有不同的自动扩缩选项。最热门的自动扩缩平台是 Kubernetes,它支持自动扩缩。Kubernetes 的主要缺点是使标准固定分布式集群部署变得非常复杂。
要使情况变得简单,可以用以下容器即服务 (CaaS) 替代 Kubernetes:AWS Fargate、Google Cloud Run 或 Microsoft Azure 容器。这些部署服务可以使部署应用程序更加简单。将 Docker 容器中的应用程序提供给这种服务,这种服务便会处理自动扩缩。CaaS 解决方案的缺点是它们的成本高于标准虚拟机,甚至可能高于托管的 Kubernetes 部署。
结语
减少过度预配置可以帮助您节省云成本。从根本上说,您能实施的策略在很大程度上取决于您的应用程序及其性能配置文件。无论您使用哪种策略来减少过度预配置,了解应用程序启动和运行时发生的情况都很有裨益。了解 Java 应用程序的 CPU 和内存配置文件有助于您了解应用程序在运行时的性能。
欢迎考虑使用更高效的高性能 JVM(例如 Azul Platform Prime)进行各种规模的 Java 应用程序部署。Azul Platform Prime 有助于降低 Java 资源的过度预配置:
- 借助先进的 C4 垃圾收集器、低级别优化和出色的 Falcon JIT 编译器,能比其他 JVM 更好地处理峰值负载。
- 可以使用 ReadyNow 避免 JIT 加速(以及 JIT 导致的高 CPU 利用率)。
- 通过处理峰值负载的独特方式,可在高负载下提供较低的延迟,并能够应对更高的峰值。