Vert.x,Core - 黄金法则

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):

  1. 让阻塞代码在线程池(worker pool)中执行,可以使用Vertx实例自带的线程池(可以通过setWorkerPoolSize设置大小),也可以额外创建一个线程池来执行。
  2. 使用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毫秒)的时间。”

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值