52、原生编译的 Java 微服务实践

原生编译的 Java 微服务实践

1. 配置文件与验证脚本更新

在微服务开发中,对于产品微服务, src/test/resources/access-filter.json 文件内容如下:

{ "rules":
  [
    {"excludeClasses": "org.apache.maven.surefire.**"},
    {"excludeClasses": "net.bytebuddy.**"},
    {"excludeClasses": "org.apiguardian.**"},
    {"excludeClasses": "org.junit.**"},
    {"excludeClasses": "org.gradle.**"},
    {"excludeClasses": "org.mockito.**"},
    {"excludeClasses": "org.springframework.test.**"},
    {"excludeClasses": "org.springframework.boot.test.**"},
    {"excludeClasses": "org.testcontainers.**"},
    {"excludeClasses": "se.magnus.microservices.core.product.MapperTests"},
    {"excludeClasses": "se.magnus.microservices.core.product.MongoDbTestBase"},
    {"excludeClasses": "se.magnus.microservices.core.product.PersistenceTests"},
    {"excludeClasses": "se.magnus.microservices.core.product.ProductServiceApplicationTests"}
  ]
}

config-output-dir 参数指定的文件夹将包含生成的配置文件,而 src/main/resources/META-INF/Native Image 文件夹是 GraalVM 原生编译器查找可达性元数据的位置。

在验证脚本方面,之前使用 eclipse - temurin 作为 Docker 镜像的基础镜像, test - em - all.bash 验证脚本使用该基础镜像自带的 curl 命令在 product - composite 容器内运行断路器测试。但对于原生编译的微服务,Docker 镜像不再包含 curl 等实用工具。为解决此问题, curl 命令将从 auth - server 容器执行,因为其 Docker 镜像仍基于 eclipse - temurin ,包含所需的 curl 命令。由于用于验证断路器功能的端点未在 Docker 内部网络外部暴露,所以验证脚本在 product - composite 容器内运行 curl 命令。同时,由于断路器测试从 auth - server 执行,主机名 localhost 需替换为 product - composite

2. 测试和编译原生镜像

要进行原生镜像的测试和编译,需使用以下工具:
- 运行跟踪代理
- 执行原生测试
- 为当前操作系统创建原生镜像
- 将原生镜像创建为 Docker 镜像

由于前三个工具要求本地安装 GraalVM 及其原生镜像编译器,所以首先要进行安装。

2.1 安装 GraalVM 及其原生镜像编译器

安装步骤如下:
1. 使用 SDKman(https://sdkman.io)安装 GraalVM。若未安装 SDKman,可使用以下命令安装:

curl -s "https://get.sdkman.io" | bash
source "$HOME/.sdkman/bin/sdkman-init.sh"
  1. 使用以下命令验证 SDKman 是否正确安装:
sdk version

期望输出类似:

SDKMAN 5.18.1
  1. 在 Linux 上,GraalVM 的原生镜像编译器需要安装 GCC。若在 Windows 的 WSL 2 下运行 Ubuntu 实例,可使用以下命令安装 GCC 及其依赖项:
sudo apt install -y build-essential libz-dev zlib1g-dev
  1. 安装 GraalVM 22.3.1 版本(适用于 Java 17),并将其设置为默认 Java 版本:
sdk install java 22.3.1.r17-grl 
sdk default java 22.3.1.r17-grl
  1. 安装原生镜像编译器:
gu install Native Image
  1. 验证安装:
java -version
gu list

java -version 命令期望输出类似:

openjdk version "17.0.6" 2023-01-17
OpenJDK Runtime Environment GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13)
OpenJDK 64-Bit Server VM GraalVM CE 22.3.1 (build 17.0.6+10-jvmci-22.3-b13, mixed mode, sharing)

gu list 命令期望输出:
| ComponentId | Version | Component name |
| — | — | — |
| graalvm | 22.3.1 | GraalVM Core |
| Native Image | 22.3.1 | Native Image |

2.2 运行跟踪代理

对于某些情况,可能需要使用跟踪代理生成所需的可达性元数据。若要尝试使用跟踪代理,可按以下步骤操作:
1. 移除 build.gradle 文件中所选微服务部分 jvmArgs 参数前的注释字符 // 以激活该参数。
2. 运行 Gradle 测试命令,以产品服务为例:

