Vert.x 实战(五):性能验证、Native Image 与故障隔离

Vert.x 实战(五):性能验证、Native Image 与故障隔离

Elvis Lv2

性能数字只有与代码版本、测试环境、请求模型和原始输出放在一起才有意义。当前项目还不具备稳定、隔离的压测环境,因此本章先建立可复现的测试方法,不预设吞吐结论。

固定请求模型

先准备 create-order.lua

1
2
3
wrk.method = "POST"
wrk.headers["Content-Type"] = "application/json"
wrk.body = '{"customerId":"load-test","sku":"book","quantity":1}'

当前库存只有 10 件,连续压测很快会得到 409,因此它并不适合直接测吞吐。性能场景必须增加可重置的测试库存或使用不会修改状态的专用端点;把大量 409 也统计成“成功 QPS”没有业务意义。

准备好隔离数据后再运行:

1
2
wrk -t4 -c200 -d60s --latency \
-s create-order.lua http://127.0.0.1:8080/orders

每次必须记录的内容

1
2
3
4
5
6
7
8
9
提交版本:
JDK / Vert.x / Gradle:
CPU / 内存 / 操作系统:
服务端与压测端是否同机:
线程、连接、持续时间:
请求体与数据准备方式:
QPS、P50、P95、P99、最大延迟:
非 2xx 数量和错误类型:
CPU、堆、GC 和 Event Loop 阻塞:

优化必须一次只改变一个变量,并保留优化前后原始输出。JFR 或 async-profiler 用来定位热点,不能先猜“JSON 慢”就替换序列化库。

本章没有给出新的 QPS,是因为当前教学实现包含有限库存、内存存储且没有稳定压测环境。缺少这些条件时,诚实地不下结论比漂亮数字更有价值。

用子项目构建 Native Image

Native Image 通常能缩短启动时间并降低部分场景的常驻内存,但代价包括更慢的构建、闭世界分析限制,以及与 JVM 不同的峰值性能特征。要讨论这些取舍,第一步是让同一份业务代码真正生成并运行原生二进制。

项目新增 native-image-demo 子项目。它依赖根项目,不复制订单代码:

1
2
3
4
5
6
7
8
9
10
11
12
plugins {
application
id("org.graalvm.buildtools.native") version "0.11.1"
}

repositories {
mavenCentral()
}

dependencies {
implementation(project(":"))
}

多项目构建中,子项目不会自动继承根项目的 repositories。第一次执行时正是因为缺少这段配置,Gradle 无法解析根项目传递来的 Vert.x 依赖,构建在进入闭世界分析之前失败。

原生二进制配置为:

1
2
3
4
5
6
7
8
9
10
graalvmNative {
toolchainDetection = true
binaries {
named("main") {
imageName = "vertx-order-native"
mainClass = "dev.elvis.orders.nativeimage.NativeOrderApplication"
buildArgs.add("-H:+ReportExceptionStackTraces")
}
}
}

构建命令从根目录执行:

1
./gradlew :native-image-demo:nativeCompile

本次实际环境和结果如下:

1
2
3
4
5
6
7
8
GraalVM CE:25.0.3
架构:macOS arm64
Native Image 阶段:23.0 秒
可达类型:7,562
可达方法:39,049
反射注册类型:413
峰值构建 RSS:1.85 GiB
二进制大小:27.68 MiB

这些是当前机器、依赖和提交下的一次构建记录,不是对其他项目的尺寸或速度承诺。

处理 Netty allocator 的运行时问题

第一次生成的二进制可以监听端口,但请求建立连接后立即被重置。增加 HTTP Server 异常日志后得到堆栈:

1
2
3
java.lang.NullPointerException
at io.netty.util.internal.PlatformDependent0.safeConstructPutInt(...)
at io.netty.buffer.AdaptivePoolingAllocator$Chunk.<init>(...)

问题发生在 Netty 的 adaptive allocator,而不是路由或 JSON 反射。使用运行参数验证 pooled allocator 后,健康检查恢复 200。为了不要求部署者记住额外参数,子项目提供专用入口:

1
2
3
4
5
6
public final class NativeOrderApplication {
public static void main(String[] args) {
System.setProperty("io.netty.allocator.type", "pooled");
OrderApplication.main(args);
}
}

这个入口只属于 Native 子项目,不会改变 JVM 主项目。它也记录了明确的兼容原因,后续升级 Netty 或 GraalVM 时可以删除属性重测,而不是永久保留一个来源不明的系统参数。

验证原生服务的业务路径

1
2
HTTP_PORT=18081 \
./native-image-demo/build/native/nativeCompile/vertx-order-native

本次验证了三条请求:

请求 预期 实际
GET /health 服务可用 200,{"status":"UP"}
POST /orders,购买 2 本书 创建并预留库存 201,剩余库存 8
POST /orders,购买 4 个键盘 库存不足 409,返回明确错误

这说明 HTTP Router、JSON、Event Bus、Future 编排和内存状态在当前 Native Image 中能工作。WebSocket 长连接和熔断恢复计时仍应增加原生进程级自动化测试;健康检查成功本身不足以证明完整兼容。

用 Circuit Breaker 隔离支付故障

支付下游不可用时,新请求不应该持续冲击已经故障的服务。项目使用一个可以注入真实 WebClient 或测试实现的 ResilientPaymentClient

1
2
3
4
5
6
7
8
9
10
11
this.breaker = CircuitBreaker.create(
"payment",
vertx,
new CircuitBreakerOptions()
.setTimeout(800)
.setMaxFailures(3)
.setResetTimeout(5_000));

public Future<JsonObject> authorize(JsonObject payment) {
return breaker.execute(() -> delegate.apply(payment));
}

800 毫秒、3 次失败和 5 秒恢复只是演示参数。真实值要结合支付接口延迟分位、订单总超时预算、流量和下游恢复方式决定。

测试注入一个每次失败的支付实现,并用 AtomicInteger 记录下游真实调用次数。连续执行四次授权后断言:

1
2
assertThat(client.state()).isEqualTo(CircuitBreakerState.OPEN);
assertThat(calls).hasValue(3);

前三次到达下游,达到失败阈值后第四次被熔断器直接拒绝。这是由当前 Vert.x 5.0.0 依赖和自动化测试验证的结果,而不是根据文档推测。

支付请求不能随意自动重试。超时不代表支付方没有成功处理,缺少幂等键时重试可能重复扣款。降级也不能伪造支付成功;更合理的状态通常是 PAYMENT_PENDING 或明确失败,再通过查询与对账恢复。

Circuit Breaker 只负责故障隔离,不等于分布式事务、限流或监控。生产接入仍需记录开路状态、失败原因和半开探测,并为订单状态设计可靠恢复流程。

当前系列的验证边界

截至本文更新,配套项目执行 ./gradlew test,订单创建查询、库存不足和连续支付失败后三个场景均通过。Hexo 站点也可以完整生成。

仍未验证的内容会保持明确:没有可靠数据库、没有消息代理、没有稳定压测结果,也没有多节点部署。Native Image 已完成构建和三条 HTTP 路径验证,但还没有 WebSocket 与熔断恢复的原生进程级测试。后续补充时仍应同时提交代码、命令和原始结果。

  • 标题: Vert.x 实战(五):性能验证、Native Image 与故障隔离
  • 作者: Elvis
  • 创建于 : 2026-05-05 22:00:00
  • 更新于 : 2026-07-13 14:20:00
  • 链接: https://qianwj.github.io/2026/05/05/vertx-series-5/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。