Skip to content

使用 GraalVM 构建 tio-boot Native 程序 ​

[toc]

本文将介绍如何使用 GraalVM 构建基于 tio-boot 的原生程序。这对于希望构建快速高效的 Java 服务器应用程序的开发人员尤其有用。我们将从安装必要的依赖开始,逐步构建一个可以独立于 JVM 运行的本地二进制镜像。

代码已提交至 GitHub 仓库。

编写代码 ​

pom.xml 配置 ​

首先,配置 Maven 项目的 pom.xml 文件,指定项目的依赖和构建配置。

xml
  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <java.version>21</java.version>
    <maven.compiler.source>${java.version}</maven.compiler.source>
    <maven.compiler.target>${java.version}</maven.compiler.target>
    <graalvm.version>23.1.1</graalvm.version>
    <tio-boot.version>2.1.8</tio-boot.version>
    <final.name>web-hello</final.name>
    <main.class>nexus.io.tio.web.hello.HelloApp</main.class>
  </properties>
  <dependencies>
    <dependency>
      <groupId>nexus.io</groupId>
      <artifactId>tio-boot</artifactId>
      <version>${tio-boot.version}</version>
    </dependency>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.12</version>
      <scope>test</scope>
    </dependency>
  </dependencies>
  <profiles>
    <!-- 开发环境 -->
    <profile>
      <id>development</id>
      <activation>
        <activeByDefault>true</activeByDefault>
      </activation>
      <dependencies>
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.3.3</version>
        </dependency>
      </dependencies>
    </profile>

    <!-- 生产环境 -->
    <profile>
      <id>production</id>
      <dependencies>
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.3.3</version>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-maven-plugin</artifactId>
            <version>2.7.18</version>
            <configuration>
              <mainClass>${main.class}</mainClass>
              <excludeGroupIds>org.projectlombok</excludeGroupIds>
            </configuration>
            <!-- 设置执行目标 -->
            <executions>
              <execution>
                <goals>
                  <goal>repackage</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    <!-- assembly -->
    <profile>
      <id>assembly</id>
      <dependencies>
        <dependency>
          <groupId>ch.qos.logback</groupId>
          <artifactId>logback-classic</artifactId>
          <version>1.3.3</version>
        </dependency>
      </dependencies>
      <build>
        <plugins>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-jar-plugin</artifactId>
            <version>3.2.0</version>
          </plugin>
          <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-assembly-plugin</artifactId>
            <version>3.1.1</version>
            <configuration>
              <archive>
                <manifest>
                  <mainClass>${main.class}</mainClass>
                </manifest>
              </archive>
              <descriptorRefs>
                <descriptorRef>jar-with-dependencies</descriptorRef>
              </descriptorRefs>
              <appendAssemblyId>false</appendAssemblyId>
            </configuration>
            <executions>
              <execution>
                <id>make-assembly</id>
                <phase>package</phase>
                <goals>
                  <goal>single</goal>
                </goals>
              </execution>
            </executions>
          </plugin>
        </plugins>
      </build>
    </profile>
    <profile>
      <id>native</id>
      <dependencies>
        <!-- GraalVM 环境使用 jdk log -->
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-jdk14</artifactId>
          <version>1.7.31</version>
        </dependency>
        <!-- GraalVM -->
        <dependency>
          <groupId>org.graalvm.sdk</groupId>
          <artifactId>graal-sdk</artifactId>
          <version>${graalvm.version}</version>
          <scope>provided</scope>
        </dependency>
      </dependencies>
      <build>
        <finalName>${final.name}</finalName>
        <plugins>
          <plugin>
            <groupId>org.graalvm.nativeimage</groupId>
            <artifactId>native-image-maven-plugin</artifactId>
            <version>21.2.0</version>
            <executions>
              <execution>
                <goals>
                  <goal>native-image</goal>
                </goals>
                <phase>package</phase>
              </execution>
            </executions>
            <configuration>
              <skip>false</skip>
              <imageName>${final.name}</imageName>
              <mainClass>${main.class}</mainClass>
              <buildArgs>
              -H:+RemoveSaturatedTypeFlows
              --allow-incomplete-classpath
              --no-fallback
            </buildArgs>
            </configuration>
          </plugin>
        </plugins>
      </build>
    </profile>
  </profiles>

处理器代码 ​

创建一个请求处理器,用于处理 /hello 和 /hi 两个接口。

java
package nexus.io.tio.web.hello.handler;

import nexus.io.tio.http.common.HttpRequest;
import nexus.io.tio.http.common.HttpResponse;
import nexus.io.tio.http.server.util.Resps;

public class HelloRequestHandler {

  public HttpResponse hello(HttpRequest httpRequest) {
    return Resps.txt(httpRequest, "hello");
  }

  public HttpResponse hi(HttpRequest httpRequest) {
    return Resps.txt(httpRequest, "hi");
  }

}

配置类 ​

配置 HTTP 请求路由,将特定路径映射到对应的处理方法。

java
package nexus.io.tio.web.hello.config;

import nexus.io.tio.boot.server.TioBootServer;
import nexus.io.tio.http.server.router.HttpRequestRouter;
import nexus.io.tio.web.hello.handler.HelloRequestHandler;

