Vert.x的核心Java API被我们称为Vert.x Core, 提供了Vert.x底层功能。对于Maven项目可以通过以下方式添加依赖:
<dependency>
<groupId>io.vertx</groupId>
<artifactId>vertx-core</artifactId>
<version>4.5.10</version>
</dependency
创建Vertx对象
使用Vert.x进行开发第一步是创建Vertx对象,它是Vert.x的控制中心,也是调用几乎所有Vert.x API的基础。通常一个应用程序只会需要一个Vert.x实例,但如果有需要也可创建多个Vert.x实例。可以使用VertxOptions对Vertx实例进行配置,包括集群、高可用、池大小等。可通过以下代码创建Vertx实例:
Vertx vertx = Vertx.vertx();
/*
Vertx vertx = Vertx.vertx(new VertxOptions()
.setWorkerPoolSize(40)
.setBlockedThreadCheckInterval(2000)
.setBlockedThreadCheckIntervalUnit(TimeUnit.MILLISECONDS));
*/
黄金法则:不要阻塞Event Loop
Vert.x的API大部分都是事件驱动的。我们只需要为感兴趣的事情编写处理器,当有事件时,Vert.x会将事件传给处理器来处理。例如,Vert.x版本的应答服务器,当NetServer有(客户端连接)事件发生,会将事件(NetSocket),传给我们编写的处理器方法(Handler.handle(NetSocket socket) {…})来处理。
Vert.x使用被称为Event Loop的线程来调用我们的处理器来处理事件。处理器方法在Event Loop线程中调用,当Event Loop线程执行的代码块中没有阻塞,就可以在事件到达时快速地分发到不同的处理器中,实现少量的线程来处理大量的并发。我们称之为Reactor模式。
虽然Vert.x的方法都是异步的,但是我们编写的处理器可能会存在阻塞代码阻塞Event Loop线程。如果Event Loop被阻塞将无法继续处理事件。需要强调的是,不要阻塞的是Event Loop,在Event Loop之外的阻塞代码并不会影响Vert.x的事件分发和调用。
我们看看如果处理器包含阻塞代码会发生什么:
import java.util.concurrent.TimeUnit;
import java.util.logging.Logger;
import io.vertx.core.Vertx;
import io.vertx.core.VertxOptions;
public class Core1 {
private static final Logger LOGGER = Logger.getLogger(Core1.class.getName());
public static void main(String[] args) {
Vertx vertx = Vertx.vertx(new VertxOptions()
.setMaxEventLoopExecuteTime(1000L) //设置Event Loop执行Handler的最长时间为1000毫秒, 如果超过会有告警日志。
.setMaxEventLoopExecuteTimeUnit(TimeUnit.MILLISECONDS));
// 包含阻塞代码的处理器
vertx.setPeriodic(1000, event -> {
try {
TimeUnit.SECONDS.sleep(4);
LOGGER.info("slow handler done!");
} catch (InterruptedException e) {
}
});
// 不包含阻塞代码的处理器
vertx.setPeriodic(1000, event -> {
LOGGER.info("fast handler done!");
});
}
}
setPeriodic是一个计时器方法,当到达指定的时间后,会将(long类型的)timer ID做为事件传递给我们的处理器。这里设置了两个计时器,间隔都是1秒,并分别为他们编写处理器,第一个是带有阻塞代码的处理器,TimeUnit.SECONDS.sleep(4)会阻塞4秒,而另一个处理器处理器没有阻塞代码。程序执行的输出如下:
2024-09-19 17:26:36 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 1668 ms, time limit is 1000 ms
2024-09-19 17:26:37 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 2675 ms, time limit is 1000 ms
2024-09-19 17:26:38 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 3682 ms, time limit is 1000 ms
2024-09-19 17:26:38 [信息] slow handler done!
2024-09-19 17:26:38 [信息] fast handler done!
2024-09-19 17:26:40 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 1686 ms, time limit is 1000 ms
2024-09-19 17:26:41 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 2692 ms, time limit is 1000 ms
2024-09-19 17:26:42 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 3702 ms, time limit is 1000 ms
2024-09-19 17:26:42 [信息] slow handler done!
2024-09-19 17:26:42 [信息] fast handler done!
2024-09-19 17:26:44 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 1713 ms, time limit is 1000 ms
2024-09-19 17:26:45 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 2718 ms, time limit is 1000 ms
2024-09-19 17:26:46 [警告] Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for 3726 ms, time limit is 1000 ms
...
在程序日志输出种可以看到告警信息: “Thread Thread[vert.x-eventloop-thread-0,5,main] has been blocked for xxxx ms, time limit is 1000 ms”,这是因为vertx-blocked-thread-checker线程检测到Event Loop被阻塞时间超过了1000ms的阈值。同时可以看到,第二个定时函数的执行被拖慢了,正常应该每1秒打印一条"fast handler done!"输出,但现在现在变成了4秒一次打印。可以想象,如果第一个处理器一直阻塞,那么第二个处理器将永远无法被执行。
这就是不能阻塞Event loop的原因。有道友会说,如果处理器就是有阻塞代码,阁下应该如何应对?Vertx提供了2种方法应对,可以参考(How to Run Blocking Code in Vert.x: https://dzone.com/articles/how-to-run-blocking-code-in-vertx):
- 让阻塞代码在线程池(worker pool)中执行,可以使用Vertx实例自带的线程池(可以通过setWorkerPoolSize设置大小),也可以额外创建一个线程池来执行。
- 使用Worker Verticles(WorkerVerticle),Worker Verticles are similar to standard verticles. The only difference is the use of a thread from the worker pool as distinct from the standard verticles.
两种本质上是相同的,都是将阻塞代码放到Event loop之外的线程池中执行,避免阻塞Event loop线程。使用Vertx实例的executeBlocking对第一个处理器进行改造:
vertx.setPeriodic(1000, event -> {
Callable<String> blockingCodeHandler = new Callable<String>() {
@Override
public String call() throws Exception {
TimeUnit.SECONDS.sleep(4);
Boolean ok = new Random().nextBoolean();
if(ok) {
return "execute blocking code done!";
} else {
throw new RuntimeException("execute blocking code failed!");
}
}
};
Handler <AsyncResult <String>> resultHandler = new Handler<>() {
@Override
public void handle(AsyncResult<String> event) {
if(event.succeeded()) {
LOGGER.info(event.result());
} else if(event.failed()) {
LOGGER.warning(event.cause().getLocalizedMessage());
}
}
};
vertx.executeBlocking(blockingCodeHandler, resultHandler);
/*
// 使用Lambda表达式写法
vertx.executeBlocking(() -> {
TimeUnit.SECONDS.sleep(4);
Boolean ok = new Random().nextBoolean();
if (ok) {
return "execute blocking code done!";
} else {
throw new RuntimeException("execute blocking code failed!");
}
}, result -> {
if (result.succeeded()) {
LOGGER.info(result.result());
} else if (result.failed()) {
LOGGER.warning(result.cause().getLocalizedMessage());
}
});
*/
});
程序执行的输出如下,Event loop线程不再被阻塞,"fast handler done!"每秒输出一次。
2024-09-20 08:39:57 [信息] fast handler done!
2024-09-20 08:39:58 [信息] fast handler done!
2024-09-20 08:39:59 [信息] fast handler done!
2024-09-20 08:40:00 [信息] fast handler done!
2024-09-20 08:40:01 [信息] fast handler done!
2024-09-20 08:40:01 [警告] execute blocking code failed!
2024-09-20 08:40:02 [信息] fast handler done!
2024-09-20 08:40:03 [信息] fast handler done!
2024-09-20 08:40:04 [信息] fast handler done!
2024-09-20 08:40:05 [信息] fast handler done!
2024-09-20 08:40:05 [信息] execute blocking code done!
2024-09-20 08:40:06 [信息] fast handler done!
2024-09-20 08:40:07 [信息] fast handler done!
2024-09-20 08:40:08 [信息] fast handler done!
...
通过arthas观察程序线程:
[arthas@25908]$ thread
Threads Total: 45, NEW: 0, RUNNABLE: 11, BLOCKED: 0, WAITING: 3, TIMED_WAITING: 6, TERMINATED: 0, Internal threads: 25
ID NAME GROUP PRIORITY STATE %CPU DELTA_TIME TIME INTERRUPTED DAEMON
23 vertx-blocked-thread-checker main 5 TIMED_WAITI 0.0 0.000 0:0.015 false true
24 Thread-2 main 5 RUNNABLE 0.0 0.000 0:0.000 false true
25 vert.x-eventloop-thread-0 main 5 RUNNABLE 0.0 0.000 0:0.046 false false
26 DestroyJavaVM main 5 RUNNABLE 0.0 0.000 0:0.890 false false
27 vert.x-worker-thread-0 main 5 TIMED_WAITI 0.0 0.000 0:0.000 false false
...
通常,每个Vertx实例会启动一个Event loop线程"vert.x-eventloop-thread-0", 同时会启动一个"vertx-blocked-thread-checker"线程来监控Event loop的执行,当调用了executeBlocking,会通过线程池(ThreadPoolExecutor)启动一个线程“vert.x-worker-thread-0”来执行我们的阻塞代码。
在这里去观察程序线程结构,是为了方便的去理解Vertx行为,而不是去研究Vertx底层,上述说法不一定准确,也没有太细究。个人认为,在初学一种技术时,在没有对整体有比较全面的了解时,就去研究非常底层的细节,钻牛角尖,不是一种明智的做法。
[arthas@25908]$ thread 27
"vert.x-worker-thread-0" Id=27 TIMED_WAITING
at java.base@11.0.21/java.lang.Thread.sleep(Native Method)
at java.base@11.0.21/java.lang.Thread.sleep(Thread.java:339)
at java.base@11.0.21/java.util.concurrent.TimeUnit.sleep(TimeUnit.java:446)
at app//learning.vertx.c2.Core1$1.call(Core1.java:32) ==> 对应源码32行: TimeUnit.SECONDS.sleep(4);
at app//learning.vertx.c2.Core1$1.call(Core1.java:1)
at app//io.vertx.core.impl.ContextImpl.lambda$executeBlocking$0(ContextImpl.java:178)
at app//io.vertx.core.impl.ContextImpl$$Lambda$57/0x0000000800180040.handle(Unknown Source)
at app//io.vertx.core.impl.ContextInternal.dispatch(ContextInternal.java:270)
at app//io.vertx.core.impl.ContextImpl.lambda$internalExecuteBlocking$2(ContextImpl.java:210)
at app//io.vertx.core.impl.ContextImpl$$Lambda$58/0x0000000800180840.run(Unknown Source)
at app//io.vertx.core.impl.TaskQueue.run(TaskQueue.java:76)
at app//io.vertx.core.impl.TaskQueue$$Lambda$54/0x0000000800155440.run(Unknown Source)
at java.base@11.0.21/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
at java.base@11.0.21/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
at app//io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
at java.base@11.0.21/java.lang.Thread.run(Thread.java:834)
Vert.x单线程(核)处理能力取决于你编写的处理器的执行速度。“如果您只有单个Event Loop,而且您希望每秒处理10000个HTTP请求, 很明显的是每一个请求处理时间不可以超过0.1毫秒,所以您不能阻塞任何过多(大于0.1毫秒)的时间。”