Spring Boot 3.2四个新特点提升运行性能


随着 Spring Framework 6.1 和 Spring Boot 3.2 普遍可用性的临近,我们想分享一下 Spring 团队为让开发人员优化其应用程序的运行时效率而做出的几项努力的概述。

我们将介绍以下技术和用例:

  • Spring MVC 将使用 基于JDK 21 虚拟线程 Web 堆栈
  • 使用 Spring 和 GraalVM Native Image 优化容器部署
  • JVM 检查点恢复:使用 Spring 和 Project CRaC 扩展到零
  • 通过 Spring AOT 和 Leyden 项目一睹 OpenJDK 的未来

1、Spring MVC 将使用 基于JDK 21 虚拟线程 Web 堆栈
虚拟线程(Virtual Threads)旨在降低以简单、流行的每请求线程方式编写的服务器应用程序的成本,以接近最佳的硬件利用率进行扩展。

虚拟线程降低了 I/O 阻塞的成本,因此非常适合 Servlet 栈上的 Spring Web MVC 应用程序。通过虚拟线程设置,Spring MVC 可以在 Tomcat 或 Jetty 等平台上充分利用这些新的运行时特性。在大多数用例中,这不需要修改代码,就能自然地提供最佳性能,而无需对线程池配置进行微调。