public class HttpRequestHandlerConfig {

  public void config() {
    HttpRequestRouter router = TioBootServer.me().getRequestRouter();

    // 实例化处理器
    HelloRequestHandler handler = new HelloRequestHandler();

    // 添加路由
    router.add("/hi", handler::hi);
    router.add("/hello", handler::hello);
  }
}
java
package nexus.io.tio.web.hello.config;

import nexus.io.context.BootConfiguration;

public class AppConfig implements BootConfiguration {

  @Override
  public void config() {
    new HttpRequestHandlerConfig().config();
  }

}

启动类 ​

定义应用程序的入口点,启动 tio-boot 服务并记录启动时间。

java
package nexus.io.tio.web.hello;

import nexus.io.tio.boot.TioApplication;
import nexus.io.tio.web.hello.config.AppConfig;

public class HelloApp {
  public static void main(String[] args) {
    long start = System.currentTimeMillis();
    TioApplication.run(HelloApp.class, new AppConfig(), args);
    long end = System.currentTimeMillis();
    System.out.println((end - start) + "ms");
  }
}

上述代码已提交至 GitHub 仓库。

安装 GraalVM ​

GraalVM 通过将代码提前编译成本地可执行文件来提升 Java 应用的性能。

1. 下载并解压 GraalVM ​

首先,下载最新版本的 GraalVM 并将其解压到指定目录。

shell
wget https://download.oracle.com/graalvm/21/latest/graalvm-jdk-21_linux-x64_bin.tar.gz
mkdir -p ~/program/
tar -xf graalvm-jdk-21_linux-x64_bin.tar.gz -C ~/program/

2. 设置环境变量 ​

更新系统的环境变量以包含 GraalVM 的路径,使得可以在全局使用 GraalVM 的 java 及其他命令行工具。

shell
export JAVA_HOME=~/program/graalvm-jdk-21.0.10+8.1
export GRAALVM_HOME=~/program/graalvm-jdk-21.0.10+8.1
export PATH=$JAVA_HOME/bin:$PATH

安装 Maven ​

Apache Maven 是 Java 项目的主要构建自动化工具。

1. 下载并解压 Maven ​

下载并解压 Maven 至本地环境。

shell
wget https://dlcdn.apache.org/maven/maven-3/3.8.8/binaries/apache-maven-3.8.8-bin.zip
unzip apache-maven-3.8.8-bin.zip -d ~/program/

2. 设置环境变量 ​

确保 Maven 的 bin 目录在系统的 PATH 中,以便在任何位置使用 Maven 命令。

shell
export MVN_HOME=~/program/apache-maven-3.8.8/
export PATH=$MVN_HOME/bin:$PATH

构建应用程序 ​

所有工具和依赖配置完成后,接下来编译并运行示例应用程序。

1. 克隆示例应用程序 ​

克隆内置 HTTP 请求处理器的 TIO-Boot 示例应用程序。

shell
git clone https://github.com/litongjava/tio-boot-http-request-handler-demo.git

2. 构建 JAR 文件(可选) ​

将 Java 应用程序编译成 JAR 文件。如果您打算直接构建本地镜像,此步骤可选。

shell
cd tio-boot-http-request-handler-demo
mvn clean package -DskipTests -Dgpg.skip -Pproduction

3. 构建本地二进制镜像 ​

将应用程序编译成本地可执行文件,相较于在 JVM 上运行,可以减少启动时间和资源消耗。

shell
mvn clean package -DskipTests -Pnative

完整构建日志 ​

以下是构建过程的完整日志示例:

Welcome to Ubuntu 20.04.6 LTS (GNU/Linux 5.15.0-105-generic x86_64)

 * Documentation:  https://help.ubuntu.com
 * Management:     https://landscape.canonical.com
 * Support:        https://ubuntu.com/advantage

 * Introducing Expanded Security Maintenance for Applications.
   Receive updates to over 25,000 software packages with your
   Ubuntu Pro subscription. Free for personal use.

     https://ubuntu.com/pro

Expanded Security Maintenance for Applications is not enabled.

80 updates can be applied immediately.
To see these additional updates run: apt list --upgradable

29 additional security updates can be applied with ESM Apps.
Learn more about enabling ESM Apps service at https://ubuntu.com/esm

New release '22.04.3 LTS' available.
Run 'do-release-upgrade' to upgrade to it.

