领先一步
VMware 提供培训和认证,助您加速进步。
了解更多我想借我们 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 以及通过 Spring Boot 集成的更广泛的 JVM 生态系统在编译为原生镜像时能够良好运行。这项工作包括 GraalVM 原生中的修复和新特性、Spring 自身的更改,以及提高此 GraalVM 原生平台测试性和可维护性的额外工作。
同样值得注意的是,原生镜像的范围现在已经超出 GraalVM,因为 Mark Reinhold 最近宣布了 Project Leyden,这是一项在 Java 平台级别标准化原生镜像的工作。
很高兴宣布 Spring GraalVM Native 0.7.0 已可用。spring-graalvm-native
是我们目前正在孵化 Spring Boot 应用原生镜像支持的实验性项目,这个新的里程碑版本引入了以下改进:
带有 actuator、Spring Data JPA 仓库和缓存的 Petclinic JPA 标准示例。
Spring Data MongoDB 支持。
改进的 Kotlin 支持。
改进的日志支持。
Spring Cloud Function 支持。
如果您关注了我关于 Spring Fu 的工作,您可能知道,可以使用函数式 bean 注册而不是 @Configuration
来配置 Spring Boot 应用。
为了说明这个原理,以下是基于注解的配置
@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
现在除了支持 agent
和 feature
模式外,还支持 functional
模式。您可以查看 jafu
和 jafu-webmvc
示例,以查看无需 GraalVM 原生反射配置即可运行的 Spring 应用的具体示例。
我们的另一个工作领域是与 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 bug,代理配置的排除尚不起作用,但这应该在即将发布的 GraalVM 20.1.1 版本中修复。
我们才刚刚开始致力于优化 Spring Boot 原生镜像的内存占用,但初步结果已经很有希望。在这个 0.7.0 里程碑版本中,我们专注于优化函数式 Spring Boot 应用(jafu
和 jafu-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 版本,以便在构建时移除更多基础设施。我们还将添加标志,以便在构建时移除 XML 或 SpEL 支持。这些标志在 JVM 上也可用。
我们下一个0.8.0 里程碑版本预计是一个重要的版本,将引入一些新特性:
一个专用的混合模式,简而言之,将使用 GraalVM 追踪代理处理 Spring 基础设施,并为用户类(如带有 @Controller
注解的类)动态计算轻量级的额外配置,以避免需要执行所有代码路径。
利用 Spring Framework 5.3 里程碑版本,在 spring-graalvm-native
侧移除大部分替换,因为它们本质上是难以维护的。
为 agent
和 hybrid
模式引入类路径缩减机制,以便在原生镜像上实现更接近 JVM 的延迟类加载行为。
围绕移除反射配置的需求通过函数式 bean 注册进行实验。
更好的 Spring Data 支持。
这个路线图可能会有所变化,但希望它能让您了解我们将遵循的方向。
如果没有Andy Clement的巨大贡献,这个 0.7.0 版本是不可能实现的,所以向他以及其他贡献者致敬:在 Tomcat 支持方面的Filip Hannik,在 Spring Data 支持方面的Christoph Strobl,以及Dave Syer为使常规 Petclinic 示例正常工作以及在Spring Cloud Function 支持方面付出的不懈努力。
这项针对 Spring Boot 应用的孵化中的原生镜像支持的独特之处在于,我们不仅针对新的 Spring Boot 应用,还针对数百万现有应用,以便根据您的需求为您提供更多部署选项。这是一个巨大的挑战,我们尚未达到所有 Spring Boot 应用都能“开箱即用”的阶段,但我们正在朝着正确的方向前进。
最后值得一提的是,虽然许多增强功能的驱动因素是创建最佳原生镜像,但这些更改不仅会改进原生镜像的状况,还会改进 Spring Boot 应用在常规 JVM 上的运行方式。