使用 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>1.9.0</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 开发者提供了强大的性能优化手段,是构建高性能服务器应用程序的理想选择。