cd $BOOK_HOME/Chapter23
./gradlew :microservices:product-service:test --no-daemon

此为正常的 Gradle 测试命令,但为避免内存不足,禁用了 Gradle 守护进程。默认情况下,守护进程的堆内存限制为 512 MB,在大多数情况下对跟踪代理而言不足。
3. 测试完成后,应在 microservices/product - service/src/main/resources/META - INF/Native Image 文件夹中找到以下文件:
- jni - config.json
- predefined - classes - config.json
- proxy - config.json
- reflect - config.json
- resource - config.json
- serialization - config.json

浏览生成的文件后,在构建文件的 jvmArgs 参数前添加注释以禁用跟踪代理,并删除创建的文件。

2.3 执行原生测试

执行原生测试有助于自动发现创建原生镜像时的问题,但目前存在一些问题导致无法对部分微服务使用原生测试:
- 使用 Testcontainers 的测试不能用于原生测试,详情见 https://github.com/spring - projects/spring - boot/issues/35663。
- 使用 Mockito 的测试也不能用于原生测试,详情见 https://github.com/spring - projects/spring - boot/issues/32195 和 https://github.com/mockito/mockito/issues/2435。

因此,所有测试类都使用 @DisabledInNativeImage 注解在类级别禁用了原生测试。这意味着仍可运行原生测试命令,所有原生镜像将被创建,但目前原生镜像中不会执行任何测试。随着这些问题的解决,可逐步移除 @DisabledInNativeImage 注解,使更多测试能由原生测试命令运行。

要对所有四个微服务运行原生测试,可使用以下命令:

./gradlew nativeTest

要测试特定微服务,可使用类似命令:

./gradlew :microservices:product-service:nativeTest

每次对微服务进行测试后,原生测试工具会生成如下测试报告:

JUnit Platform on Native Image - report
...
[        13 tests found           ]
[        13 tests skipped         ]
[         0 tests started         ]
[         0 tests aborted         ]
[         0 tests successful      ]
[         0 tests failed          ]

从报告中可看出,目前所有测试均被跳过。

2.4 为当前操作系统创建原生镜像

可使用 Gradle 的 nativeImage 命令为当前操作系统和硬件架构创建可执行文件。以 product - composite 微服务为例,操作步骤如下:
1. 使用以下命令创建原生镜像:

./gradlew microservices:product-composite-service:nativeCompile

可执行文件将在 build/native/nativeCompile 文件夹中创建,文件名为 product - composite - service
2. 使用 file 命令检查可执行文件:

file microservices/product-composite-service/build/native/nativeCompile/product-composite-service

输出可能类似:

…product-composite-service: Mach-O 64-bit executable arm64

其中, Mach - O 表示文件是为 macOS 编译的, arm64 表示是为 Apple 硅芯片编译的。
3. 手动启动所需资源。在此例中,仅需启动 RabbitMQ 即可成功启动:

docker-compose up -d rabbitmq
  1. 在终端中通过指定与 docker - compose 文件中相同的环境变量来启动原生镜像:
SPRING_RABBITMQ_USERNAME=rabbit-user-prod \
SPRING_RABBITMQ_PASSWORD=rabbit-pwd-prod \
SPRING_CONFIG_LOCATION=file:config-repo/application.yml,file:config-repo/product-composite.yml \
microservices/product-composite-service/build/native/nativeCompile/product-composite-service

它应能快速启动,并在日志输出中打印类似内容:

Started ProductCompositeServiceApplication in 0.543 seconds
  1. 调用其存活探针进行测试:
curl localhost:4004/actuator/health/liveness

期望输出:

{"status":"UP"}
  1. Ctrl + C 停止执行,并使用以下命令停止 RabbitMQ 容器:
docker-compose down

虽然这是创建原生镜像最快的方法,但对于后续测试场景不太实用,更需要为 Linux 构建原生镜像并将其放入 Docker 容器中。

2.5 将原生镜像创建为 Docker 镜像

创建步骤如下:
1. 此过程非常消耗资源,首先确保 Docker Desktop 允许至少消耗 10 GB 内存,以避免内存不足错误。
2. 若计算机内存小于 32 GB,此时可停止 minikube 实例以避免计算机内存不足,使用以下命令:

