我是靠谱客的博主 耍酷羽毛,最近开发中收集的这篇文章主要介绍Spring Native初探,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

c4b3e0df50018c5fbfd1bc303f5ed3b4.png

近几年“原生”一词一直泛滥在云计算、边缘计算等领域中,而原生宠幸的语言也一直都是Golang,Rust等脱离Sandbox运行的开发语言。Java得益于上世纪流行的一次编译,到处执行的理念,流行至今,但也因为这个原因,导致Java程序脱离不了JVM运行环境,使得不那么受原生程序的青睐。在云原生泛滥的今天,臃肿的JVM使Java应用程序对比其他语言显得无比的庞大,各路大神也想了很多方式让Java变的更“原生”。最近Spring推出了Spring Native概念,并参考了其他大牛的文章后,今天我们就一探如何让用Spring Boot编写原生应用。

Spring Native借助GraalVM native-image编译器来编译Spring应用,所以我们需要先来了解一下GraalVM。大部分脚本语言或者有动态特效的语言都需要一个语言虚拟机运行,比如CPython,Lua,Erlang,Java,Ruby,R,JS,PHP,Perl,APL等等,但是这些语言的虚拟机水平参差不齐,例如JVM的HotSpotVM、JS的V8都是“艺术”级别的,但CPython的VM就不忍直视。那能不能用一个“艺术”级别的虚拟机跑所有的语言呢?GraalVM就是这么一个高性能的救世主,它使用运行在JVM上的Truffle语言框架,将AST节点编译为机器代码,使用户只需要实现具体语言AST解释器,就能实现性能足够好的虚拟机,而实现这个编译器也是一个Java写的即时编译器Graal,GraalVM也因此得名。

也许有同学会问了怎么用Java语言编译Java代码呢,而且还是这么高性能?这我们就要说说JEP 243的JVMCI。众所周知,HotSpot JVM内置了两个C++写的即时编译器(JIT)C1和C2,一般频繁的代码先用C1编译,如果热点继续,那么会使用C2编译。JVMCI相当于把本该交给C2编译的代码交给高级JIT:Graal编译,说到底就是将一段byte[]在运行时换成另一段byte[]。

那像Go和C/C++这类语言是否也能运行在JVM上呢?答案是肯定的。解决方案是将C/C++这些语言用一些工具(如Clang)转换为LLVM IR,然后使用基于Truffle的AST解释LLVM IR即可。(但,我们为啥要这么做??)

480e2b8461d133adc80bd7eb8316f709.png

到目前为止,几乎所有的语言都能在以JVM为基础,以Graal即时编译器为核心的虚拟机上运行起来了,但大家已经一定疑惑了,程序运行需要依赖JVM,而JVM必须提前安装JDK环境,而且自身启动慢,内存负载高,就不能把程序直接打包成平台相关可执行文件吗?答案是SubstrateVM,它借助Graal编译器,可以将Java程序AOT编译为可执行程序。所以万能的Graal编译器不仅能JIT,还能AOT。

b0bd37bc1706d85e1fa29bc4b6946d12.png

好了,我们这些“CRUD仔”们了解这些基础魔法就足够了,至于SVM如何解决反射、GC等问题的高级魔法还是交给大牛们吧。现在进入我们的正题:用Spring Boot来编写一个原生应用。

制作过程

1517896e7b6c4128876bf2f76ef397ef.png

Step 1:安装GraalVM和依赖工具

因为大家都比较熟悉JDK安装过程,所以本过程带过了一些细节,不做重点讲解。首先我们需要安装GraalVM,笔者以自己的macOS系统为例,其他系统请参考官方安装文档。比较遗憾的是,GraalVM并没有提供针对M1优化的AArch64平台的包,我们只能使用AMD64平台,下载地址点击这里[1],我们使用Java 17版本的darwin压缩包,解压至:

/Library/Java/JavaVirtualMachines/

并且设置JAVA_HOME:

export GRAALVM17_HOME=$(/usr/libexec/java_home -v 17)
export JAVA_HOME=$GRAALVM17_HOME

为了使用方便也可以设置Alias:

alias java17g='export JAVA_HOME=$GRAALVM17_HOME;java -version'

