SpringBoot项目GraalVM迁移

一些背景

一直想把项目迁移到使用GraalVM构建出的原生应用上,但是在前段时间的一次尝试后,发现很难做到,其中一个最主要原因就在于我目前手头上没有X86架构的电脑。平时我使用的是一个M1处理器的MacBook,编译出的Docker镜像架构指令集也是Arm64的,无法在我的X86服务器启动。原本想着就这样算了,但是实际上我最近在和一个朋友做一个node的项目,这个项目上朋友计划配合GitHub Action进行发布流水线的操作,我就去查了一个,没想到还真查到Graal官方推出的一个Github Action插件,基于这里,我就去尝试探索了下,发现还真可以编译成功,最终也把项目在生产环境启动起来了。但是在这个过程中踩了不少坑。

代码变更

这里代码变更主要分为3类,分别是Gradle配置,GitHub Action配置文件和手动补充一些需要被AOT进去的配置。值得注意的是,去年开始Oracle推出的Oracle GraalVM已经支持了G1垃圾收集器,不再是企业版独享的功能,可以直接在编译配置中指定,前提是使用了Oracle GraalVM,这个可以通过下面buildpacks进行配置,如果不配置可能使用的是其他JDK,基于版权原因它一定没有G1回收器:

tasks.named<BootBuildImage>("bootBuildImage") {
	buildpacks = listOf("docker.io/paketobuildpacks/oracle", "urn:cnb:builder:paketo-buildpacks/java-native-image")
	environment = mapOf("BP_NATIVE_IMAGE_BUILD_ARGUMENTS" to
			"""
				-march=compatibility
				--gc=G1
				-R:MaxHeapSize=128m
			""")
	docker {
		publish.set(true)
		publishRegistry {
			imageName.set("docker.io/mingchiuli/${rootProject.name}:${version}")
			url.set("https://docker.io")
			val un = System.getenv("DOCKER_USERNAME")
			val pwd = System.getenv("DOCKER_PWD")
			username.set(un)
			password.set(pwd)
		}
	}
}

基于Spring boot的初始化配置,还需要对BootBuildImage这个任务进行一定配置,因为需要使用Spring官方提供的这个打包任务进行镜像构建,故配置DockerHub的用户名和密码用来推送镜像。在这里用户名和密码是通过环境变量注入的,需要和GitHub Action配置文件搭配。配置里的-march=compatibility主要作用是禁止CPU指令优化,不加的话,由于编译机器的CPU和启动机器CPU的差异,有可能启动镜像报错CPU指令集不支持的问题。

name: GraalVM build
on: [push, pull_request]
jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: graalvm/setup-graalvm@v1
        with:
          java-version: '21'      # See 'Options' section below for all supported versions
          distribution: 'graalvm' # See 'Options' section below for all available distributions
          github-token: ${{ secrets.GITHUB_TOKEN }}
      - name: Example step
        run: |
          echo "GRAALVM_HOME: $GRAALVM_HOME"
          echo "JAVA_HOME: $JAVA_HOME"
          java --version
          native-image --version
      - name: Example step using Gradle plugin
        run: ./gradlew bootBuildImage
        env:
          DOCKER_USERNAME: ${{ secrets.DOCKERHUB_USERNAME }}
          DOCKER_PWD: ${{ secrets.DOCKERHUB_PWD }}

GitHub Action配置文件里对环境变量进行定义,就是项目里Gradle配置的DOCKER_USERNAME和DOCKER_PWD。这里的关键在于需要把gradle-wrapper.jar和gradle-wrapper.properties提交到GitHub仓库里,否则会报错找不到gradlew这个命令。

另外,像caffeine这样的库,可能它在程序运行的时候根据开发者缓存的配置动态加载了一些类进来,所以需要增加一些配置,如:

        hints.reflection().registerType(
                TypeReference.of("com.github.benmanes.caffeine.cache.SSMSA"),
                MemberCategory.PUBLIC_FIELDS, MemberCategory.INVOKE_DECLARED_CONSTRUCTORS, MemberCategory.INVOKE_PUBLIC_METHODS);

上线

上线我使用的是Docker Compose,关于生产环境的服务配置,只需要通过Docker容器启动的时候环境变量注入就可以了。在Docker Compose文件的environment这个下面写就OK。

之后的计划

这次成功改造为native化可以说久旱逢甘霖,由于native化以后占用内存大大降低,使在有限的经济条件下项目有了微服务化的可能,以前后端应用需要600M的内存占用,现在优化到200M上下:

启动时间也有了大提升,之前启动需要25秒左右,现在只需要3秒上下:

目前这个服务器内存节省出来很多内存,可以把它拆分出微服务了。之后这个项目的改造方向是应该是这个。

微服务化相关架构图

由于目前并没有太多空闲时间,所以我暂时应该不会开启这个微服务化进程,但是流程图可以先开始画一下:

flowchart TD 前端 --流量--> Nginx外网网关 Nginx外网网关 --未登录流量--> 展示服务-本地缓存 Nginx外网网关 --已登录流量--> 内网鉴权网关 消息队列 --投递消息--> 展示服务-本地缓存 消息队列 --投递消息--> 权限中心-本地缓存 消息队列 --投递消息--> 搜索中心 内容中心 --缓存变更消息--> 消息队列 权限中心-本地缓存 --缓存变更消息--> 消息队列 内网鉴权网关 --获取权限--> 权限中心-本地缓存 内网鉴权网关 --分流--> 权限中心-本地缓存 内网鉴权网关 --分流--> 用户中心 内网鉴权网关 --分流--> 内容中心 内网鉴权网关 --分流--> 搜索中心 内网鉴权网关 --分流--> websocket服务 内容中心 --数据变更--> 消息队列 内容中心 --数据获取/变更--> MariaDB 权限中心-本地缓存 --数据获取/变更--> MariaDB 用户中心 --数据获取/变更--> MariaDB 搜索中心 --数据获取/变更--> ES 权限中心-本地缓存 --数据获取/变更--> 中心化缓存Redis 展示服务-本地缓存 --数据获取/变更--> 中心化缓存Redis 展示服务-本地缓存 --数据获取--> 内容中心
posted @ 2024-05-09 14:18  imissinstagram  Views(247)  Comments(0Edit  收藏  举报