minikube stop
  1. 确保 Docker 客户端与 Docker Desktop 通信,而非与 minikube 实例通信:
eval $(minikube docker-env -u)
  1. 运行以下命令编译产品服务:
./gradlew :microservices:product-service:bootBuildImage --no-daemon

若构建失败并出现类似 <container - name> exited with code 137 的错误消息,表明 Docker 内存不足。编译过程中会有大量输出,包括各种警告和错误消息。成功编译的日志输出类似:

Successfully built image 'docker.io/hands-on/native-product-service:latest'
  1. 使用以下命令对其余三个微服务进行原生编译:
./gradlew :microservices:product-composite-service:bootBuildImage --no-daemon
./gradlew :microservices:recommendation-service:bootBuildImage --no-daemon
./gradlew :microservices:review-service:bootBuildImage --no-daemon
  1. 使用以下命令验证 Docker 镜像是否成功构建:
docker images | grep "hands-on/native"

3. 使用 Docker Compose 进行测试

为使用包含原生编译微服务的 Docker 镜像,创建了两个新的 Docker Compose 文件 docker - compose - native.yml docker - compose - partitions - native.yml 。它们是 docker - compose.yml docker - compose - partitions.yml 的副本,移除了微服务定义中的 build 选项,并更改了要使用的 Docker 镜像名称,使用之前创建的以 native - 开头的镜像。

为比较启动时间和初始内存消耗,将进行以下测试:
- 使用禁用 AOT 模式的基于 Java VM 的微服务。
- 使用启用 AOT 模式的基于 Java VM 的微服务。
- 使用包含原生编译微服务的 Docker 镜像。

首先,使用以下命令停止 minikube 实例以避免端口冲突:

minikube stop
3.1 测试禁用 AOT 模式的基于 Java VM 的微服务

操作步骤如下:
1. 在 Docker Desktop 中编译源代码并构建基于 Java VM 的 Docker 镜像:

cd $BOOK_HOME/Chapter23
eval $(minikube docker-env -u)
./gradlew build
docker-compose build
  1. 使用基于 Java VM 的微服务的默认 Docker Compose 文件:
unset COMPOSE_FILE
  1. 启动除微服务容器外的所有容器:
docker-compose up -d mysql mongodb rabbitmq auth-server gateway

等待容器启动,直到 CPU 负载下降。
4. 使用 Java VM 启动微服务:

docker-compose up -d 

等待微服务启动,再次监控 CPU 负载。
5. 使用以下命令查找微服务启动所需的时间:

docker-compose logs product-composite product review recommendation | grep ": Started"

输出中可看到启动时间在 5.5 到 7 秒之间。由于四个微服务实例同时启动,与逐个启动相比,启动时间更长。
6. 运行测试以验证系统环境是否按预期工作:

USE_K8S=false HOST=localhost PORT=8443 HEALTH_URL=https://localhost:8443 ./test-em-all.bash
  1. 期望测试输出与之前看到的相同。
  2. 使用以下命令查看启动并运行测试后使用的内存:
docker stats --no-stream

可看到微服务消耗约 240 - 310 MB 内存。
9. 关闭系统环境:

docker compose down
3.2 测试启用 AOT 模式的基于 Java VM 的微服务

操作步骤如下:
1. 启动除微服务容器外的所有容器:

docker-compose up -d mysql mongodb rabbitmq auth-server gateway
  1. 编辑每个微服务的 Dockerfile,在 ENVIRONMENT 命令中设置 -Dspring.aot.enabled=true ,使其如下所示:
ENTRYPOINT ["java", "-Dspring.aot.enabled=true", "org.springframework.boot.loader.JarLauncher"]
  1. 重建微服务:
docker-compose build
  1. 启动微服务:
docker-compose up -d
  1. 检查 AOT 模式:
docker-compose logs product-composite product review recommendation | grep "Starting AOT-processed"

期望输出包含四行 Starting AOT - processed
6. 检查启动时间:

docker-compose logs product-composite product review recommendation | grep ": Started"

期望输出与禁用 AOT 模式时类似,但启动时间稍短。例如,启动时间可能在 4.5 到 5.5 秒之间,比正常 Java VM 启动时间快 1 到 1.5 秒。
7. 运行 test - em - all.bash