由于macOS的安全限制,需要删除quarantine:

$ sudo xattr -r -d com.apple.quarantine $GRAALVM17_HOME

我们依然需要Maven作为本次探索的打包工具,请大家自行安装Maven,这里不再赘述。一切安装完成,我们可以运行java -version和mvn -v来验证一下安装是否成功。

$ java -version
openjdk version "17.0.1" 2021-10-19
OpenJDK Runtime Environment GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05)
OpenJDK 64-Bit Server VM GraalVM CE 21.3.0 (build 17.0.1+12-jvmci-21.3-b05, mixed mode, sharing)

最后,我们需要安装native-image作为原生代码编译工具:

$ cd $GRAALVM_HOME/bin
$ ./gu install native-image

当然,Xcode工具包因为包含GCC等工具,也必须安装,如已经安装可跳过。

$ sudo xcode-select --install

Step 2:建立Spring Boot应用

按着官方的向导建立一个基于Spring Boot 2.6.2版本,Java版本使用1.8的Web应用。注意一定要使用最新的2.6.2+版本,否则不支持AOT功能。并且,Java版本也只支持1.8。目录如下:

.
├── HELP.md
├── pom.xml
├── src
│   ├── main
│   │   ├── java
│   │   │   └── com
│   │   │       └── ajk
│   │   │           └── testspringnative
│   │   │               └── TestSpringNativeApplication.java
│   │   └── resources
│   │       ├── application.yml
│   │       ├── static
│   │       └── templates
│   └── test
│       └── java
│           └── com
│               └── ajk
│                   └── testspringnative
│                       └── TestSpringNativeApplicationTests.java

其中TestSpringNativeApplication代码如下:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@SpringBootApplication
@RestController
public class TestSpringNativeApplication {

    public static void main(String[] args) {
        SpringApplication.run(TestSpringNativeApplication.class, args);
    }

    @GetMapping("/hello")
    public String hello(@RequestParam(value = "name", defaultValue = "World") String name) {
        return String.format("Hello %s!", name);
    }

}

配置文件application.yml代码如下:

server:
  port: 9000
  shutdown: graceful

spring:
  profiles:
    active: default

logging:
  level:
    root: info

Step 3:配置Maven

为了方便演示,我们使用了最简单的代码和配置,重点是Maven的配置,以至于我需要用整个Step来说明。

由于使用了官方向导生成的项目,所以基础pom.xml文件如下:

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.6.2</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.ajk</groupId>
    <artifactId>test-spring-native</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>test-spring-native</name>
    <description>test-spring-native</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    <dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

接下来我们开始配置Spring Boot Native,官方有两种方式实现编译原生应用:

  • 用Spring Boot Buildpacks生成包含原生应用的OCI容器。

  • 用GraalVM native image Maven plugin生成原生应用。

由于篇幅关系,这里只介绍第二种方式,即编译为原生应用。

首先增加包和插件依赖库:

<repositories>
    <!-- ... -->
    <repository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </repository>
</repositories>
<pluginRepositories>
    <!-- ... -->
    <pluginRepository>
        <id>spring-release</id>
        <name>Spring release</name>
        <url>https://repo.spring.io/release</url>
    </pluginRepository>
</pluginRepositories>

再次确认我们的Spring Boot版本为2.6.2(因为Spring Native 0.11.1版本支持此版本),并添加如下依赖:

<dependencies>
    <!-- ... -->
    <dependency>
        <groupId>org.springframework.experimental</groupId>
        <artifactId>spring-native</artifactId>
        <version>0.11.1</version>
    </dependency>
</dependencies>

添加Spring AOT部署插件:

<build>
    <plugins>
        <!-- ... -->
        <plugin>
            <groupId>org.springframework.experimental</groupId>
            <artifactId>spring-aot-maven-plugin</artifactId>
            <version>0.11.1</version>
            <executions>
                <execution>
                    <id>generate</id>
                    <goals>
                        <goal>generate</goal>
                    </goals>
                </execution>
                <execution>
                    <id>test-generate</id>
                    <goals>
                        <goal>test-generate</goal>
                    </goals>
                </execution>
            </executions>
        </plugin>
    </plugins>
</build>

