Spring Boot 原生应用的路径

工程 | Sébastien Deleuze | 2020年6月10日 | ...

借此机会,我很高兴地宣布 Spring GraalVM Native 0.7.0 发布,并向大家汇报我们关于 Spring Boot 原生镜像工作的最新进展。

为什么?

原生镜像 (Native image) 提供了一种构建和运行 Spring Boot 应用的方式,其特性与常规的 JVM 部署有所不同。

  • 生成的结果是一个原生可执行文件,它包含了你的应用程序以及运行它所需的 JDK 子集和依赖项。

  • 实际上,这个可执行文件很可能被打包到一个高度优化的容器镜像中(支持 FROM scratch Docker 镜像),攻击面缩小,非常适合与 Kubernetes 结合使用。

  • 启动时间几乎是即时的,并且峰值性能立即可用,这使得支持“scale-to-zero”(Serverless)应用成为可能,包括常规的 Spring Boot Web 应用。

  • 内存消耗降低,非常适合拆分成多个微服务的系统。

正如你所料,原生镜像并非“免费午餐”,这些强大的功能也带来了一些缺点。

  • GraalVM 原生平台相对年轻,远不如 JVM 成熟。

  • 这种新型 Java 尚未得到 JVM 库的良好支持和充分测试。

  • 它需要显式的初始化、资源、反射和代理配置。

  • 构建时间非常长,并且构建内存消耗很高。

  • 吞吐量较低,延迟较高(更多详情)。

显然,原生镜像是不断变化的。其中一些特性很可能会在未来发生演变。Spring 团队目前正积极与 GraalVM 团队合作,以确保 Spring 以及通过 Spring Boot 集成的更广泛的 JVM 生态系统在编译成原生镜像时能够良好运行。这项工作包括 GraalVM 原生镜像的修复和新功能Spring 本身的更改,以及改进与 GraalVM 原生平台的可测试性和可维护性的额外工作。

值得注意的是,原生镜像的范围现在已经超出了 GraalVM,因为 Mark Reinhold 最近宣布了 Project Leyden,这是一个致力于在 Java 平台层面标准化原生镜像的努力。

Spring GraalVM Native 0.7.0

我很高兴地宣布 Spring GraalVM Native 0.7.0 现已可用。 spring-graalvm-native 是我们目前孵化 Spring Boot 应用原生镜像支持的实验性项目,本次里程碑版本带来了诸多改进,包括:

您可以阅读 详细的变更日志文档。还有其他值得深入探讨的改进。

专门针对函数式 Spring 应用的支持

如果您关注过我的 Spring Fu 工作,您可能已经了解,可以使用函数式 Bean 注册来配置 Spring Boot 应用,而不是使用 @Configuration

为了说明这个原理,请看以下基于注解的配置:

@Configuration
public class SampleConfiguration {

    @Bean
    public Foo foo() {
        return new Foo();
    }

    @Bean
    public Bar bar(Foo foo) {
        return new Bar(foo);
    }
}

它将转换为以下函数式替代方案:

public class SampleInitializer
      implements ApplicationContextInitializer<GenericApplicationContext> {

    @Override
    public void initialize(GenericApplicationContext context) {
        context.registerBean(Foo.class, () -> new Foo());
        context.registerBean(Bar.class, () -> new Bar(context.getBean(Foo.class)));
    }
}

这种基于 Lambda 的配置机制的一个有趣的特点是,它不需要 GraalVM 原生镜像的反射配置,因为 GraalVM 原生编译器能够原生理解它,包括其在构建时以方法/字段为粒度移除未使用的代码的能力。

请注意,从 @Configuration 转换为函数式配置可以在构建时自动完成,正如 spring-init 项目所探索的那样。这使得我们可以在基础设施层面受益于函数式配置,同时在编程模型层面继续使用 @Configuration

因此,spring-graalvm-native 现在支持 functional 模式,以及 agentfeature 模式。您可以查看 jafujafu-webmvc 示例,以了解在没有 GraalVM 原生镜像反射配置的情况下运行的 Spring 应用的实际例子。

Maven 测试驱动的配置生成

我们的另一项工作是与 GraalVM 原生镜像团队合作,使其更容易利用跟踪代理。跟踪代理可以在 JVM 上跟踪资源、代理和反射的使用情况,从而生成相关的 GraalVM 原生镜像配置。从 GraalVM 20.1.0 开始,可以使用 访问过滤器 (access filters) 来排除生成的配置中的包和类。

我们在 commandlinerunner-agent 示例中利用了这一新功能,其中负责运行测试的 Maven Surefire 插件配置如下:

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<argLine>-agentlib:native-image-agent=access-filter-file=access-filter.json,config-output-dir=target/classes/META-INF/native-image</argLine>
	</configuration>
</plugin>

使用 access-filter.json 排除与测试相关的包。

