摘要
如果你对Node.js感兴趣,Vert.x可能是你的下一个大事件:一个建立在JVM上一个类似的架构企业制度。 这一部分介绍Vert.x是通过两个动手的例子(基于Vert.x 2.0)。
当Node.js出现,许多开发者兴奋的感到可以用不寻常的方法来构建可扩展的服务器端应用程序。 而不是开始,将服务使用多线程的请求重量级的容器。Node.js是启动多个轻便单线程的服务器和流量路由到他们。
现在,类似的框架已经出现,它的服务器部署在一个JVM中,使用JVM设施来管理流量轻量级服务器进程。 本批中的开源Java项目 ,您将了解Vert.x,事件驱动的架构类似的Node.js,是建立在JVM系列还扩展它在某些重要的新途径。
Vert.x亮点
Vert.x应用程序是事件驱动,异步和单线程的。 Vert.x过程通过事件总线,这是Vert.x的事件驱动架构的内置一块通信。 结合异步处理,单线程组件和事件总线产生高度的可扩展性,并编写单线程应用对习惯于多线程并发Java的人来说是一种解脱。 可以说,Vert.x的最好的部分是其模块化的基于JVM的架构。 Vert.x应用程序可以运行在几乎所有的操作系统,并且可以使用任何支持的JVM兼容的编程语言来编写。 一个Vert.x应用可完全在单一语言编写,也可以是用不同的编程语言模块的跨界混搭。 Vert.x模块集成了Vert.x事件总线上。
在Vert.x基于事件的编程
Vert.x的基于事件的编程模型的标准和独特的功能组合。 Vert.x应用在很大程度上是通过定义事件处理程序。 不同于传统的基于事件的应用程序,但是,Vert.x应用保证不被阻塞。 而不是打开一个socket到服务器,请求资源,然后等待(阻塞)的响应,Vert.x发送到应用程序异步地响应,通过事件处理程序,并通过事件总线传递消息写入。
Vert.x的编程框架包括一些白话,这将有助于更好的了解,通过两个演示应用程序在本文的后面:
一个verticle是部署在Vert.x.单位 每个verticle包含启动它的主要手段。 一个应用程序可以是单个verticle或者可以由与彼此经由事件总线通信的多个verticles的。
Verticles在Vert.x实例中运行。 每个Vert.x实例在其自己的JVM实例运行,并且可以承载多个verticles。 一个Vert.x实例确保verticles彼此通过运行每个在其自己的类加载器分离,所以没有修改一个静态变量是另一个实例的风险。 主机可以运行一个单一的Vert.x实例或多个的。
一个Vert.x实例,保证每个verticle实例总是在同一个线程执行。 并发Vert.x 是单线程的。
在内部,Vert.x实例维护一组线程(通常为CPU核心数)是在执行一个事件循环 :检查,看看是否有工作要做,做到这一点,去睡觉。
Verticles通过使用事件总线传递消息通信。
虽然你可能会认为,共享数据和可扩展性截然相反。 Vert.x提供了一个共享的MAP和跨在同一Vert.x实例中运行verticles传递不可改变的数据共享一套设施,这时候数据是可变的唯一真正的 。
Vert.x使用相对较少的线程来创建一个事件循环和执行verticles。 但在某些情况下,需要verticle做一些要么昂贵计算,或可能阻塞,如连接到数据库。 当发生这种情况Vert.x可以让你标记verticle实例作为worker verticle 。Vert.x确保worker verticles将永远不会被同时执行,所以要他们保持在最低水平,但他们在那里帮助你,当你需要他们,在这种情况下,将由后台执行的线程池执行。
图1示出Vert.x系统包括Vert.x实例,verticles,JVM中,服务器主机,以及事件总线的体系结构。
图1. Vert.x系统的体系结构
Vert.x核心服务和模块
Vert.x功能可以分为两类:核心服务和模块核心服务是可从verticle直接调用并包括用于TCP / SSL,HTTP和网络套接字的客户端和服务器上的服务。 服务来访问Vert.x事件总线; 定时器,缓冲区,流量控制,文件系统访问,共享maps 和 sets,访问配置,SockJS服务器,以及部署和取消部署verticles。 核心服务是相当静态的,预计不会改变,因此所有其他的功能由模块提供。
Vert.x应用和资源可以很容易地打包成模块,并通过Vert.x共享的公共模块库 。 与模块的交互是通过Vert.x事件总线异步:用JSON发送模块的消息,你的申请将得到答复。 模块和集成通过服务总线之间的这种脱钩意味着模块可以在任何支持的语言编写和其它任何支持的语言使用。 所以,如果有人写在Ruby中,你想在你的Java应用程序使用这个模块,是完全可以的!
编写一个基于Java的Vert.x Web服务器
一个基本的Web服务器应用程序和消息传递系统。 首先下载vert.x 写这篇文章的是2.0.0.final。 当地解开并添加它的bin文件夹到你的PATH 。 请注意,您需要安装Java 7,如果你还没有。
如果你是一个Maven的人跟我一样,那么你可以简单以下的依赖添加到您的POM文件:
清单1. Maven的POM对Vert.x
<dependency>
<groupId> io.vertx </groupId>
<artifactId> vertx-core </artifactId>
<version> 2.0.0-final </version>
</dependency>
<dependency>
<groupId> io.vertx </groupId>
<artifactId> vertx-platform </artifactId>
<version> 2.0.0-final </version>
</dependency>
清单2显示了Server.java文件内容
清单2. Server.java
package com.geekcap.vertxexamples;
import org.vertx.java.core.Handler;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.deploy.Verticle;
public class Server extends Verticle {
public void start() {
vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
public void handle(HttpServerRequest req) {
String file = req.path().equals("/") ? "index.html" : req.path();
req.response.sendFile("webroot/" + file);
}
}).listen(8080);
}
}
前几行清单2导入所需的Vert.x类:
Handler是所有处理器的基类!
HttpServerRequest代表Vert.x.服务器端的HTTP请求 这个类的一个实例将为由服务器处理的每个请求创建,然后传递到通过您的应用程序Handler实例(你会使用已注册HttpServer )。
Verticle是在Vert.x应用程序部署的基本单位。 为了使用Vert.x,你需要扩展Verticle类和重写start()方法,这你的Verticle 入口点。
注意vertx清单2中的变量: 该Verticle类定义vertx作为受保护的成员变量(继承Verticle),它提供了访问Vert.x运行。 该vertx变量的类型的Vertx,这暴露了以下方法:
createHttpClient()创建一个HTTP / HTTPS客户端
createHttpServer()创建一个HTTP / HTTPS服务器
createNetClient()创建了一个TCP / SSL客户端
createNetServer()创建了一个TCP / SSL服务器
creatSockJSServer()创建一个包装了HTTP服务器SockJS服务器
eventBus()提供事件总线您的应用程序访问
fileSystem()提供对文件系统的应用程序访问
sharedData()提供应用程序访问共享数据对象,它可以被用来共享Verticles之间的数据
清单2中的代码创建一个新的HTTP服务器,检索其请求处理程序的参考,并将请求处理程序到新创建的HttpServerRequest处理程序。 该Handle接口定义了一个方法命名handler()并使用泛型定义实例传递给它的类定义的类型; 在这种情况下HttpServerRequest 。 该HttpServerRequest然后定义以下字段:
method 是一个String包含给定请求的方法中,如GET , POST , PUT , DELETE ,等等。
path 是一个String包含所请求的路径,如/index.html 。
query 是一个String包含查询参数,如遵循以下问号部分: ?key=value 。
response 是一个基准HttpServerResponse表示对HTTP请求的响应。
uri 为请求的完整URI。
清单2完成对通过映射一个空的请求- “ / ” -到index.html ,然后调用HttpServerResponse的sendFile()方法来告诉Vert.x流指定的文件返回给调用者。
综上所述, Server级访问Vert.x运行时,要求它创建一个新的HTTP服务器,并注册一个Handler (即期望一个HttpServerRequest变量)的HttpServer 。 在处理器的handle()方法时, Server类从位于文件系统提供文件服务webroot目录,这是相对的。
构建web服务器
让我们来构建示例应用程序,那么我们将使用Vert.x执行它。 此项目的Maven的POM文件显示清单3所示。
清单3. Maven的POM构建Web服务器
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" >
<modelVersion> 4.0.0 </modelVersion>
<groupId> com.geekcap </groupId>
<artifactId> vertx-examples </artifactId>
<version> 1.0-SNAPSHOT </version>
<packaging> jar </packaging>
<name> vertx-examples </name>
<url> http://maven.apache.org </url>
<properties>
<project.build.sourceEncoding> UTF-8 </project.build.sourceEncoding>
</properties>
<build>
<plugins>
<plugin>
<groupId> org.apache.maven.plugins </groupId>
<artifactId> maven-compiler-plugin </artifactId>
<version> 2.0.2 </version>
<configuration>
<source> 1.6 </source>
<target> 1.6 </target>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId> io.vertx </groupId>
<artifactId> vertx-core </artifactId>
<version> 2.0.0-final </version>
</dependency>
<dependency>
<groupId> io.vertx </groupId>
<artifactId> vertx-platform </artifactId>
<version> 2.0.0-final </version>
</dependency>
<dependency>
<groupId> junit </groupId>
<artifactId> junit </artifactId>
<version> 4.11 </version>
<scope> test </scope>
</dependency>
</dependencies>
</project>
两个依赖添加到POM文件是vertx-code和vertx-platform的,这是需要开发的Java应用程序Vert.x。要构建这个应用程序,请执行以下Maven命令:
mvn clean install
这将产生一个名为vertx-examples-1.0-SNAPSHOT.jar,将需要在你的CLASSPATH,以启动您的verticle。这个示例应用程序提供了在发现网络资源Web根目录相对于该应用程序的启动目录。因此,你需要创建一个webroot的目录,并建立一些资源从它服务。要启动这个verticle,执行vertx在Vert.x的应用bin目录,如下所示:
$VERTX_HOME/bin/vertx run com.geekcap.vertxexamples.Server -cp vertx-exampl
该vertx命令接受几个选项,包括我们使用它的人,运行。这是一个扩展类的只是名字Verticle和包含的start()方法和一个可选的参数集。在这种情况下,我们设置CLASSPATH使用-cp参数,并传入JAR,我们刚刚创建的文件。服务器将启动,但不输出任何内容到屏幕上,但你可以将浏览器指向的网址:HTTP://localhost:8080。
对于我的例子中,我创建了一个简单的HTML文件,该文件说:“Hello, Vert.x”,将其命名为index.html的,并把它放在我的根目录的目录。这里是我的输出:
$ curl http://localhost:8080
<html>
<head><title>Hello, Vert.x</title></head>
<body>
<p>Hello, Vert.x</p>
</body>
</html>
与Vert.x通信
Vert.x的最重要的特点之一是它的事件总线。所述Vert.x事件总线允许verticles,可能用不同的编程语言,在使用任意点对点通信彼此通信或发布/订阅消息。在本节中,你会得到怎样的功能使用这两种方法的不同verticles整合的感觉。
在我们开始之前,你为什么要使用消息在一个更传统的基于事件的编程模型?一方面,消息支持的用不同的编程语言应用程序和组件的集成。这也使松散耦合,这意味着您可以编写代码的多个任务为中心的作品,而不是单一的,复杂的程序。最后,verticles之间的异步通信提高了系统的可扩展性。异步通信允许您定义系统的容量,因为它的发展。消息可能备份您的系统负荷增加,但他们最终会被处理。Vert.x对分布式事件总线的支持也给了你启动额外verticles以处理增加的负载的选项。
为了建立一个Vert.x消息系统,则需要获得该事件总线。通过执行启动eventBus()上的方法vertx类的实例:
EventBus eb = vertx.eventBus();
一旦你连接到EventBus您可以通过以下两种方式之一发布消息:
publish()发布一个消息给使用地址发布/订阅消息,这意味着每subscriber到给定的地址将接收发布的消息。地址只是一个String,所以你要选择有意义的事,但在什么时候是这两个出版商和用户配置为使用相同的字符串。如果你熟悉Java消息系统(JMS),publish()起同样地作用,发布消息到一个topic。
send()发送消息到使用地址点对点的消息,这意味着只有一个订户将接收该消息。如果有多个用户的地址,然后Vert.x将使用循环算法发送消息。使用循环算法的优点是可扩展性:如果你没有足够的资源在一个Vert.x实例来支持你的负担,那么你可以简单地启动其他Vert.x实例,并登记他们作为听众指定的地址。
发布/订阅VS点对点通信
在发布/订阅消息模型中,发布者将消息发送到被广播到所有用户的一个话题。使用发布/事件驱动的架构订阅了点至点的消息意味着组件只对所发生事件的发布负责。发布商并不需要知道它的订阅者,以便广播到它们。图2是一个典型的Vert.x的流程图发布/订阅消息架构。
图2.发布/订阅消息
在点 - 点通信,将消息从发布者直接通过一个队列发送给消费者。点对点的消息是一个很好的选择,当你想消息消耗正好一次时,或者当两个组件要异步地相互通信。点 - 点消息架构显示在图3。
图3.点对点通信
我们将使用Vert.x在下面的章节探讨两个邮件系统。
发布/订阅消息的例子
清单4更新我原来的服务器类(清单1)在几个方面。首先,它部署了一个所谓的新verticle AuditVerticle通过调用(如清单5中定义)deployVerticle()的方法容器实例,其被定义为母体的一部分Verticle类,提供了通向容器verticle中运行;因此,它是部署新verticles适当的位置。
清单4. Server.java 点至点消息
package com.geekcap.vertxexamples;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;
public class Server extends Verticle {
public void start() {
// Create our dependent verticles
container.deployVerticle("com.geekcap.vertxexamples.AuditVerticle");
// Create an HTTP Server that serves files
vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
public void handle(HttpServerRequest req) {
Logger logger = container.logger();
if (logger.isDebugEnabled()) {
logger.debug("Received a request for resource: " + req.path());
}
logger.fatal("Where are my logs!?!?");
logger.info("Here is an info message");
// Serve up our files
String file = req.path().equals("/") ? "index.html" : req.path();
req.response().sendFile("webroot/" + file);
// Let's tell the world (via the event bus) that we received a request
EventBus eb = vertx.eventBus();
eb.publish( "com.geekcap.vertxexamples.Server.announcements", "We received a request for resource: " + req.path() );
}
}).listen(8080);
}
}
清单4执行deployVerticle()来部署AuditVerticle。所述deployVerticle()方法部署一个标准Verticle到容器上,它保持它自己的事件循环。处理传入的HTTP请求(如清单1所示)之后,清单4将消息发布到事件总线。第一,它获得通过接入到事件总线vertx实例变量的话,就执行eventBus()方法。一旦有EventBus对象将调用其发布方法,这是一个发布/订阅的时尚门户发布的消息。
消息松耦合
在过去的三年里,我以事件驱动的架构工作过,而且我发现,发布/订阅消息,有时也被称为topic,是一个伟大的方式,以松散耦合的系统。信息发布者不需要知道他们的用户,所以新用户可以在任何时候加入不影响发行人。
清单5显示了源代码AuditVerticle类。
清单5. AuditVerticle.java
package com.geekcap.vertxexamples;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;
public class AuditVerticle extends Verticle {
@Override
public void start() {
// Let's register ourselves as a listener to Server notifications
EventBus eb = vertx.eventBus();
Handler<Message> auditHandler = new Handler<Message>() {
@Override
public void handle(Message message) {
Logger logger = container.logger();
logger.info( "AuditVerticle here, someone requested resource: " + message.body() );
}
};
eb.registerHandler( "com.geekcap.vertxexamples.Server.announcements", auditHandler );
}
}
该AuditVerticle清单5的行为很像一个报告引擎:它监听来自“announcements” server类,然后写出来的那些作为信息的日志信息。如果感兴趣的东西在发生服务器类,它可以将它发布到其announcements的topic,不同的用户可以做不同的事情,比如记录的消息或在Hadoop集群中供以后分析插入。
清单5然后创建一个处理程序在线实例(创建一个匿名内部类,并分配给一个变量,而无需创建一个单独的文件中的类)记录消息。接着,注册一个处理程序的“com.geekcap.vertxexamples.Server.announcements通过调用”地址EventBus的registerHandler()方法。现在,在任何时候服务器类将消息发布到该目的地,AuditHandler的handle()方法将被调用。
点 - 点通信实例
当要仅由单个消费者或作为机构组件相互异步通信进行处理的消息的点至点的消息时使用,也可以。在这一节中,我通过创建依赖于一个worker verticle做的工作是一个新的类演示了后者,然后该worker verticle通信的结果返回给服务器2。
清单6显示了源代码Server2类。
清单6. Server2.java
package com.geekcap.vertxexamples;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.http.HttpServerRequest;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.deploy.Verticle;
import java.util.concurrent.ConcurrentMap;
public class Server2 extends Verticle {
public void start() {
// Create our dependent verticles
container.deployWorkerVerticle("com.geekcap.vertxexamples.MyWorkerVerticle");
// Start a server that handles things with point-to-point messaging
vertx.createHttpServer().requestHandler(new Handler<HttpServerRequest>() {
@Override
public void handle(final HttpServerRequest req) {
// Set a shared variable
ConcurrentMap<String, String> map = vertx.sharedData().getMap("mymap");
map.put("mykey", "myvalue");
// Let's send a message to a worker verticle and wait for it to respond
EventBus eb = vertx.eventBus();
eb.send("request.worker", req.path, new Handler<Message<String>>() {
@Override
public void handle(Message<String> message) {
Logger logger = container.getLogger();
logger.info( "Received a reply from our worker: " + message.body );
req.response.headers().put("Content-Length", Integer.toString(message.body.length()));
req.response.write(message.body);
}
});
}
}).listen(8080);
}
}
该Server2类通过部署一个工人verticle开始。worker verticles是 从在它们不包含事件外表和预期由事件总线消息来触发标准verticles不同。worker verticles由获得访问Vert.x部署容器并调用其deployWorkerVerticle()方法。
接着,Server2获得访问EventBus通过调用,再次eventBus()的方法vertx实例变量。此时的Server2调用的send()方法,它是通往在点对点方式发送消息。在这种情况下,将请求发送路径到一个命名为“request.worker ”。到的第一个参数的send()方法是目的地,第二个参数是数据发送到目的地,和一个可选的第三个参数是一个处理程序能够由消息的接收者被回调。
该MyWorkerVerticle,这是清单7所示,旨在构建指定的请求路径的响应和发送响应返回给Server2上的处理程序。该处理程序记录的响应,然后写入该响应返回给HttpServerRequest发起的动作。我们能够写回之前HttpServerRequest我们需要指定HTTP 内容长度的响应,这是我们返回的字符串只是长度。
另外两个Vert.x的功能被添加到Server2类:
日志记录:该容器变量命名的方法getLogger()提供了访问Vert.x的记录。此记录是非常相似的log4j的,并提供了方法,如debug() ,info() ,和fatal()记录在不同的日志记录级别的消息。默认情况下,日志信息将被回显到标准输出和将被写入一个文件名 为vertx.log位于TMPDIR -defined目录。
共享数据:verticles之间共享数据是通过执行sharedData()的方法来实现vertx实例,然后调用的共享数据访问方法之一。在清单4中,我们存储在数据MAP是通过调用检索的GetMap() ; 你同样可以找回共享数据的设置通过调用GETSET() 。所有在Vert.x实例verticles的访问使用相同的模式相同的共享数据,所以它是为你verticles之间共享不可变数据的一种方式。
清单7显示了源代码MyWorkerVerticle类。
清单7. MyWorkerVerticle.java
package com.geekcap.vertxexamples;
import org.vertx.java.core.Handler;
import org.vertx.java.core.eventbus.EventBus;
import org.vertx.java.core.eventbus.Message;
import org.vertx.java.core.logging.Logger;
import org.vertx.java.platform.Verticle;
import java.util.concurrent.ConcurrentMap;
public class MyWorkerVerticle extends Verticle {
@Override
public void start() {
// Register a listener
EventBus eb = vertx.eventBus();
Handler<Message> workerHandler = new Handler<Message>() {
@Override
public void handle(Message message) {
Logger logger = container.logger();
logger.info( "MyWorkerVerticle just received a request for: " + message.body() );
// Examine our shared map
ConcurrentMap<String, String> map = vertx.sharedData().getMap("mymap");
logger.info( "Shared data: " + map.get( "mykey" ) );
message.reply( "<html><head><title>Worker Response</title></head><body>Hello, from the worker verticle</body></html>" );
}
};
eb.registerHandler( "request.worker", workerHandler );
}
}
该MyWorkerVerticle类创建一个新的处理程序与实例handle()从处理消息的方法Server2的类。从清单6中的传递给一个参数召回的send()方法是一个处理程序,可以通过邮件收件人调用实例。清单7调用message.reply() ,它发送一个响应返回给始发者(在该示例中是服务器2的处理程序)。
该MyWorkerVerticle类获得访问EventBus,然后注册其处理程序以侦听发送到“request.worker”目标的消息,以完成循环。
至于功能性,MyWorkerVerticle简单地构造一个HTML文档,并返回它回到Server2类。可以通过连接到一个数据库或读取数据从另一个服务器以检索与该建立响应中的数据建立在这个例子。
而且你会发现,MyWorkerVerticle从检索共享数据vertx的sharedData()map。
结论
随着企业系统的复杂演变,融合已经成为了软件开发人员的编程最大的挑战之一。Vert.x解决一体化的几种方法复杂:首先,它是围绕一个事件总线,松散的耦合verticles同时支持多种编程语言构建的。不管代码是用Java编写的,ruby,python,或JavaScript,可以无缝通过Vert.x事件车内集成。此外,该事件总线本身支持异步消息和事件驱动架构,它产生的高可扩展性和松耦合。
本文展示Vert.x,其独特的语言,那它结合打造高度可扩展的企业级解决方案的核心组件的概述。我展示两个web服务器和写入Vert.x一个消息传送系统中,采用后者的例子来开发一种发布/订阅消息和点至点的消息的解决方案。在后者的例子中,我还证明事件记录,共享数据,以及标准和worker verticles之间的差异。虽然这篇文章介绍中,我谈到了一些,说明Vert.x,类似的Node.js,但它主要功能是建立在JVM的一个解决方案。我希望启发你,更多地了解Vert.x,它解决了类型的编程挑战。
作者信息
原文作者:Steven Haines
原文链接:http://t.cn/R56aBJc
翻译自MaxLeap团队_云服务研发成员:JOE JIA
中文翻译首发链接:https://blog.maxleap.cn/archives/908
欢迎关注微信订阅号:从移动到云端
欢迎加入我们的MaxLeap活动QQ群:555973817,我们将不定期做技术分享活动。
若有转载需要,请转发时注意自带作者信息一栏并本自媒体公号:力谱宿云,尊重原创作者及译者的劳动成果~ 谢谢配合~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。