再添加原生编译插件,这里使用一个profile来更好的管理:

<profiles>
    <profile>
        <id>native</id>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.graalvm.buildtools</groupId>
                    <artifactId>native-maven-plugin</artifactId>
                    <version>0.9.9</version>
                    <extensions>true</extensions>
                    <executions>
                        <execution>
                            <id>build-native</id>
                            <goals>
                                <goal>build</goal>
                            </goals>
                            <phase>package</phase>
                        </execution>
                    </executions>
                    <configuration>
                        <!-- ... -->
                    </configuration>
                </plugin>
                <!-- Avoid a clash between Spring Boot repackaging and native-maven-plugin -->
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                    <configuration>
                        <classifier>exec</classifier>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </profile>
</profiles>

一切妥当!开始编译吧!

$ mvn clean -Pnative -DskipTests package

官方推荐编译的机器不能少于8核8G内存,否则编译工具会报错。在我的M1的机器上,编译大概需要10分钟左右,编译时CPU峰值使用率大概在50%,内存占用6.9GB。

简单评测

2b022febf5c82b6f81473ff92acb9654.png

首先看一下编译文件大小对比:

  • fatjar包文件为17.8M(不包含JRE),原生可执行文件为68.2M。

  • 使用spring-boot-maven-plugin生成包含JRE运行环境的容器镜像大小为270M,而使用Tiny Core Linux+原生应用的形式,镜像大小可以控制在100M以内,为96M。压缩比达到35%之多。

再来看看启动速度对比:

  • fatjar启动时间为8.2s

  • 原生文件启动时间为5.6s

程序使用CPU和内存对比:

  • fatjar空载CPU 0.5%,内存使用528M

  • 原生应用空载CPU 0.3%,内存使用85M

如下表格:


FatJar包Native包
应用大小17.8M68.2M
容器镜像大小270M96M
启动速度8.2s5.6s
空载CPU0.5%0.3%
空载内存528M85M

总体来讲,原生应用从产物大小,启动速度,运行负载来讲都优与Jar包应用,这还是在没有针对arm的指令集做优化的基础上的,但对比官方宣传的内存使用20M内存占用还有一定差距。

总结

606700112818ed550529aa0b7d2d2157.png

经过几天折腾,GraalVM的性能即使不编译为原生应用也优于HotSpot VM,在编译为原生应用后,性能也有一定的提升。但目前Spring Native还不够成熟,笔者想用undertow代替Tomcat Web容器而编译后的原生应用,始终无法运行。相信后面版本应该会修复一些问题。

本文总结了一种编译原生的方式,另一种生成原生镜像的方式大家可以自行研究(注意,编译成原生镜像需要阅读大量文章)。另外,由于时间有限,在两者的压测过程中,原生应用GC回收内存速度快于jar包应用,大家也可以深入研究原生内存回收方式。

所有代码可在GitHub[2]上参考。

相关链接:

  1. https://github.com/graalvm/graalvm-ce-builds/releases

  2. https://github.com/huang-kai/test-spring-native

参考资料:

  1. https://www.graalvm.org/docs/introduction/

  2. https://docs.spring.io/spring-native/docs/current/reference/htmlsingle/

  3. https://openjdk.java.net/jeps/243

  4. http://trufflesuite.com/truffle/

  5. https://github.com/graalvm/labs-openjdk-17

作者:黄凯,58安居客新房技术部负责人。

Kubernetes线下实战与CKA培训

8e299c9c131d25a8771deee3087f289e.png

本次培训在深圳开班,基于最新考纲,理论结合实战,通过线下授课、考题解读、模拟演练等方式,帮助学员快速掌握Kubernetes的理论知识和专业技能,并针对考试做特别强化训练,让学员能从容面对CKA认证考试,使学员既能掌握Kubernetes相关知识,又能通过CKA认证考试,理论、实践、考证一网打尽,学员可多次参加培训,直到通过认证。点击下方图片或者阅读原文链接查看详情。

89452e55eb5707d47d58056152ecfd40.png

最后

以上就是耍酷羽毛为你收集整理的Spring Native初探的全部内容,希望文章能够帮你解决Spring Native初探所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(43)

评论列表共有 0 条评论

立即
投稿
返回
顶部