USE_K8S=false HOST=localhost PORT=8443 HEALTH_URL=https://localhost:8443 ./test-em-all.bash

期望输出与禁用 AOT 模式时相同。
8. 恢复 Dockerfile 中的更改并重建 Docker 镜像以禁用 AOT 模式。
9. 关闭系统环境:

docker compose down
3.3 测试原生编译的微服务

操作步骤如下:
1. 切换到新的 Docker Compose 文件:

export COMPOSE_FILE=docker-compose-native.yml
  1. 启动除微服务容器外的所有容器:
docker-compose up -d mysql mongodb rabbitmq auth-server gateway

等待容器启动,直到 CPU 负载下降。
3. 使用 Java VM 启动微服务:

docker-compose up -d 

等待微服务启动,再次监控 CPU 负载。
4. 使用以下命令查找原生编译微服务的启动时间:

docker-compose logs product-composite product review recommendation | grep ": Started"

可看到启动时间在 0.2 - 0.5 秒之间。考虑到所有微服务实例同时启动,与基于 Java VM 的测试所需的 5.5 到 7 秒相比,这个时间非常可观。
5. 运行测试以验证系统环境是否按预期工作:

USE_K8S=false HOST=localhost PORT=8443 HEALTH_URL=https://localhost:8443 ./test-em-all.bash

期望输出与使用基于 Java VM 的 Docker 镜像的测试相同。
6. 使用以下命令查看启动并运行测试后使用的内存:

docker stats --no-stream

可看到微服务消耗约 80 - 130 MB 内存,与 Java VM 容器使用的 240 - 310 MB 相比,有明显减少。
7. 关闭系统环境:

docker compose down

总结

通过上述步骤,我们完成了原生编译 Java 微服务的一系列操作,包括配置文件的设置、验证脚本的更新、GraalVM 及其原生镜像编译器的安装、原生镜像的测试和编译,以及使用 Docker Compose 进行不同模式下的测试。从测试结果可以看出,原生编译的微服务在启动时间和内存消耗方面具有明显优势。

以下是整体流程的 mermaid 流程图:

graph LR
    A[安装 GraalVM 及其原生镜像编译器] --> B[运行跟踪代理]
    B --> C[执行原生测试]
    C --> D[为当前操作系统创建原生镜像]
    D --> E[将原生镜像创建为 Docker 镜像]
    E --> F[使用 Docker Compose 进行测试]
    F --> F1[测试禁用 AOT 模式的基于 Java VM 的微服务]
    F --> F2[测试启用 AOT 模式的基于 Java VM 的微服务]
    F --> F3[测试原生编译的微服务]

通过这些操作和测试,我们可以更好地了解原生编译 Java 微服务的性能和优势,为实际项目的应用提供参考。

4. 性能对比分析

为了更直观地展示不同模式下微服务的性能差异,我们将上述测试结果进行汇总对比,如下表所示:
| 测试模式 | 启动时间范围 | 内存消耗范围 |
| — | — | — |
| 禁用 AOT 模式的基于 Java VM 的微服务 | 5.5 - 7 秒 | 240 - 310 MB |
| 启用 AOT 模式的基于 Java VM 的微服务 | 4.5 - 5.5 秒 | 240 - 310 MB |
| 原生编译的微服务 | 0.2 - 0.5 秒 | 80 - 130 MB |

从表格中可以清晰地看出,原生编译的微服务在启动时间和内存消耗方面都有显著的优化。启动时间大幅缩短,这对于需要快速响应和部署的场景非常有利,例如在云原生环境中进行弹性伸缩时,能够更快地启动新的服务实例。而内存消耗的减少,意味着在相同的硬件资源下,可以运行更多的微服务实例,提高资源利用率,降低成本。

5. 常见问题及解决方法

在进行原生编译 Java 微服务的过程中,可能会遇到一些常见问题,以下是一些问题及对应的解决方法:

