所有以Vert.x构建的应用程序都是异步的。Vert.x应用程序是事件驱动和非阻塞的。当感兴趣的事情发生时,您的应用程序会得到通知。让我们看一个具体的例子。Vert.x提供了创建HTTP服务器的简单方法.每次收到HTTP请求时,都会通知这个HTTP服务器:
vertx.createHttpServer()
.requestHandler(request -> {
// This handler will be called every time an HTTP
// request is received at the server
request.response().end("hello Vert.x");
})
.listen(8080);
在本例中,我们设置了一个requestHandler来接收HTTP请求(事件)并发送 hello Vert.x(反应/响应)。Handler是在事件发生时调用的函数。在我们的示例中,处理器的代码在每个传入请求中执行。注意,Handler并不返回结果。但是,Handler可以提供结果。如何提供这个结果取决于交互的类型。在最后一个片段中,它只是将结果写入HTTP的response中。Handler被链接到套接字上侦听请求。调用此HTTP端点会产生一个简单的HTTP响应:
HTTP/1.1 200 OK
Content-Length: 12
hello Vert.x
除了极少数例外,Vert.x中的所有的API都不会阻塞调用线程。如果可以立即提供结果,将会返回;否则,Handler 将用于在稍后的时间接收事件。当一个事件可以被处理,或者异步操作的结果计算完成时候,将通知Handler 。
在传统的命令式编程中,你会像下面这样写一些东西:
int res = compute(1, 2);
在这个代码中,您等待方法的结果。当切换到异步非阻塞开发模型时,您传递一个Handler,当结果准备好的时候,它会被调用:
compute(1, 2, res -> {
// Called with the result
});
在最后一个片段中,compute不再返回一个结果,所以不要等到这个结果被计算和返回。您传递一个Handler ,当结果准备好时调用它。
由于这个非阻塞开发模型,您可以使用少量线程来处理高度并发的工作负载。在大多数情况下,Vert.x 用一个名为事事件回环的线程调用您的处理程序。这个事件循环如图2-3所示。它使用事件队列,并将每个事件分派给感兴趣的处理器(即刚才讲到的Handler)。
由事件回环提出的线程模型有一个巨大的好处:它简化了并发性。因为只有一个线程,所以您总是被同一个线程调用,而且永远不会并发。然而,它也有一个非常重要的规则,你必须遵守:
不要阻塞事件回环.
—Vert.x 金科玉律
因为没有任何阻塞,事件回环可以在短时间内交付大量事件。这就是所谓的反应堆(reactor)模式("https://en.wikipedia.org/wiki/Reactor\_pattern"\)。
让我们想象一下,你打破了规则。在前面的代码片段中,请求处理器总是从相同的事件循环调用。因此,如果HTTP请求处理阻塞了而不是立即回复用户,其他请求将不会及时处理,并将排队等待线程释放。您将失去Vert.x的可伸缩性和效率优势。那么,什么是阻塞呢?第一个明显的例子是JDBC数据库访问。他们天然的就是阻塞的。长计算也被阻塞。例如,一个计算圆周率后20万位小数点的代码肯定是阻塞的。别担心-----Vert.x提供了处理阻塞代码的结构。
在标准的反应堆(reactor)实现中,有一个单独的事件回环线程,它在回环中运行,将所有到达的事件发送到所有处理程序。单线程的问题很简单:它只能一次运行在单个CPU内核上。Vert.x在这里以不同的方式工作。而不是单个事件回环,每个 Vert.x实例维护多个事件回环,称为多反应堆(multireactor)模式,如图2-4所示。
事件由不同的事件回环发送。但是,一旦一个Handler由事件回环执行了,它将始终被这个事件回环调用,从而强制执行反应器模式的并发性。如果,如图2-4所示,您有多个事件回环,它可以平衡不同CPU内核上的负载。这与我们的HTTP例子有什么关系?Vert.x注册了套接字监听器一次,并将请求发送到不同的事件回环。