Your Hardware Enablement Stack (HWE) is supported until April 2025.
Last login: Mon May  6 05:50:37 2024 from 192.168.3.8
root@ping-Inspiron-3458:~# export JAVA_HOME=~/program/graalvm-jdk-21.0.10+8.1
root@ping-Inspiron-3458:~# export GRAALVM_HOME=~/program/graalvm-jdk-21.0.10+8.1
root@ping-Inspiron-3458:~# export PATH=$JAVA_HOME/bin:$PATH
root@ping-Inspiron-3458:~# export MVN_HOME=~/program/apache-maven-3.8.8/
root@ping-Inspiron-3458:~# export PATH=$MVN_HOME/bin:$PATH
root@ping-Inspiron-3458:~# cd ~/code/tio-boot-http-request-handler-demo/
root@ping-Inspiron-3458:~/code/tio-boot-http-request-handler-demo# mvn clean package -DskipTests -Pnative
[INFO] Scanning for projects...
[INFO]
[INFO] ---------< nexus.io:tio-boot-http-request-handler-demo >----------
[INFO] Building tio-boot-http-request-handler-demo 1.0.0
[INFO]   from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- maven-clean-plugin:2.5:clean (default-clean) @ tio-boot-http-request-handler-demo ---
[INFO] Deleting /root/code/tio-boot-http-request-handler-demo/target
[INFO]
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ tio-boot-http-request-handler-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /root/code/tio-boot-http-request-handler-demo/src/main/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ tio-boot-http-request-handler-demo ---
[INFO] Changes detected - recompiling the module!
[INFO] Compiling 4 source files to /root/code/tio-boot-http-request-handler-demo/target/classes
[INFO]
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ tio-boot-http-request-handler-demo ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory /root/code/tio-boot-http-request-handler-demo/src/test/resources
[INFO]
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ tio-boot-http-request-handler-demo ---
[INFO] No sources to compile
[INFO]
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ tio-boot-http-request-handler-demo ---
[INFO] Tests are skipped.
[INFO]
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ tio-boot-http-request-handler-demo ---
[INFO] Building jar: /root/code/tio-boot-http-request-handler-demo/target/web-hello.jar
[INFO]
[INFO] --- native-image-maven-plugin:21.2.0:native-image (default) @ tio-boot-http-request-handler-demo ---
[INFO] ImageClasspath Entry: nexus.io:tio-boot:jar:1.6.4:compile (file:///root/.m2/repository/com/litongjava/tio-boot/1.6.4/tio-boot-1.6.4.jar)
...
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time:  08:10 min
[INFO] Finished at: 2024-05-06T06:01:02+08:00
[INFO] ------------------------------------------------------------------------

注意:以上日志仅为示例,实际构建过程可能有所不同。

启动测试 ​

构建完成后,运行生成的本地可执行文件并测试其功能。

shell
(base) root@DL:/data/apps/tio-boot-http-request-handler-demo# ./target/web-hello --server.port=1024
Jan 06, 2025 4:42:52 PM nexus.io.tio.utils.environment.Prop <init>
INFO: file created successful:app.properties
Jan 06, 2025 4:42:52 PM nexus.io.tio.utils.environment.EnvUtils load
INFO: app.env:null
Jan 06, 2025 4:42:52 PM nexus.io.tio.utils.environment.EnvUtils load
INFO: app.name:null
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext run
INFO: AOP class not found: nexus.io.jfinal.aop.Aop
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext configureHttp
INFO: Server session enabled: false
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext run
INFO: Using cache: class nexus.io.tio.utils.cache.mapcache.ConcurrentMapCacheFactory
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext run
INFO: Server heartbeat timeout: 0
Jan 06, 2025 4:42:52 PM nexus.io.tio.utils.Threads getTioExecutor
INFO: new worker thead pool:nexus.io.tio.utils.thread.pool.SynThreadPoolExecutor@bca51ec[Running, pool size = 1, active threads = 1, queued tasks = 0, completed tasks = 0]
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext run
INFO: HTTP handler:
{
  "/hi": "nexus.io.tio.web.hello.config.HttpRequestHandlerConfig$$Lambda/0xd081350b15bca0456936253d349a9e11adb6c5190@4bb38d70",
  "/hello": "nexus.io.tio.web.hello.config.HttpRequestHandlerConfig$$Lambda/0xcb437d3e797185b36b558533d31a5d6958f792c10@299840ed"
}
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext run
INFO: Initialization times (ms): Total: 12, Scan Classes: 0, Init Server: 0, Config: 1, Server: 11, Route: 0
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext printUrl
INFO: Server port: 1024
Jan 06, 2025 4:42:52 PM nexus.io.tio.boot.context.TioApplicationContext printUrl
INFO: Access URL: http://localhost:1024
17ms

启动时间仅为 17 毫秒,极大提升了应用的响应速度。

通过 curl 命令进行测试:

shell
curl http://localhost:1024/hi
curl http://localhost:1024/hello

构建后的可执行文件大小约为 34MB。您可以通过以下链接下载构建后的文件:

下载地址

性能测试 ​

使用 ApacheBench (ab) 工具进行性能测试,模拟高并发请求。

shell
ab -c1000 -n10000000 http://localhost:1024/ok

此命令将以 1000 个并发连接,总共发送 1000 万个请求到 http://localhost/ok,用于评估服务器的处理能力和稳定性。

总结 ​

通过上述步骤,我们成功地使用 GraalVM 将基于 tio-boot 的 Java 应用程序编译为原生可执行文件。这不仅显著减少了启动时间,还优化了资源消耗,使应用在生产环境中表现更加高效。GraalVM 的原生镜像技术为 Java 开发者提供了强大的性能优化手段,是构建高性能服务器应用程序的理想选择。

参考资料 ​

MIT Licensed