资讯详情

资讯详情

Vert.x入门:从零搭建事件驱动的高并发异步服务

Vert.x 这个框架我断断续续用过两三年从最开始拿它写内部工具到后来在几个正经项目里落地踩过的坑不算少但总体体验是这东西一旦理解了它的线程模型和事件驱动思路写出来的服务在并发和资源占用上比传统 Spring Boot 那套舒服太多。这篇入门文章我不打算给你堆概念就按我自己当时的学习路径来从零搭一个带 HTTP 接口、事件总线通信、MySQL 查询的 Vert.x 应用代码全贴注释写清楚你照着敲一遍基本就能上手。1. Vert.x 到底是什么为什么值得学1.1 它不是一个 Web 框架而是一整套异步工具包很多人第一次听说 Vert.x第一反应是又一个 Java Web 框架。这么理解不算全错但会严重限制你的想象力。Vert.x 官方给自己的定位是 Polyglot 异步编程工具包也就是说它不只是用来写 HTTP 接口的它能做 TCP/UDP 通信、消息队列消费、定时任务、分布式事件总线、反应式数据库访问、甚至内嵌一个完整的 Web 服务器。你完全可以用它写一个纯粹的 WebSocket 推送服务也可以用它做 Kafka 的消费者或者把它当成高性能网关的内核。我自己最直观的感受是用 Spring Boot 写一个服务你要么用 Servlet 的阻塞模型要么额外引入 WebFlux 和 Reactor学习成本不低。Vert.x 的写法天然就是事件驱动的你写的是回调、Future、RxJava 或者协程这几套 API 它都有对应封装。简单说一个 Vert.x 应用就是一组在 Event Loop 线程上运行的处理器每个处理器通过事件总线互相通信这种架构让它在高并发下的线程开销非常小。1.2 核心线程模型Event Loop 到底是怎么转的Vert.x 的线程模型是它的灵魂。每个 Vert.x 实例默认创建 CPU 核心数乘以一定系数的 Event Loop 线程这些线程是单线程模型内核是 Netty 的 EventLoop。你的所有业务代码——HTTP 请求处理、数据库回调、定时任务回调——绝大多数都跑在这几个线程上。所谓单线程模型就是同一个 Event Loop 上不会同时执行两个处理器所以你不需要像传统多线程编程那样加锁。这里有个特别需要注意的点既然 Event Loop 线程不能阻塞那你的业务代码里绝对不能出现 Thread.sleep、同步 JDBC 查询、死循环、大文件同步读取这类操作。一旦某个处理器把 Event Loop 线程占住整个 Vert.x 实例上跑的其他请求全部排队等着性能瞬间崩塌。我见过不少新手写 Vert.x 代码时顺手在处理器里调了个同步的 Redis 客户端结果压测一上来 CPU 跑满但 QPS 几乎为零。正确做法是需要阻塞的操作比如同步第三方 SDK、磁盘 IO丢到 Worker 线程池去执行Vert.x 提供了 executeBlocking 方法专门干这个事需要异步的操作HTTP 调用、数据库异步驱动、Redis 异步客户端直接用回调让 Event Loop 线程赶紧腾出来处理下一个事件。理解了这一点后面所有代码你都能看明白它为什么那么写。1.3 适合谁看看完能到什么水平这篇教程适合有一年以上 Java 经验、熟悉 Maven 和 IDEA 基本操作、了解 Lambda 表达式和函数式接口的读者。如果你写过 Netty 或者 NIO 相关代码会更好理解但不是必须。另外如果你对 Reactive Streams 或者响应式编程有一点概念哪怕只是听说过 Flux 和 Mono学 Vert.x 会顺畅很多。看完这篇你能掌握搭建一个 Vert.x 工程、用 Router 实现 RESTful 接口、用 Event Bus 在不同业务模块之间发消息、用异步客户端操作 MySQL、处理 Json 数据、以及排查最常见的坑。这些足够你独立写一个简单的后端服务了。说实话Vert.x 的上手门槛没有网上说的那么高你不需要先学完整个 Reactor 生态才能动手把最基本的 HTTP 和 Event Bus 跑通其他的遇到再查就行。2. 工程搭建与依赖选型2.1 用一个最简单的 Maven 工程起步我先说下我推荐的工程结构不是必须严格照抄但按照这个来后面加模块、加代码都会比较舒服vertx-demo ├── pom.xml └── src └── main ├── java │ └── com │ └── demo │ ├── MainVerticle.java │ ├── HttpServerVerticle.java │ ├── DatabaseVerticle.java │ └── service │ └── UserService.java └── resources └── config.jsonpom.xml 里只需要引入一个核心依赖Vert.x 的模块是拆分的但大部分场景下 vertx-core 加 vertx-web 就够了。下面是我常用的依赖版本建议直接用与你 JDK 版本匹配的稳定版我这边用的是 JDK 11 和 Vert.x 4.4.6properties maven.compiler.source11/maven.compiler.source maven.compiler.target11/maven.compiler.target vertx.version4.4.6/vertx.version /properties dependencies dependency groupIdio.vertx/groupId artifactIdvertx-core/artifactId version${vertx.version}/version /dependency dependency groupIdio.vertx/groupId artifactIdvertx-web/artifactId version${vertx.version}/version /dependency /dependencies这里解释一下为什么不推荐把整个 BOM 全引进来。Vert.x 的模块非常细你全量引入会导致 jar 包膨胀而且很多模块你用不到。等真正需要时再按需加依赖比如后面要连 MySQL 就加 vertx-mysql-client要发 HTTP 请求就加 vertx-web-client要 Redis 就加 vertx-redis-client。这种按需引入的方式能让你对每个模块的作用有更清晰的认识排查依赖冲突也更容易。2.2 理解 Verticle 与 Deploy 机制Vert.x 里最核心的组件是 Verticle你可以把它理解成 Vert.x 世界里的一个独立任务单元。一个 Verticle 对应一个类可以部署多次每次部署会占用一个独立的上下文。Verticle 有两种类型Standard Verticle跑在 Event Loop 线程上适合处理 IO 密集和事件密集的任务Worker Verticle跑在 Worker 线程池里适合处理 CPU 密集或阻塞密集的任务。部署的方式非常简单Vertx vertx Vertx.vertx(); vertx.deployVerticle(new MainVerticle());不过主 Verticle 里一般不会直接写业务逻辑而是作为启动入口通过部署子 Verticle 来组织整个应用。比如我习惯的做法是启动时先部署 DatabaseVerticle再部署 HttpServerVerticle两个 Verticle 之间通过 Event Bus 通信。这种拆分的好处是职责清晰数据库挂了不会直接拖垮 HTTP 层后续加功能也只需要在新 Verticle 里订阅消息。注意部署是异步的别在主线程里直接依赖部署完成后的状态要用回调或者 Future 来处理。2.3 配置文件读取Vert.x 官方默认支持从 classpath 读取配置文件我一般用 JsonObject 存配置启动时读入内存JsonObject config vertx.fileSystem() .readFileBlocking(config.json) .toJsonObject();不推荐用阻塞读取这里用 readFileBlocking 是因为它只发生在启动阶段此时 Task 调度还不密集阻塞一次无伤大雅。真正跑起来的代码里所有文件 IO 都要用异步版本。config.json 里我放 MySQL 连接信息、HTTP 端口、Event Bus 地址前缀等这比把各种常量散落在代码里好维护得多。3. 写第一个能用的 HTTP 服务3.1 核心代码Router 路由注册现在进入正题。先看一个能跑的 HTTP 服务长什么样。我用 vertx-web 的 Router 来定义接口它比直接手写 HttpServer requestHandler 舒服得多支持路径参数、正则匹配、子路由、中间件这些在真实项目中都会用到。import io.vertx.core.AbstractVerticle; import io.vertx.core.Promise; import io.vertx.core.http.HttpServer; import io.vertx.core.http.HttpServerOptions; import io.vertx.ext.web.Router; import io.vertx.ext.web.handler.BodyHandler; public class HttpServerVerticle extends AbstractVerticle { Override public void start(PromiseVoid startPromise) { Router router Router.router(vertx); // 注册 BodyHandler解析 POST/PUT 请求体为 JsonObject 或表单 router.route().handler(BodyHandler.create()); // 一个简单的 GET 接口 router.get(/api/hello).handler(ctx - { ctx.json(new JsonObject() .put(message, Hello Vert.x!) .put(timestamp, System.currentTimeMillis())); }); // 带路径参数的 GET 接口 router.get(/api/users/:id).handler(ctx - { String userId ctx.pathParam(id); ctx.json(new JsonObject().put(userId, userId)); }); HttpServerOptions options new HttpServerOptions() .setPort(config().getInteger(http.port, 8080)) .setCompressionSupported(true); HttpServer server vertx.createHttpServer(options); server.requestHandler(router) .listen(ar - { if (ar.succeeded()) { System.out.println(HTTP server started on port ar.result().actualPort()); startPromise.complete(); } else { startPromise.fail(ar.cause()); } }); } }这段代码里有几个细节我想单独拎出来说。第一个是 BodyHandler你不加这个POST 请求体根本拿不到这个中间件会把请求体缓冲到内存或者临时文件里然后再交给后续路由处理器。第二个是 ctx.json()这是 RoutingContext 里自带的方法会自动设置 Content-Type 为 application/json并把对象序列化成 JSON 输出不用手动写 response.end()属于非常常用的便利方法。第三个是 startPromiseVerticle 的异步启动机制如果你初始化的资源连数据库、连接消息队列是异步的必须在真正完成后再调用 complete否则 Vert.x 会认为启动失败。启动入口类public class MainVerticle extends AbstractVerticle { Override public void start(PromiseVoid startPromise) { vertx.deployVerticle(new DatabaseVerticle()) .compose(v - vertx.deployVerticle(new HttpServerVerticle())) .onSuccess(id - { System.out.println(All verticles deployed: id); startPromise.complete(); }) .onFailure(startPromise::fail); } }这里用了 Future 链式调用DatabaseVerticle 先部署成功再部署 HttpServerVerticle避免 HTTP 服务启动后数据库还没就绪导致查询报错。注意 deployVerticle 返回的是 Future compose 能把多个异步操作串联起来这种写法的可读性和维护性都远好于嵌套回调。3.2 代码里为什么必须用异步写 Vert.x 最容易犯的错误就是我在 Spring 里就是这么写的然后在 Vert.x 里也来一套同步逻辑。我举个极端例子假设你在 HTTP 请求处理器里做了一次同步的 Thread.sleep(1000)这个 Event Loop 线程就被你占用了 1 秒。如果这时候有 100 个请求同时打进来它们会排队挨个等待每个请求都延迟 1 秒以上正常情况下一秒能处理几万请求的服务现在只能处理几个。异步代码的本质是发起一个耗时操作比如写数据库之后当前线程立刻返回事件循环等数据库结果返回后再继续执行回调。这样同一个线程可以同时管理成千上万个 IO 任务而不是为每个请求单独分配线程。Java 的传统线程模型里线程切换和内存占用是很大的开销而 Vert.x 通过事件循环把这种开销压缩到了极致。你如果之前写过 Node.js会发现 Vert.x 的模型和 Node 非常像。区别在于 Vert.x 可以利用多核 CPU 启动多个 Event Loop 线程而 Node.js 默认是单线程。这也是 Vert.x 在 Java 生态里比较有竞争力的原因之一。3.3 一个完整的 POST 接口示例光有 GET 太单薄我们加一个 POST 接口来演示请求体解析和参数校验。这里模拟保存用户信息先把数据打到控制台后面再接数据库router.post(/api/users).handler(ctx - { JsonObject body ctx.body().asJsonObject(); if (body null || !body.containsKey(name) || !body.containsKey(age)) { ctx.response() .setStatusCode(400) .json(new JsonObject().put(error, name and age are required)); return; } String name body.getString(name); int age body.getInteger(age); // 模拟异步保存后续替换为数据库操作 vertx.setTimer(100, id - { ctx.json(new JsonObject() .put(id, 1) .put(name, name) .put(age, age) .put(status, created)); }); });注意 BodyHandler 必须注册在路由之前否则 ctx.body() 会是 null。实际项目中我还会在 BodyHandler 后面加一个限制请求体大小的设置防止用户传超大 JSON 把内存撑爆默认是 -1 也就是不限制这个在生产环境一定要显式设置。BodyHandler bodyHandler BodyHandler.create() .setBodyLimit(1024 * 1024); // 1MB router.route().handler(bodyHandler);4. Event Bus让模块之间通信不再纠缠4.1 Event Bus 三种模式如果你只是用 Vert.x 写接口不碰 Event Bus那等于只用了一半功力。Event Bus 是 Vert.x 的神经系统它允许不同 Verticle、不同模块、甚至不同 JVM 实例上的代码通过消息进行通信解耦能力非常强。三种模式分别是点对点Point-to-Point发送方把消息发给单个消费者如果有多个消费者则负载均衡发布订阅Publish-Subscribe消息发给所有订阅者请求-应答Request-Reply类似 RPC发送时带回复地址消费者处理完可以回传结果。实际开发中我用得最多的就是请求-应答模式。比如 HTTP 层收到请求后通过 Event Bus 把数据发给业务 Verticle业务 Verticle 处理完把结果回传HTTP 层再响应客户端。这样 HTTP 层完全不关心数据怎么来的、存哪里业务层也不关心 HTTP 协议细节两边只依赖一个消息地址。4.2 请求-应答模式代码演示还是用保存用户的例子。把 UserServiceVerticle 单独拆出来订阅 user.save 地址public class UserServiceVerticle extends AbstractVerticle { Override public void start(PromiseVoid startPromise) { vertx.eventBus().consumer(user.save, message - { JsonObject user (JsonObject) message.body(); int saveResult saveToDatabase(user); if (saveResult 0) { message.reply(new JsonObject() .put(code, 0) .put(message, ok) .put(data, new JsonObject() .put(id, saveResult) .put(name, user.getString(name)))); } else { message.fail(500, save failed); } }); startPromise.complete(); } }在 HTTP 层调用时用 request 方法router.post(/api/users).handler(ctx - { JsonObject body ctx.body().asJsonObject(); if (body null || !body.containsKey(name)) { ctx.response().setStatusCode(400).json(new JsonObject().put(error, name is required)); return; } vertx.eventBus().request(user.save, body, reply - { if (reply.succeeded()) { ctx.json(reply.result().body()); } else { ctx.response().setStatusCode(500).json(new JsonObject() .put(error, reply.cause().getMessage())); } }); });这个模式的好处非常明显你的 HTTP 层处理接口时只是发了个消息它不需要知道是谁在处理也不需要等处理完当然 request 模式会异步等结果这样当你把 user.save 的消费者从单机换成集群或者把消费者从 Verticle 换成另一个服务HTTP 层代码一行都不用改。Event Bus 支持集群模式通过 Hazelcast 或 Infinispan 做集群管理器节点之间自动广播订阅关系这是很多分布式框架做不到的透明解耦。4.3 异步回调的痛点与 Handler 封装Vert.x 的普通回调写法容易产生回调地狱尤其业务逻辑一长嵌套三四层就非常痛苦。你大概见过这种代码service.a(param, res1 - { if (res1.succeeded()) { service.b(res1.result(), res2 - { if (res2.succeeded()) { service.c(res2.result(), res3 - { // ... }); } }); } });这种代码的问题不只是丑关键是错误处理非常容易遗漏一旦中间某一步失败后面的代码根本没机会执行。我的建议是优先用 Future API它把异步操作变成可组合的对象配合 compose 和 map 能写出几乎和同步代码一样流畅的逻辑流。如果你用的是 Vert.x 4.x可以试试它内置的 Fiber 支持通过引入 vertx-lang-java 的协程模块写代码时完全用同步风格底层自动帮你切线程这个体验是最舒服的但需要额外学习协程的概念。5. 连接 MySQL异步数据库访问5.1 引入依赖并配置连接池Vert.x 提供了完整的异步数据库客户端我们拿 MySQL 举例。首先在 pom.xml 加入依赖dependency groupIdio.vertx/groupId artifactIdvertx-mysql-client/artifactId version${vertx.version}/version /dependency这个客户端底层使用 Netty 实现 MySQL 协议完全异步不依赖 JDBC。这意味着它不会像 JDBC 那样在调用时阻塞线程也不吃连接池的线程资源。配置连接池的方式如下import io.vertx.mysqlclient.MySQLClient; import io.vertx.mysqlclient.MySQLConnectOptions; import io.vertx.mysqlclient.MySQLPool; import io.vertx.sqlclient.PoolOptions; MySQLConnectOptions connectOptions new MySQLConnectOptions() .setPort(3306) .setHost(config().getString(mysql.host, 127.0.0.1)) .setDatabase(config().getString(mysql.database, test)) .setUser(config().getString(mysql.user, root)) .setPassword(config().getString(mysql.password, password)) .setCharset(utf8mb4); PoolOptions poolOptions new PoolOptions() .setMaxSize(20) .setMaxWaitQueueSize(100); MySQLPool pool MySQLPool.pool(vertx, connectOptions, poolOptions);连接池大小设置是门学问。Vert.x 的异步连接池和传统 DBCP/C3P0 不太一样因为连接请求本身是异步的不会阻塞线程等待所以 pool size 可以设置得更大一些但也不能盲目调大。MySQL 服务端默认 max_connections 是 151如果你的应用实例比较多每个实例 20 个连接加起来很容易打满。我自己一般先按实例数乘以 10 估算然后通过压测微调。5.2 异步查询的几种写法第一种直接查询返回 JsonObject 列表pool.query(SELECT id, name, age FROM users) .execute() .onSuccess(rows - { ListJsonObject users new ArrayList(); for (Row row : rows) { users.add(new JsonObject() .put(id, row.getInteger(id)) .put(name, row.getString(name)) .put(age, row.getInteger(age))); } // 输出或返回给调用方 }) .onFailure(err - { System.err.println(query failed: err.getMessage()); });第二种带参数查询使用 PreparedStatement 方式防止 SQL 注入pool.preparedQuery(SELECT * FROM users WHERE age ?) .execute(Tuple.of(18)) .onSuccess(rows - { // 处理结果 }) .onFailure(err - { // 处理错误 });这里的 Tuple 是 Vert.x SQL 客户端提供的参数封装。注意 row.getInteger(id) 是按下表映射因为 MySQL 的 int 类型会映射成 Integer。如果是 bigint那要用 getLong。很多坑都是从数据类型映射开始的建议你先打印一行 rows看下 Vert.x 自动推断的类型是不是和你预期一致再往下做业务逻辑。5.3 把 DAO 封装成一个独立的 Service为了不让业务代码乱成一锅粥我习惯把数据库操作封装成一个 UserService 类。但注意它不是 Spring 里的单例 Bean而是通过构造方法传入 pool 和 vertx这样可以在数据访问层做更精细的控制public class UserService { private final MySQLPool pool; public UserService(MySQLPool pool) { this.pool pool; } public FutureJsonObject getUserById(Integer id) { PromiseJsonObject promise Promise.promise(); pool.preparedQuery(SELECT id, name, age FROM users WHERE id ?) .execute(Tuple.of(id)) .onSuccess(rows - { if (rows.rowCount() 0) { promise.fail(new RuntimeException(user not found, id id)); } else { Row row rows.iterator().next(); promise.complete(new JsonObject() .put(id, row.getInteger(id)) .put(name, row.getString(name)) .put(age, row.getInteger(age))); } }) .onFailure(promise::fail); return promise.future(); } }用 Future 作为返回类型的好处有三个第一调用方可以自由选择用回调还是用 await 还是用 compose 来消费结果第二Future 本身是一个值可以被缓存、被合并、被重试第三它把错误处理统一到 future 的 onFailure 上不依赖 try-catch 跨线程传播。建议你把这个模式记下来Vert.x 4.x 大量 API 都是 Future 返回。在 Verticle 里初始化 UserServicepublic class DatabaseVerticle extends AbstractVerticle { private MySQLPool pool; private UserService userService; Override public void start(PromiseVoid startPromise) { // 省略连接配置代码 pool MySQLPool.pool(vertx, connectOptions, poolOptions); userService new UserService(pool); vertx.eventBus().consumer(user.get, message - { Integer userId ((JsonObject) message.body()).getInteger(id); userService.getUserById(userId).onComplete(ar - { if (ar.succeeded()) { message.reply(ar.result()); } else { message.fail(500, ar.cause().getMessage()); } }); }); startPromise.complete(); } }这样整个链路就是HTTP 请求 → Event Bus 消息 → UserService 异步查询 → 返回结果 → HTTP 响应。每一层之间完全解耦你可以在不修改调用方的情况下替换 UserService 的实现这对单元测试也非常友好。6. 常见问题与排查技巧实录6.1 Event Loop 被阻塞压测 QPS 惨不忍睹这个问题出现的频率最高。表现是服务线上跑着刚开始正常某次请求一来整个服务卡住CPU 占用率却不高所有的请求都像排队一样被堵住。用 jstack 查看线程栈会看到多个线程卡在某个业务方法的同步调用上。排查思路先检查代码里有没有 Thread.sleep、循环等待某个锁、同步的 JDBC 或者 HTTP 客户端调用特别注意第三方 SDK 的默认实现很多 SDK 底层是同步的。修复方式是要么换成异步客户端要么用 vertx.executeBlocking 把同步操作丢到 Worker 线程池。千万别直接在 Event Loop 里硬调同步代码这不是优化能解决的是架构上的错误。6.2 乱码问题中文显示成问号Vert.x 4.x 里 JSON 处理默认按 UTF-8 编解码一般不会乱码。真正容易出问题的是 MySQL 连接字符集。如果你创建表的时候用了 latin1或者连接参数没加 utf8mb4中文数据就会变成问号。我的排查路径是先从接口返回看是否乱码如果接口返回正常但数据库字段乱码那就是数据库字符集的问题如果接口返回就乱码那看 HTTP 响应头有没有正确设置 Content-Type。Vert.x 的 ctx.json() 会自动处理如果你用了 ctx.response().end(string)记得手动设置ctx.response().putHeader(Content-Type, application/json; charsetutf-8).end(jsonString);6.3 回调用多了代码无法维护怎么办回调嵌套超过三层我就建议重构了。常见解法按优先级排第一选择是使用 Future 链式调用把每一步拆成返回 Future 的方法然后 compose 串联第二选择是引入 RxJava 3 或 MutinyVert.x 官方对这两套 API 都有集成适合复杂的数据流处理第三选择是用协程Fibervertx-lang-java 提供了 Kotlin 风格的 suspend 支持但需要引入额外的字节码增强团队需要学习成本。我自己的经验是大部分业务场景 Future 链式调用就够了RxJava 适合做并发合并、窗口操作这类复杂场景。别一上来就上重型响应式框架简单问题用简单方案解决才是工程化的正路。6.4 快速定位问题的日志技巧Vert.x 的日志默认是 JULjava.util.logging格式不太好看。建议接上 SLF4J 加 Logback配置很简单加两个依赖再加一个 logback.xml 就行。我在 logback.xml 里会专门把 io.vertx 的日志级别调到 INFO 或 WARN把业务包的日志级别设为 DEBUG这样既能看清业务日志又不会被框架内部的握手、重连日志刷屏。还有Vert.x 的异常如果没人接会走到 Vertx 实例的异常处理器你可以在创建 Vertx 时设置Vertx vertx Vertx.vertx(new VertxOptions() .setBlockedThreadCheckInterval(5000) .setWarningExceptionTime(5000));这样任何 Event Loop 线程发生阻塞超过 5 秒控制台就会打出 WARNING 日志提醒你这是诊断阻塞问题的利器。6.5 连接池耗尽导致请求超时异步连接池因为不阻塞线程池耗尽的表现为请求一直 pending不会直接报错。如果 MySQL 连接数上限设置小了高并发下新请求的取连接操作会进入 MaxWaitQueueSize如果队列也满了就会抛出 PoolException。出现这个问题的第一件事别急着调大 maxSize先看是查询慢导致连接占用时间长还是应用连接泄漏没释放。用 show processlist 看看 MySQL 侧有没有大量的 sleep 连接如果是泄漏多半是某个分支没有正确 close 连接或没有释放 RowSet。再检查代码里有没有用到 pool 的同时又手动创建了新的客户端连接这种隐性连接最容易被忽略。7. 几个让我少走弯路的实践习惯7.1 用本地 Docker 跑 MySQL别用本机安装开发 Vert.x 时不建议把 MySQL 直接装在本机上。我推荐用 Docker 起一个临时 MySQL 实例端口映射到 3306数据库随便造坏了直接删容器重建不用折腾清理残留文件。一条命令搞定docker run --name vertx-mysql -e MYSQL_ROOT_PASSWORDpassword -e MYSQL_DATABASEtest -p 3306:3306 -d mysql:87.2 压测工具选对结果才有参考价值Vert.x 写出来的服务性能好不好得靠压测说话。别用浏览器刷新当压测至少用 wrk 或者 ab。我经常用 wrk它对 Event Loop 模型的异步服务压测效果比较真实wrk -t4 -c200 -d30s http://localhost:8080/api/hello注意 wrk 里的线程数最好小于等于 CPU 核心数连接数从 50 开始逐步加观察延迟分布而不是只看 QPS。Vert.x 的容量规划必须关注 p99 延迟因为异步服务的平均延迟往往很好看但 p99 一旦恶化说明 Event Loop 已经在过载边缘了。7.3 小步提交接口先跑通再优化最后一条经验Vert.x 入门的最大障碍是想太多。很多初学者一上来就想设计一个完美的响应式架构结果被各种概念折磨得放弃。我的建议是先把最简单的 HTTP 接口跑通再加数据库再加 Event Bus每一步都小步快跑验证跑通了再优化。代码能跑就是 0 到 1优化是 1 到 1000 到 1 这个阶段不需要完美设计只需要让自己建立信心。接触 Vert.x 这几年我最喜欢的还是它那种框架很少替你做决定的风格。它不像 Spring Boot 那样给你安排好一切而是提供一堆可靠的积木让你自己组织逻辑。这种自由度对新手来说可能有点不知所措但只要你按照上面这套路径跑一遍把一个带数据库的小服务完整写出来你就会发现异步编程其实没有想象中那么可怕。后续想深入的话可以从 WebSocket、集群部署、灰度发布、实时数据推送这几个方向继续扩展Vert.x 在实时通信这块的优势非常明显。这篇就到这里有问题评论区聊。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →