使用 GraalVM 构建 tio-boot Native 程序
[toc]
本文将介绍如何使用 GraalVM 构建基于 tio-boot 的原生程序。这对于希望构建快速高效的 Java 服务器应用程序的开发人员尤其有用。我们将从安装必要的依赖开始,逐步构建一个可以独立于 JVM 运行的本地二进制镜像。
编写代码
pom.xml 配置
首先,配置 Maven 项目的 pom.xml 文件,指定项目的依赖和构建配置。
<properties>
  <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  <java.version>1.8</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.0.3</tio-boot.version>
  <final.name>web-hello</final.name>
  <main.class>com.litongjava.tio.web.hello.HelloApp</main.class>
</properties>
<dependencies>
  <dependency>
    <groupId>com.litongjava</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.2.3</version>
      </dependency>
    </dependencies>
  </profile>
  <!-- 生产环境 -->
  <profile>
    <id>production</id>
    <dependencies>
      <dependency>
        <groupId>ch.qos.logback</groupId>
        <artifactId>logback-classic</artifactId>
        <version>1.2.3</version>
      </dependency>
    </dependencies>
    <build>
      <plugins>
        <plugin>
          <groupId>org.springframework.boot</groupId>
          <artifactId>spring-boot-maven-plugin</artifactId>
          <version>2.7.4</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.2.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>
  <!-- Native Image 配置 -->
  <profile>
    <id>native</id>
    <dependencies>
      <!-- GraalVM 环境使用 JDK 日志 -->
      <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-jdk14</artifactId>
        <version>1.7.31</version>
      </dependency>
      <!-- GraalVM SDK -->
      <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 两个接口。
package com.litongjava.tio.web.hello.handler;
import com.litongjava.tio.http.common.HttpRequest;
import com.litongjava.tio.http.common.HttpResponse;
import com.litongjava.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 请求路由,将特定路径映射到对应的处理方法。
package com.litongjava.tio.web.hello.config;
import com.litongjava.tio.boot.server.TioBootServer;
import com.litongjava.tio.http.server.router.HttpRequestRouter;
import com.litongjava.tio.web.hello.handler.HelloRequestHandler;
public class HttpRequestHanlderConfig {
  public void config() {
    HttpRequestRouter router = TioBootServer.me().getRequestRouter();
    // 实例化处理器
    HelloRequestHandler handler = new HelloRequestHandler();
    // 添加路由
    router.add("/hi", handler::hi);
    router.add("/hello", handler::hello);
  }
}
package com.litongjava.tio.web.hello.config;
import com.litongjava.context.BootConfiguration;
public class AppConfig implements BootConfiguration {
  @Override
  public void config() {
    new HttpRequestHanlderConfig().config();
  }
}
启动类
定义应用程序的入口点,启动 tio-boot 服务并记录启动时间。
package com.litongjava.tio.web.hello;
import com.litongjava.tio.boot.TioApplication;
import com.litongjava.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 并将其解压到指定目录。
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 及其他命令行工具。
export JAVA_HOME=~/program/graalvm-jdk-21.0.5+9.1
export GRAALVM_HOME=~/program/graalvm-jdk-21.0.5+9.1
export PATH=$JAVA_HOME/bin:$PATH
安装 Maven
Apache Maven 是 Java 项目的主要构建自动化工具。
1. 下载并解压 Maven
下载并解压 Maven 至本地环境。
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 命令。
export MVN_HOME=~/program/apache-maven-3.8.8/
export PATH=$MVN_HOME/bin:$PATH
构建应用程序
所有工具和依赖配置完成后,接下来编译并运行示例应用程序。
1. 克隆示例应用程序
克隆内置 HTTP 请求处理器的 TIO-Boot 示例应用程序。
git clone https://github.com/litongjava/tio-boot-http-request-handler-demo.git
2. 构建 JAR 文件(可选)
将 Java 应用程序编译成 JAR 文件。如果您打算直接构建本地镜像,此步骤可选。
cd tio-boot-http-request-handler-demo
mvn clean package -DskipTests -Pproduction
3. 构建本地二进制镜像
将应用程序编译成本地可执行文件,相较于在 JVM 上运行,可以减少启动时间和资源消耗。
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.5+9.1
root@ping-Inspiron-3458:~# export GRAALVM_HOME=~/program/graalvm-jdk-21.0.5+9.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] ---------< com.litongjava: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: com.litongjava: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] ------------------------------------------------------------------------
注意:以上日志仅为示例,实际构建过程可能有所不同。
启动测试
构建完成后,运行生成的本地可执行文件并测试其功能。
(base) root@DL:/data/apps/tio-boot-http-request-handler-demo# ./target/web-hello --server.port=1024
Jan 06, 2025 4:42:52 PM com.litongjava.tio.utils.environment.Prop <init>
INFO: file created successful:app.properties
Jan 06, 2025 4:42:52 PM com.litongjava.tio.utils.environment.EnvUtils load
INFO: app.env:null
Jan 06, 2025 4:42:52 PM com.litongjava.tio.utils.environment.EnvUtils load
INFO: app.name:null
Jan 06, 2025 4:42:52 PM com.litongjava.tio.boot.context.TioApplicationContext run
INFO: AOP class not found: com.litongjava.jfinal.aop.Aop
Jan 06, 2025 4:42:52 PM com.litongjava.tio.boot.context.TioApplicationContext configureHttp
INFO: Server session enabled: false
Jan 06, 2025 4:42:52 PM com.litongjava.tio.boot.context.TioApplicationContext run
INFO: Using cache: class com.litongjava.tio.utils.cache.mapcache.ConcurrentMapCacheFactory
Jan 06, 2025 4:42:52 PM com.litongjava.tio.boot.context.TioApplicationContext run
INFO: Server heartbeat timeout: 0
Jan 06, 2025 4:42:52 PM com.litongjava.tio.utils.Threads getTioExecutor
INFO: new worker thead pool:com.litongjava.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 com.litongjava.tio.boot.context.TioApplicationContext run
INFO: HTTP handler:
{
  "/hi": "com.litongjava.tio.web.hello.config.HttpRequestHanlderConfig$$Lambda/0xd081350b15bca0456936253d349a9e11adb6c5190@4bb38d70",
  "/hello": "com.litongjava.tio.web.hello.config.HttpRequestHanlderConfig$$Lambda/0xcb437d3e797185b36b558533d31a5d6958f792c10@299840ed"
}
Jan 06, 2025 4:42:52 PM com.litongjava.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 com.litongjava.tio.boot.context.TioApplicationContext printUrl
INFO: Server port: 1024
Jan 06, 2025 4:42:52 PM com.litongjava.tio.boot.context.TioApplicationContext printUrl
INFO: Access URL: http://localhost:1024
17ms
启动时间仅为 17 毫秒,极大提升了应用的响应速度。
通过 curl 命令进行测试:
curl http://localhost:1024/hi
curl http://localhost:1024/hello
构建后的可执行文件大小约为 34MB。您可以通过以下链接下载构建后的文件:
性能测试
使用 ApacheBench (ab) 工具进行性能测试,模拟高并发请求。
ab -c1000 -n10000000 http://localhost:1024/ok
此命令将以 1000 个并发连接,总共发送 1000 万个请求到 http://localhost/ok,用于评估服务器的处理能力和稳定性。
总结
通过上述步骤,我们成功地使用 GraalVM 将基于 tio-boot 的 Java 应用程序编译为原生可执行文件。这不仅显著减少了启动时间,还优化了资源消耗,使应用在生产环境中表现更加高效。GraalVM 的原生镜像技术为 Java 开发者提供了强大的性能优化手段,是构建高性能服务器应用程序的理想选择。
