通往 Spring Boot 原生应用的道路

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

我想借此机会,在我们发布 Spring GraalVM Native 0.7.0 的同时,向大家通报一下我们关于 Spring Boot 原生镜像的工作进展。

为什么?

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

  • 输出的是一个原生可执行文件,其中包含您的应用程序、JDK 的一个子集以及运行它所需的依赖项。

  • 实际上,可执行文件可能会被打包在一个高度优化的容器镜像中(支持 FROM scratch Docker 镜像),并减少攻击面,非常适合 Kubernetes。

  • 启动时间几乎是即时的,并且可以立即获得峰值性能,从而支持缩容至零(无服务器)应用程序,包括常规的 Spring Boot Web 应用程序。

  • 内存消耗减少,这非常适合拆分为多个微服务的系统。

正如您所料,原生镜像并非免费的午餐,这些有趣的特性也伴随着一些缺点。

  • GraalVM 原生平台还比较年轻,远不如 JVM 成熟。

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

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

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

  • 吞吐量降低,延迟增加(更多细节)。

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

值得注意的是,原生镜像的范围现在已超出 GraalVM,因为 Mark Reinhold 最近宣布了 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 现在除了 agentfeature 模式之外,还支持 functional 模式。您可以查看 jafujafu-webmvc 示例,以了解在没有 GraalVM 原生反射配置的情况下运行的 Spring 应用程序的具体示例。

Maven 测试驱动配置生成

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

我们在 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 问题,代理配置的排除尚无法使用,但应该在即将发布的 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 原生镜像消耗 78M 的 RSS 内存。

  • 以功能方式配置带有 Spring MVC REST 端点的 Spring Boot 原生镜像消耗 39M 的 RSS 内存。

我们还在针对原生镜像进行 Tomcat 优化,这将进一步减少占用空间。

下一步

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

我们的下一个0.8.0 里程碑预计将是一个重要的里程碑,其中将引入一些新功能。

  • 一个专用的混合模式,简而言之,它将使用 GraalVM 跟踪代理用于 Spring 基础设施,以及在构建时动态计算的轻量级额外配置用于用户类(例如@Controller注释的类),以避免必须执行所有代码路径。

  • 利用 Spring Framework 5.3 里程碑,以便能够移除spring-graalvm-native端的大部分替换,因为它们本质上是无法维护的。

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

  • 尝试围绕移除对反射配置的需求,通过函数式 Bean 注册来实现。

  • 更好的 Spring Data 支持。

  • 记录 Visual Studio Code 远程容器开发人员体验。.

这份路线图可能会发生变化,但希望它能让你了解我们将遵循的方向。

结论

如果没有Andy Clement的大量工作,这个 0.7.0 版本是不可能发布的,所以要向他以及其他贡献者表示感谢:Filip Hannik 在 Tomcat 支持方面,Christoph Strobl 在 Spring Data 支持方面,以及Dave Syer 为使常规 Petclinic 示例正常工作以及Spring Cloud Function 支持做出的不懈努力。

Spring Boot 应用程序的这个孵化中的原生镜像支持的独特之处在于,我们不仅针对新的 Spring Boot 应用程序,还针对数百万个现有应用程序,以便根据您的需求提供更多部署选项。这是一个巨大的挑战,我们还没有达到每个 Spring Boot 应用程序都能“正常工作”的阶段,但我们正在朝着正确的方向前进。

最后,值得一提的是,尽管许多增强功能都是出于创建最佳原生镜像的意图而驱动的,但这些更改不仅会改善原生镜像的故事,还会改善 Spring Boot 应用程序在普通 JVM 上的运行方式。

如果您想了解更多信息,可以阅读文档,以便在一个简单的项目上尝试它或使用示例进行尝试。期待您的反馈。

获取 Spring Newsletter

与 Spring Newsletter 保持联系

订阅

领先一步

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

了解更多

获取支持

Tanzu Spring 在一个简单的订阅中提供 OpenJDK™、Spring 和 Apache Tomcat® 的支持和二进制文件。

了解更多

即将举行的活动

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

查看全部