5.1 内存不足问题
  • 问题描述 :在运行跟踪代理或编译原生镜像时,可能会出现内存不足的错误,如构建 Docker 镜像时出现 <container - name> exited with code 137 错误。
  • 解决方法
    • 确保 Docker Desktop 允许至少消耗 10 GB 内存。
    • 若计算机内存小于 32 GB,停止 minikube 实例以释放内存。
    • 在运行 Gradle 测试命令时,禁用 Gradle 守护进程,如 ./gradlew :microservices:product-service:test --no-daemon
5.2 原生测试无法使用问题
  • 问题描述 :使用 Testcontainers 或 Mockito 的测试不能用于原生测试。
  • 解决方法 :目前所有测试类使用 @DisabledInNativeImage 注解在类级别禁用原生测试。随着相关问题的解决,逐步移除该注解,使更多测试能由原生测试命令运行。

6. 实际应用建议

根据上述测试和分析,以下是一些在实际项目中应用原生编译 Java 微服务的建议:

6.1 选择合适的场景

原生编译的微服务在启动时间和内存消耗方面有优势,适合对启动速度要求高、资源受限的场景,如云原生环境、边缘计算等。而对于对启动时间要求不高、开发和测试阶段,基于 Java VM 的微服务可能更合适,因为其开发和调试更加方便。

6.2 逐步引入

在实际项目中,可以先选择部分对性能要求较高的微服务进行原生编译测试,验证其性能和稳定性。在确保没有问题后,再逐步将更多的微服务迁移到原生编译模式。

6.3 持续监控和优化

引入原生编译微服务后,需要持续监控其性能指标,如启动时间、内存消耗、CPU 使用率等。根据监控结果进行优化,如调整配置参数、优化代码等。

7. 未来展望

随着技术的不断发展,原生编译 Java 微服务的性能和稳定性将不断提高。未来可能会有更多的工具和框架支持原生编译,使开发和部署更加便捷。同时,随着硬件技术的进步,原生编译微服务在更多的场景中将会得到更广泛的应用。

以下是一个关于原生编译 Java 微服务应用流程的 mermaid 流程图:

graph LR
    A[需求分析] --> B{是否适合原生编译}
    B -- 是 --> C[选择部分微服务进行原生编译测试]
    B -- 否 --> D[使用基于 Java VM 的微服务]
    C --> E[验证性能和稳定性]
    E -- 通过 --> F[逐步迁移更多微服务]
    E -- 未通过 --> G[优化和调整]
    G --> C
    F --> H[持续监控和优化]

综上所述,原生编译 Java 微服务为我们提供了一种更高效、更节省资源的微服务部署方式。通过合理的应用和优化,能够显著提升系统的性能和竞争力。在实际项目中,我们可以根据具体需求和场景,灵活运用这种技术,为业务的发展提供有力支持。

基于matlab建模FOC观测器采用龙贝格观测器+PLL进行无传感器控制(Simulink仿真实现)内容概要:本文档主要介绍基于Matlab/Simulink平台实现的多种科研仿真项目,涵盖电机控制、无人机路径规划、电力系统优化、信号处理、图像处理、故障诊断等多个领域。重点内容之一是“基于Matlab建模FOC观测器,采用龙贝格观测器+PLL进行无传感器控制”的Simulink仿真实现,该方法通过状态观测器估算电机转子位置与速度,结合锁相环(PLL)实现精确控制,适用于永磁同步电机等无位置传感器驱动场景。文档还列举了大量相关科研案例与算法实现,如卡尔曼滤波、粒子群优化、深度学习、多智能体协同等,展示了Matlab在工程仿真与算法验证中的广泛应用。; 适合人群:具备一定Matlab编程基础,从事自动化、电气工程、控制科学、机器人、电力电子等相关领域的研究生、科研人员及工程技术人员。; 使用场景及目标:①学习并掌握FOC矢量控制中无传感器控制的核心原理与实现方法;②理解龙贝格观测器与PLL在状态估计中的作用与仿真建模技巧;③借鉴文中丰富的Matlab/Simulink案例,开展科研复现、算法优化或课程设计;④应用于电机驱动系统、无人机控制、智能电网等实际工程仿真项目。; 阅读建议:建议结合Simulink模型与代码进行实践操作,重点关注观测器设计、参数整定与仿真验证流程。对于复杂算法部分,可先从基础案例入手,逐步深入原理分析与模型改进。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符  | 博主筛选后可见
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值