在 Spring Framework 6.1 中引入一个名为 RestClient 的 "虚拟线程友好型现代 HTTP 客户端"(Spring Cloud Gateway 和 Spring 产品组合中的相关基础架构同样可以从虚拟线程设置和 Spring MVC 中受益,从而提供一致的整体体验。

那么,这对 WebFlux 和反应堆栈意味着什么呢?
我们特意选择了不同的阻塞堆栈和反应堆栈,以充分利用 WebFlux 服务器中的反应优势,并尽可能精简 Spring Web MVC 堆栈(start.spring.io 上迄今为止最常用的 Web 堆栈)和常规阻塞线程架构。

  • Servlet 容器上的 Spring MVC 是虚拟线程的理想定位,是提高传统网络应用程序可扩展性的理想解决方案。
  • 另一方面,WebFlux 服务器提供了优化的反应式堆栈,非常适合 Netty I/O 设置,通过不同的编程模型提供同等的运行时优势。

当您需要应用级并发(例如,发送多个远程 HTTP 请求(可能是流式请求)并合并结果)时:

  • WebFlux 和 Reactor 等反应式 API 目前具有无可比拟的附加值,
  • Kotlin 的 Coroutines 及其 Flow 类型也具有无可比拟的附加值,它们提供了命令式和声明式编程模型的有趣组合。
  • RSocket 是反应式交互模型带来巨大附加值的另一个例子。

请注意, Spring MVC 也提供可选的反应式支持。

因此,如果您只需要在服务器应用程序中的几个用例中处理并发性问题,您可以简单地使用带有虚拟线程设置的 Spring MVC 堆栈,并在 Web 控制器中无缝地包含反应式 WebClient 交互,Spring MVC 会将反应式返回值调整为 Servlet 异步响应。

Spring MVC 中的这种反应式支持完全是可选的,只有在实际使用反应式端点时才需要在堆栈中使用 Reactor 和 Reactive Streams,而且 HTTP 堆栈要基于 Servlet 容器,如 Tomcat 或 Jetty(而不是 Netty)。

对于典型的网络Web场景,我们预计虚拟线程将成为 Spring MVC 的常见选择,成为 Java 21+ 的 Spring 开发人员的Web服务器栈。

请确保使用 Spring Boot 3.2 或更高版本,将属性 spring.threads.virtual.enable 设置为 true,并使用最新的库和驱动程序版本来评估虚拟线程。

2、使用 Spring 和 GraalVM 原生镜像优化容器部署
我们将继续完善 Spring Boot 3 中引入的 GraalVM 本机支持。主要用例是使用 Buildpacks 构建优化的容器映像,其中包含一个微小的操作系统基础层,以及通过 Spring AOT(Ahead Of Time)转换和 GraalVM 本地映像编译器编译为本地可执行文件的应用程序。无需分发 JVM。

这样就可以部署在几十毫秒内启动的小型容器(通常比普通 JVM 的启动时间快 50 倍),降低应用基础架构的内存消耗,并立即实现峰值性能。

GraalVM 紧跟 Java 的新特性,例如已经提供了一流的虚拟线程支持:请参阅 Josh Long 最近发表的博文《 All together now》。

与 JVM 相比,GraalVM 的出色运行特性得益于不同的权衡取舍。本地镜像编译需要几分钟而不是几秒钟。它需要额外的元数据才能正确处理反射、代理和 JVM 的其他动态行为。Spring 会推断出很多此类元数据,但任何实际项目都可能需要一些额外的提示才能正常运行(例如,对于组织依赖关系)。最后,Spring AOT 转换和 GraalVM 原生镜像的结合要求我们在构建时冻结类路径和 Spring Boot Bean 条件。您通常可以在运行时配置中更改数据库的 URL 或密码,但不能更改数据库类型或更改 Spring Bean 的结构。

从历史上看,另一个缺点是由于缺乏即时编译而导致峰值性能有限。

有了即时启动和立即可用的峰值性能,Spring Boot 本机应用程序就能实现零扩展。

缩放为零
缩放为零是对无服务器的一种概括。工作负载不仅可以部署到无服务器云平台,还可以部署到任何提供在没有请求要处理时向零扩展能力的 Kubernetes 或云平台。通过 Kubernetes,您可以使用 Knative 或 KEDA 等解决方案来实现零扩展。

而且,你并不局限于函数,你可以将任何类型的应用程序、任何类型的编程模型(包括传统的网络应用程序)扩展到零。无服务器最重要的特点不在于技术,而在于它所实现的 "即用即付 "计费模式。

在各种用例中,扩展到零可能会很有趣。JVM 在开发大流量网站方面表现出色,但老实说,我们也开发了许多小型后台应用程序,这些应用程序通常不会一直使用。既然没人使用,我们为什么还要付费呢?还有一些暂存环境,它们通常只需要运行一小部分时间,还有一些微服务,缓存允许在大部分时间关闭其中的几个。还有高可用性,它迫使我们为每个服务维护两个实例,以备不时之需,因为我们的应用程序启动时间太长,无法从危险中恢复。

但是,对于那些无法接受 GraalVM 原生镜像所要求的权衡的项目来说,如何将其扩展为零呢?

3、JVM 检查点恢复:利用 Spring 和 CRaC 项目实现零扩展
CRaC 是一个 OpenJDK 项目,它定义了一个新的 Java API,允许您在 HotSpot JVM 上检查点和还原应用程序,该项目由 Azul Systems 开发,同时也得到 AWS Lambda 和 IBM OpenLiberty 的支持。它基于在 Linux 上实现检查点/还原功能的 CRIU 项目。

其原理如下:您几乎像往常一样启动应用程序,但使用的是启用了 CRaC 的 JDK 版本。然后在某个时间点,可能是在执行了所有常用代码路径而导致 JVM 发热的某些工作负载之后,使用 API 调用、jcmd 命令、HTTP 端点或其他机制触发检查点。

然后,运行中的 JVM 的内存表示(包括其热度)会被序列化到磁盘上,这样就可以在稍后的时间点(可能是在具有类似操作系统和 CPU 架构的另一台机器上)快速恢复。恢复后的进程保留了 HotSpot JVM 的所有功能,包括运行时的进一步 JIT 优化。

有趣的是,"检查点 "和 "恢复 "与 Spring 应用上下文生命周期的停止和启动阶段非常匹配。

Spring Framework 6.1 的 CRaC 支持主要是将 CRaC 和 Spring 生命周期映射到一起,其他支持与 CRaC 无关,主要是对 Spring 生命周期的完善,旨在正确关闭和重新创建套接字、文件和池。除了常规的启动和停止生命周期外,这里的目标是支持多个停止和重启周期。

与 GraalVM 一样,Project CRaC 允许应用程序从零扩展到零,即使在小型服务器上也能在几十毫秒内瞬间启动。这比普通 JVM 冷启动快 50 倍,与 GraalVM 本机镜像类似。但是,让我们来探讨一下其中的利弊得失。

请记住,我们正处于 CRaC 项目的早期阶段,Spring Boot 3.2 是支持 CRaC 的第一个版本。随着检查点还原技术和 Spring 支持的发展,其中一些限制可能会被取消。

4、通过 Spring AOT 和 Project Leyden 一窥 OpenJDK 的未来
我们已经看到了通过 GraalVM 和 CRaC 将 Spring 工作负载扩展到零的两种方法,但其中涉及到不小的权衡。如果有另一种方法可以在减少限制的情况下提高 Spring Boot 运行时特性,那会怎样呢?

您可能听说过 Project Leyden,这是一个新的 OpenJDK 项目,旨在改善 Java 程序的启动时间、达到峰值性能的时间和占用空间。

Project Leyden 最近引入了 "premain "优化(基本上是类数据共享 + AOT 的类固醇),有趣的是,Java Platform 团队发现了 Spring Ahead-Of-Time 优化的巨大协同作用,Spring Ahead-Of-Time 优化最初是为了支持 GraalVM 原生镜像而创建的,但已经能够将 JVM 的启动时间缩短 15%。

虽然 "premain "优化是高度实验性的(目前它只是 GitHub 上 Leyden 代码库的一个实验性分支),但 Spring 团队最近通过结合 JVM 上的 Spring AOT 和项目 Leyden 中的这些优化,已经测得 Spring Petclinic 示例应用程序的启动时间缩短了 2 到 4 倍,预热速度也加快了,而且几乎没有任何取舍。

与 GraalVM 和 CRaC 不同的是,这些优化措施目前还无法实现零扩展,因为它们无法让应用程序在生产中以几十毫秒的速度启动。但是,如果我们能在几乎不受任何限制的情况下显著改善 JVM 的启动和预热时间,那么它就有可能成为主流。