Administrator
发布于 2023-05-11 / 11001 阅读
184

Spring Boot 原生镜像实战:构建 50MB 的可执行文件

背景:一次冷启动慢到被运维吐槽

五月初我把一个内部配置中心客户端容器化,K8s 里副本从 0 扩容到 10 时,启动要 8 秒多,滚动发布期间总有几秒请求打不到。运维说你这 Java 应用太重了。我琢磨着试一把 Spring Boot 3 的原生镜像——GraalVM 把应用直接编译成机器码,启动该是毫秒级。

AOT 处理:编译期和运行期的分工

原生镜像的核心是把 Spring 的「运行时反射、动态代理、资源加载」提前到构建期通过 AOT(Ahead-Of-Time)处理掉。Spring Boot 3 的 spring-boot-starter-aot 会在构建时生成运行时提示文件reflect-config.jsonresource-config.json 等),告诉 GraalVM 哪些类需要保留反射信息。

踩坑:反射与资源配置

第一版编译直接失败,报错 ClassNotFoundExceptionReflectiveAccessException。原因是我用了 FastJSON 做动态字段解析,GraalVM 静态分析抓不到那些运行时才出现的类。两处要补:

  • 反射:用 @RegisterReflectionForBindingRuntimeHintsRegistrar 显式注册。
  • 资源application.yml 之外,模板文件得在 @ImportRuntimeHints 里登记,否则打包后读不到。
@Configuration
@ImportRuntimeHints(MyHints::register)
class MyHints {
    static void register(HintsRegistrar r) {
        r.resources().registerPattern("templates/*.ftl");
    }
}

构建命令与耗时

用 Maven 插件(需 GraalVM 22.3+,JDK 17 基础):

mvn -Pnative native:build
# 或 Spring Boot 插件
mvn clean package -Pnative

构建本身很慢,我本机 16C 跑了 4 分 12 秒(JVM 包只要 22 秒)。但产出是一个 53MB 的可执行文件,不依赖 JRE。

启动与内存实测

指标JVM(JDK 17)原生镜像
启动时间8.3s0.18s
RSS 常驻内存480MB86MB
首次请求就绪9.1s0.4s

扩容从 10 副本 0→10,整体就绪时间从 80 秒降到不到 4 秒,滚动发布零抖动。

先到这

《Spring Boot 原生镜像实战:构建 50MB 的可执行文件》这块我前前后后踩了不止一次。今天先写这些,后面想到新的再补。

参考