2.4 什么才是真正的verticle?
到目前为止,您已经了解了如何编写verticles、如何部署和配置verticles,以及如何处理阻塞代码。通过在示例中使用信息日志,您已经了解了Vert.x线程模型的元素。
现在是时候回过头来仔细分析一下verticle里面是什么了,并确保你在本章结束时对verticle是如何工作的以及如何正确使用它们有了全面的了解。
2.4.1 Verticles及其environment
图 2.5 概述了一个 Verticle 和它的环境之间的关系。
一个verticle对象本质上是两个对象的组合:
- Verticle 所属的 Vert.x 实例
- 允许将事件分派给处理程序的专用上下文实例
Vert.x 实例公开了用于声明事件处理程序的核心 API。 我们已经在之前的代码示例中使用了它,其中包括 setTimer
、setPeriodic
、createHttpServer
、deployVerticle
等方法。 Vert.x 实例被多个 Verticle 共享,每个 JVM 进程一般只有一个 Vert.x 实例。
上下文实例持有对线程的访问权以执行处理程序。 事件可能源自各种来源,例如计时器、数据库驱动程序、HTTP 服务器等。 因此,它们通常由其他线程触发,例如 Netty的accepting线程或计时器线程。
用户定义的回调中的事件处理通过上下文发生。 上下文实例允许我们在 Verticle 事件循环线程上调用处理程序,从而尊重 Vert.x 线程模型。
worker verticles的情况没有太大区别,只是处理程序是在工作线程池中使用一个工作线程执行的,如图2.6 所示。它们仍然是verticles,就像它们的事件循环一样,代码可以假定是单线程访问。只是没有固定的工作线程被用于处理一个worker verticle的事件。
2.4.2 有关上下文的更多信息
可以使用 Vert.x 类中的 getOrCreateContext()
方法访问上下文对象。 虽然上下文几乎总是与verticle相关联,但可以在verticle之外创建事件循环上下文。 正如该方法的名称所暗示的那样
- 从像verticle这样的上下文线程调用
getOrCreateContext()
会返回上下文。 - 从非上下文线程调用
getOrCreateContext()
会创建一个新上下文。
清单 2.18 显示了一个创建全局 Vertx 实例的示例,并且在 JVM 进程主线程上对 getOrCreateContext
进行了两次调用。 每次调用之后都会调用 runOnContext
,它允许我们在上下文线程上运行代码块。
正如您在下一个清单中看到的,每个上下文都被分配给一个事件循环。
上下文对象支持更多操作,例如保存上下文范围的任意键/值数据和声明异常处理程序。 下面的清单显示了一个示例,其中 foo
键包含字符串 bar
,并且声明了一个异常处理程序以在事件循环线程上执行处理程序时捕获和处理异常。
package chapter2.dissecting;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class ThreadsAndContexts {
private static final Logger logger = LoggerFactory.getLogger(ThreadsAndContexts.class);
public static void main(String[] args) {
createAndRun();
dataAndExceptions();
}
private static void createAndRun() {
Vertx vertx = Vertx.vertx();
vertx.getOrCreateContext()
.runOnContext(v -> logger.info("ABC"));
vertx.getOrCreateContext()
.runOnContext(v -> logger.info("123"));
}
private static void dataAndExceptions() {
Vertx vertx = Vertx.vertx();
Context ctx = vertx.getOrCreateContext();
ctx.put("foo", "bar");
ctx.exceptionHandler(t -> {
if ("Tada".equals(t.getMessage())) {
logger.info("Got a _Tada_ exception");
} else {
logger.error("Woops", t);
}
});
ctx.runOnContext(v -> {
throw new RuntimeException("Tada");
});
ctx.runOnContext(v -> {
logger.info("foo = {}", (String) ctx.get("foo"));
});
}
}
当事件处理分布在多个类中时,上下文数据可能很有用。 否则,使用类字段要简单得多(而且速度更快!)。
当事件处理可能引发异常时,异常处理程序很重要。 默认情况下,异常仅由 Vert.x 记录,但在执行自定义操作以处理错误时,重写上下文异常处理程序很有用。
运行清单 2.20 中的代码会产生类似于清单 2.21 的输出。
2.4.3 桥接 Vert.x 和非 Vert.x 线程模型
在编写 Vert.x 应用程序时,您可能不必处理 Vert.x 上下文。 尽管如此,在一种情况下它最有意义:当您必须使用具有自己的线程模型的第三方代码,并且您希望使其与 Vert.x 一起正常工作时。
下一个清单中的代码显示了一个创建非 Vert.x 线程的示例。 通过传递从 Verticle 获得的上下文,我们能够从运行在非Vert.x线程上的代码里在事件循环中执行一些代码。
package chapter2.dissecting;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Context;
import io.vertx.core.Vertx;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.concurrent.CountDownLatch;
public class MixedThreading extends AbstractVerticle {
private final Logger logger = LoggerFactory.getLogger(MixedThreading.class);
@Override
public void start() {
Context context = vertx.getOrCreateContext(); // <1>
new Thread(() -> {
try {
run(context);
} catch (InterruptedException e) {
logger.error("Woops", e);
}
}).start(); // <2>
}
private void run(Context context) throws InterruptedException {
CountDownLatch latch = new CountDownLatch(1);
logger.info("I am in a non-Vert.x thread");
context.runOnContext(v -> { // <3>
logger.info("I am on the event-loop");
vertx.setTimer(1000, id -> {
logger.info("This is the final countdown");
latch.countDown();
});
});
logger.info("Waiting on the countdown latch...");
latch.await();
logger.info("Bye!");
}
public static void main(String[] args) {
Vertx vertx = Vertx.vertx();
vertx.deployVerticle(new MixedThreading());
}
}
<1>: 因为start在一个事件循环线程上运行,所以我们获得了该verticle的上下文。
<2>: 我们启动一个普通的Java线程。
<3>: runOnContext确保我们在verticle的事件循环线程上运行一些代码。
下面清单中的日志显示了这一点。
每当您需要将非 Vert.x 线程模型集成到您的应用程序中时,您都可以使用拥有verticle上下文并发出 runInContext
调用的方法。
💡提示: 这个例子展示了上下文的另一个重要属性: 在定义处理程序时传播上下文。实际上,使用
runOnContext
运行的代码块会在一秒钟后设置一个计时器处理程序。您可以看到,计时器处理程序在与用来定义它的上下文相同的上下文中执行。
下一章将讨论事件总线,这是Vertx应用程序中verticles可以相互通信和进行事件处理的标准方式。
总结
- Verticle 是 Vert.x 应用程序中异步事件处理的核心组件。
Event-loop verticles
处理异步 I/O 事件,并且应该没有阻塞和长时间运行的操作。Worker Verticle
可用于处理阻塞 I/O 和长时间运行的操作。- 通过使用事件循环上下文,可以将代码与 Vert.x 和非 Vert.x 线程混合。