{ "rules": [
  {"excludeClasses": "org.apache.maven.surefire.**"},
  {"excludeClasses": "net.bytebuddy.**"},
  {"excludeClasses": "org.apiguardian.**"},
  {"excludeClasses": "org.junit.**"},
  {"excludeClasses": "org.mockito.**"},
  {"excludeClasses": "org.springframework.test.**"},
  {"excludeClasses": "org.springframework.boot.test.**"},
  {"excludeClasses": "com.example.commandlinerunner.test.**"}
  ]
}

这使得该配置可以比较好地集成到 Maven 生命周期中(这方面还有更多工作要做)。请注意,由于 oracle/graal#2490 这个 bug,代理配置的排除功能尚不可用,但这应该会在即将发布的 GraalVM 20.1.1 版本中得到修复。

内存占用空间减小

我们才刚刚开始着手 优化 Spring Boot 原生镜像的内存占用,但初步结果已经很有希望。在此 0.7.0 版本中,我们专注于优化函数式 Spring Boot 应用(jafujafu-webmvc 示例),以便更好地了解在 native-image 编译器能够更积极地移除未使用的代码路径时,Spring 基础设施所能达到的内存占用水平。

我们还确保了 Spring 基础设施中与原生镜像不兼容的部分(例如 CGLIB 代理支持)在构建时被移除,并引入了一些标志来在构建时移除基础设施的某些部分。

  • -Dspring.native.remove-yaml-support=true 移除 Yaml 支持。

  • -Dspring.native.remove-xml-support=true 移除 XML 支持。

  • -Dspring.native.remove-spel-support=true 移除 SpEL 支持。

  • -Dspring.native.remove-jmx-support=true 移除 JMX 支持。

这些数字在未来几个月内将发生显著变化,特别是随着我们下一个 0.8.0 版本计划的更多优化,但我们已经可以看到原生镜像可以实现相当高效的内存占用。

  • 一个包含 Spring MVC REST 端点的 Spring Boot 原生镜像消耗 78MB 的 RSS 内存。

  • 一个以函数式方式配置的包含 Spring MVC REST 端点的 Spring Boot 原生镜像消耗 39MB 的 RSS 内存。

我们还在为原生镜像进行 Tomcat 优化,这将进一步减小内存占用。

下一步

spring-graalvm-native 工作的同时,我们还在 Spring Framework 5.3 及其相关的 Spring Data 和 Spring Boot 版本中改进自适应代码路径,以提高构建时移除的基础设施级别。我们还将添加标志来在构建时移除 XMLSpEL 支持。这些标志也可以在 JVM 上使用。

我们下一个 0.8.0 里程碑版本 预计将是一个重要的版本,会引入一些新功能:

  • 一个 专门的混合模式,简而言之,它将为 Spring 基础设施使用 GraalVM 跟踪代理,并为用户类(如带有 @Controller 注解的类)动态计算一个轻量级的附加配置,以避免需要遍历所有代码路径。

  • 利用 Spring Framework 5.3 的里程碑版本,在 spring-graalvm-native 端移除大部分由于其本质上难以维护的替换配置。

  • agenthybrid 模式引入 类路径缩减机制,以实现更接近 JVM 上行为的原生镜像上的延迟类加载。

  • 通过函数式 Bean 注册,尝试移除对反射配置的需求

  • 改进 Spring Data 支持。

  • 文档化 Visual Studio Code 远程容器开发体验。.

这个路线图可能会有所调整,但希望能让您了解我们将要遵循的方向。

结论

本次 0.7.0 版本之所以能够发布,离不开 Andy Clement 的辛勤工作,向他以及其他贡献者致以崇高的敬意:Filip Hannik 在 Tomcat 支持方面的贡献,Christoph Strobl 在 Spring Data 支持方面的贡献,以及 Dave Syer 在使常规 Petclinic 示例正常工作以及 Spring Cloud Function 支持 方面的不懈努力。

Spring Boot 应用的这项孵化原生镜像支持之所以独特,是因为我们不仅面向新的 Spring Boot 应用,还面向数百万个现有应用,从而根据您的需求提供更多的部署选项。这是一个巨大的挑战,我们尚未达到对每个 Spring Boot 应用都能“开箱即用”的阶段,但我们正朝着正确的方向前进。

最后,值得一提的是,虽然许多增强功能是出于优化原生镜像的目的而驱动的,但这些更改不仅将改善原生镜像的体验,还将改进 Spring Boot 应用在常规 JVM 上的运行方式。

如果您想了解更多信息,可以阅读 文档 来尝试在一个简单的项目中运行,或者 玩转示例。我们期待您的反馈。

获取 Spring 新闻通讯

通过 Spring 新闻通讯保持联系

订阅

领先一步

VMware 提供培训和认证,助您加速进步。

了解更多

获得支持

Tanzu Spring 提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件,只需一份简单的订阅。

了解更多

即将举行的活动

查看 Spring 社区所有即将举行的活动。

查看所有