Vert.x 实战(三):Event Loop、HTTP 边界与异步测试

Vert.x 实战(三):Event Loop、HTTP 边界与异步测试

Elvis Lv2

订单项目目前所有 Handler 都只操作内存并发送异步消息,很快就会返回。但一旦接入同步 JDBC、文件读取或旧 SDK,最容易出现的错误就是把阻塞调用直接放进路由 Handler。

1
2
3
4
router.post("/orders").handler(ctx -> {
legacyClient.createOrder(ctx.body().asString()); // 同步网络调用
ctx.response().end();
});

问题不只影响当前请求。这个 Event Loop 负责的其他连接也无法及时处理读写事件,因此延迟会成片上升。

先观察,不先调线程数

开发环境可以把检查阈值调低,让问题尽早暴露:

1
2
3
4
VertxOptions options = new VertxOptions()
.setBlockedThreadCheckInterval(500)
.setMaxEventLoopExecuteTime(200);
Vertx vertx = Vertx.vertx(options);

这些值用于诊断,不应该不经测量照搬到生产。出现警告后先看堆栈,确认阻塞位置;增加 Event Loop 线程数只能延后拥塞,不能修复同步调用。

无法替换的阻塞 API

旧 SDK 暂时没有异步版本时,可以把小段阻塞操作移到 Worker Pool:

1
2
3
vertx.executeBlocking(() -> legacyClient.reserve(sku, quantity))
.onSuccess(result -> message.reply(result))
.onFailure(error -> message.fail(503, error.getMessage()));

Worker Pool 也有上限。必须同时设置下游超时、监控队列等待并限制并发;否则只是把 Event Loop 堵塞改成 Worker Pool 排队。

用 JFR 保留证据

1
jcmd <pid> JFR.start name=vertx settings=profile duration=60s filename=vertx.jfr

录制期间执行稳定、可重复的请求,再用 JDK Mission Control 查看线程状态、Socket I/O、锁等待和分配热点。文章不会给出“JFR 固定小于 2%”之类脱离工作负载的承诺;开销应在自己的环境中测量。

本章也不声称当前示例能达到某个 QPS。没有独立压测机、请求模型、延迟分位和错误率的单个吞吐数字没有参考意义。

HTTP 层只承担协议职责

一个教学项目无法完整覆盖网关所需的协议安全、连接池、限流、动态配置和可观测性,因此当前项目只实现订单服务真正需要的 HTTP 接入层:

1
2
3
4
5
Router router = Router.router(vertx);
router.route().handler(BodyHandler.create());
router.get("/health").handler(this::health);
router.post("/orders").handler(this::createOrder);
router.get("/orders/:id").handler(this::findOrder);

HttpVerticle 把 JSON 转成 Event Bus 消息,再将回复转换为 HTTP 响应。订单规则仍留在 OrderVerticle,以后增加其他协议入口时不需要复制业务校验。

JSON 解析失败应立即返回 400:

1
2
3
4
5
6
try {
request = ctx.body().asJsonObject();
} catch (RuntimeException error) {
respond(ctx, 400, new JsonObject().put("error", "invalid JSON body"));
return;
}

创建成功使用 201,库存不足使用 409,内部依赖不可用使用 503。状态码会影响客户端究竟修正参数、提示用户还是稍后重试,不能把所有错误统一包装成 200 或 500。

公开部署前还需要请求体大小限制、Schema、认证授权、请求 ID、结构化日志、幂等键、TLS 和优雅停机。本文称它为“HTTP 边界”,而不是无法由代码支撑的“生产网关”。

用 VertxTestContext 测真实异步链路

每个测试先部署实际消费者:

1
2
3
4
5
6
@BeforeEach
void deploy(Vertx vertx, VertxTestContext context) {
vertx.deployVerticle(new InventoryVerticle())
.compose(ignored -> vertx.deployVerticle(new OrderVerticle()))
.onComplete(context.succeedingThenComplete());
}

只有两个 Verticle 都启动成功后才进入测试。忘记完成 VertxTestContext 会等待到超时,过早完成又可能让尚未运行的断言被跳过。

创建和查询通过同一条 Future 链验证:

1
2
3
4
5
6
7
8
vertx.eventBus().<JsonObject>request(OrderVerticle.CREATE_ADDRESS, request)
.compose(created -> vertx.eventBus().<JsonObject>request(
OrderVerticle.FIND_ADDRESS,
created.body().getString("id")))
.onComplete(context.succeeding(found -> context.verify(() -> {
assertThat(found.body().getString("sku")).isEqualTo("book");
context.completeNow();
})));

context.verify 会把异步回调中的断言异常交还 JUnit。第二条测试通过 context.failing 验证库存不足必须失败,而不是仅检查成功路径。

1
./gradlew test

这些测试证明当前消息链在已覆盖场景下工作,但还不能替代 HTTP 集成测试、并发库存测试、超时测试和线上监控。

  • 标题: Vert.x 实战(三):Event Loop、HTTP 边界与异步测试
  • 作者: Elvis
  • 创建于 : 2026-03-04 18:00:00
  • 更新于 : 2026-04-01 13:20:00
  • 链接: https://qianwj.github.io/2026/03/04/vertx-series-3/
  • 版权声明: 本文章采用 CC BY-NC-SA 4.0 进行许可。