Vert.x 与 Node.js 都有哪些区别
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Vert.x 与 Node.js 都有哪些区别相关的知识,希望对你有一定的参考价值。
Vert.x是一个用于下一代异步、可伸缩、并发应用的框架,旨在为JVM提供一个Node.js的替代方案。开发者可以通过它使用javascript、Ruby、Groovy、Java、甚至是混合语言来编写应用。在内部,一个vert.x实例会管理着一个小的线程集合,每个线程针对服务器上的一个可用内核。基本上每个线程都实现了一个事件循环。当部署一个vert.x应用实例(又叫做verticle)时,服务器会选择一个事件循环分配给该实例。接下来针对该实例的任务都会通过该线程进行分配。由于在某一时刻可能会有成千上万个verticle在运行,因此在同一时刻会将单个事件循环指定给多个verticle。
Verticle可与运行在相同或不同vert.x实例中的其他verticle进行通信,这是通过消息事件总线实现的,它类似于Erlang的actor模型。消息传递旨在让系统能够在多个可用核心上进行扩展而无需以多线程的方式来执行verticle代码。
事件总线是分布式的,并不只会跨越服务器,还会渗透进客户端的JavaScript以处理“实时”的Web应用。
除了并发与消息传递外,vert.x还具有如下特性:
TCP/SSL服务器与客户端
HTTP/HTTPS服务器与客户端
WebSockets/SockJS支持
InfoQ有幸采访到了VMWare的高级工程师Tim Fox以了解vert.x:
InfoQ:能否从架构上介绍一下vert.x及其构建方式?
Tim:vert.x的核心是用Java编写的,接下来我们为每一种支持的JVM语言编写了一个薄薄的API层,这样每种语言都有一个适合于该语言的API了。我们并没有向这些语言直接公开Java API。这意味着Ruby用户会通过Ruby的方式编写代码,JS用户会通过JS的方式编写代码。
InfoQ:能否描述一下在vert.x上典型的开发流程么,特别是与开发者使用Node.js的体验进行一下对比?
我觉得这与node.js是非常类似的。实际的工作流程取决于你是在本地还是云中运行应用。但这并非vert.x所特有的。
InfoQ:就调试、监控与运维来看,在JVM与Node.js上运行实时应用有何差别?
我想说监控与运维实际上与部署vert.x的环境之间的关系更为密切而非vert.x本身。比如说,如果将vert.x部署到云中,那么云提供商可能就会为你提供监控。顺便说一下,社区成员目前已经在OpenShift与Heroku上运行了Vert.x。我们希望不久之后CloudFoundry支持就会到来。
InfoQ:vert.x与Node.js有什么基准比较么?
我们尚未发布任何的官方基准。但我自己已经完成了一些,在我所做的测试中,vert.x的性能与可伸缩性都远远超越了node.js。我希望在不久之后能够发布一些基准。
InfoQ:vert.x与Netty相比如何呢?
Netty是个很棒的底层IO库。Vert.x实际上使用了Netty。但vert.x是个用于编写异步应用的完整平台。Vert.x还提供了一个组件模型、文件IO及各种Netty所没有的东西。我要说的是,在JVM世界中,Vert.x是更类似于Akka(也使用了Netty)之类的完整框架。 参考技术A 刚刚看了一下vert.x,觉得其思路还是很不错的。主要是提供了多语言包装进入一个异步eventbus来提供新式的actor模型体系的装置。这一动作的意义是重大的,可以让众多传统思维体系的程序员和开发进入异步的世界。
但是异步的模型对于一般人的挑战还是较大,写vert.x时还是用js和q这样的promise库比较方便。而总线上传输的还是json。
所以,如果你个人或者公司在java上面的已有投资已经非常大,那么采用vert.x可以让你进入异步的世界,用上netty的高性能,并且拥有较好的总线传递机制和actor模型,不过你还是要面对call ba c k的问题。
如果你不是上面的情况,那么就投入no d j e s的怀抱吧。node生态和运用现在已经相当成熟了。而如果不喜欢callback,新的generator模型也已出来。 参考技术B 刚刚看了一下vert.x,觉得其思路还是很不错的。主要是提供了多语言包装进入一个异步eventbus来提供新式的actor模型体系的装置。这一动作的意义是重大的,可以让众多传统思维体系的程序员和开发进入异步的世界。
但是博主也说了,异步的模型对于一般人的挑战还是较大,写
vert.x时还是用js和q这样的promise库比较方便。而总线上传输的还是json。
所以,如果你个人或者公司在java上面的已有投资已经非常大,那么采用vert.x可以让你进入异步的世界,用上netty的高性能,并且拥有较好的总线传递机制和actor模型,不过你还是要面对call ba c k的问题。
如果你不是上面的情况,那么就投入no d j e s的怀抱吧。node生态和运用现在已经相当成熟了。而如果不喜欢callback,新的generator模型也已出来。
相信我,json以及js带给你的,不是枷锁,而是整个世界:)
Vert.x-Web的讲解和使用
Vert.x-Web
Vert.x-Web是vert.x的web构建模块。
你可以使用它构建现代的可扩展的web应用程序。
Vert.x提供了一些相当底层的方式处理HTTP请求,但是对于大多数的应用程序这些也就够用了。
Vert.x-Web基于Vert.x-Core为更容易的构建真正的web应用程序提供了丰富的功能。
Vert.x-Web的设计灵感来自Node.js和Ruby,并且成功的继承了Vert.x.2.x版本。
Vert.x-Web是强大的,灵活以及可嵌入的。你可以使用其中的部分功能或者完全抛弃它来构建自己的应用程序。还有Vert.x不是一个容器。
你可以使用Vert.x来构建经典的WEB应用程序,基于restfull的Web应用程序,实时通信的web应用程序或者任何类型的web应用程序。Vert.x完全可以胜任这些工作。创建什么类型的WEB应用程序完全是有你来决定,Vert.x仅仅提供支持而已。
Vert.x-Web是一个极好的编写RESTful服务的,但是我们不强迫你写这样的程序。
关于一些Vert.x的关键特征:
路由(基于方法、路径等)
正则表达式模式匹配的路径
从路径提取参数
内容协商
请求主体处理
身体大小限制
Cookie解析和处理
多表单上传
多文件上传
子路由器支持
会话支持——本地和集群
交叉起源资源共享支持
错误页面处理程序
基本身份验证
基于重定向的身份验证
授权处理程序
JWT基于授权
用户/角色/权限授权
标识处理
服务器端呈现的模板支持,包括支持以下模板引擎的:
Handlebars
Jade
MVEL
Thymeleaf
响应时间处理程序
静态文件服务,包括缓存逻辑和目录清单。
请求超时的支持
SockJS支持
事件总线桥
大多数的Vert.x的特性被实现为处理事件(Handler),这样你就可以自己实现它。随着时间的推移我们将加入更多特性。
下面我们将讨论这些特性
使用Vert.x-Web
如果你想要使用Vert.x可以在你的构建配置文件中添加如下依赖关系:
Maven(在你的pom.xml):
<dependency>
<groupId>maven-groupId</groupId>
<artifactId>maven-artifactId</artifactId>
<version>maven-version</version>
</dependency>
Gradle(在你的build.gradle):
compile maven-groupId:maven-artifactId:maven-version
Re-cap 基于 Vert-x核心HTTP 服务器
Vert.x-Web基于Vert.x核心的公开API.所以在你使用Vert.x-Web的时候你应该对vert.x的核心API有必要的理解。
下面我们将带你熟悉一下Vert.x的核心HTTP文档。
这里有一个使用Vert.x核心API编写的《hello world》的web服务器。在这个例子中我们没有使用Vert.x-Web的相关特性:
HttpServer server = vertx.createHttpServer();
server.requestHandler(request ->
// This handler gets called for each request that arrives on the server
HttpServerResponse response = request.response();
response.putHeader("content-type", "text/plain");
// Write to the response and end it
response.end("Hello World!");
);
server.listen(8080);
我们创建了一个HTTPServer的实例,并且设置了一个请求处理事件,当一个请求到达服务器的时候,事件将被激发并作出应答。
当请求到达服务器时,我们将设置content-type为Text/plain并且输出Hello World!的文字。这样一次请求就结束了。
最后我们将服务绑定到8080端口。
然后你可以在浏览器中访问http://localhost:8080,如果出现Hello World! 说明我们的Web服务启动成功!!
基本Vert.x-Web概念
Router 是Vert.x的核心概念之一。它的一个对象可以维护零个或多个路径。
Router获得一个HTTP请求然后找到与这个请求相匹配的路径,并将这个请求转交到这个路径上。
路径上可以关联处理事件,路径接收的请求后,触发处理事件,处理完成后结束或者转到下一个程序处理。
下面是一个简单的Router的例子:
HttpServer server = vertx.createHttpServer();
Router router = Router.router(vertx);
router.route().handler(routingContext ->
// This handler will be called for every request
HttpServerResponse response = routingContext.response();
response.putHeader("content-type", "text/plain");
// Write to the response and end it
response.end("Hello World from Vert.x-Web!");
);
server.requestHandler(router::accept).listen(8080);
这个例子是我们上一章所讲的Hello World。只不过这次使用Vert.x-Web实现。
我们先创建了一个HTTPServer,然后创建了一个Router。这次我们创建了一个简单的没有任何匹配规则的Router,这意味着它将匹配所有到达服务器的请求。
然后我们为Router创建处理程序。处理程序将响应所有到达服务器的请求。
这个Router将进入标准的RoutingContext处理程序(包含标准的HttpServerRequest和HttpServerResponse)而其他的东西则使Vert.x-Web工作更简单。
每一个请求都是一个路径,每一个路径都有相应的路径的上下文实例,这些请求将被相同的上下文实例的处理程序处理。
一旦我们设置了处理程序后,所有到达服务起的请求将被处理程序响应。
处理请求和调用下一个处理程序
当Vert.x-Web决定由一个路径的处理程序请求另一个路径的处理程序时,你只需要调用下一个路径的处理程序并将RoutingContext对象传入处理程序。
如果在当前处理程序中你不想结束请求,你还可以将请求转入到另一个相匹配的处理程序中。
你不是必须马上调用下一个处理程序。你可以为下个处理程序调用设置延时,让其在多长时间之后运行:
Route route1 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
// enable chunked responses because we will be adding data as
// we execute over other handlers. This is only required once and
// only if several handlers do output.
response.setChunked(true);
response.write("route1\\n");
// Call the next matching route after a 5 second delay
routingContext.vertx().setTimer(5000, tid -> routingContext.next());
);
Route route2 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route2\\n");
// Call the next matching route after a 5 second delay
routingContext.vertx().setTimer(5000, tid -> routingContext.next());
);
Route route3 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route3");
// Now end the response
routingContext.response().end();
);
在上面的例子中:route1 写入响应 等待5秒后 ,调用route2 , 然后在等待5秒后 route3运行 最后响应结束。
注意:所有这一切发生的时候 没有线程处于阻塞状态。
使用阻塞处理程序
有时候,你需要做一些花费时间的事情,需要处理程序循环一段时间。比如:调用遗留阻塞API或者做一些密集的计算。
使用平常的处理程序你不能做这样的事情,所以我们提供了可阻塞的处理程序。
可阻塞的处理程序看起来就像一个普通的处理程序,但是在Vert.x中它使用的是一个工作池里的线程,而不是通常使用的循环。
你可以为一个路径(route)添加一个blockingHandler。比如这样:
router.route().blockingHandler(routingContext ->
// Do something that might take some time synchronously
service.doSomethingThatBlocks();
// Now call the next handler
routingContext.next();
);
默认情况下:所有阻塞程序运行相同的上下文时,这意味着在当前处理程序执行完成之前,下一个将不被执行。如果你不关心执行顺序和不在意你的阻塞处理程序并行执行,你可以设置特殊的指令屏蔽它。
没看明白附上原文:
By default, any blocking handlers executed on the same context (e.g. the same verticle instance) are ordered - this means the next one won’t be executed until the previous one has completed. If you don’t care about orderering and don’t mind your blocking handlers executing in parallel you can set the blocking ha ndler specifying ordered as false using blockingHandler.
路经的设置
一个Route可以设置请求的URI匹配路径,在本例中,他将匹配任何相同的请求路径。
在接下来的例子中这个处理程序将会响应/some/path/路径的请求。Route将会忽略斜杠(/)所以它也将响应 /some/path 和 /some/path//:
Route route = router.route().path("/some/path/");
route.handler(routingContext ->
// This handler will be called for the following request paths:
// `/some/path`
// `/some/path/`
// `/some/path//`
//
// but not:
// `/some/path/subdir`
);
Route路经的开始
经常我们需要访问特定的开头的路径,要做到这一点,我们可以使用一个表达式。有一个更简单的方法就是在定义路径的时候使用星号(*)作为路径的结束。
如下的例子中 这个处理程序将响应所有以/some/path/开头的所有请求:
比如:/some/path/foo.html 和 /some/path/otherdir/blah.css 都匹配
Route route = router.route().path("/some/path/*");
route.handler(routingContext ->
// This handler will be called for any path that starts with
// `/some/path/`, e.g.
// `/some/path`
// `/some/path/`
// `/some/path/subdir`
// `/some/path/subdir/blah.html`
//
// but not:
// `/some/bath`
);
任何Route都可以在创建时指定路径
Route route = router.route("/some/path/*");
route.handler(routingContext ->
// This handler will be called same as previous example
);
Route捕获路径参数
我们可以使用占位符来匹配路径用来捕获路径参数。
比如这样:
Route route = router.route(HttpMethod.POST, "/catalogue/products/:productype/:productid/");
route.handler(routingContext ->
String productType = routingContext.request().getParam("producttype");
String productID = routingContext.request().getParam("productid");
// Do something with them...
);
占位符的格式是《:参数名称》。其中参数名称包括任何字母字符,数字字符或下划线。
在上面的例子中:如果post请求的地址是:/catalogue/products/tools/drill123/ 然后Route会匹配为:productType的值为 tools和productID的值为drill123.
Route使用正则表达式
Route可以使用正则表达式。
Route route = router.route().pathRegex(".*foo");
route.handler(routingContext ->
// This handler will be called for:
// /some/path/foo
// /foo
// /foo/bar/wibble/foo
// /foo/bar
// But not:
// /bar/wibble
);
另外在创建Route的时候也可以指定正则表达式:
Route route = router.routeWithRegex(".*foo");
route.handler(routingContext ->
// This handler will be called same as previous example
);
Route使用正则表达式捕获路径参数
你可以使用正则表达式捕获路径参数。这里有个例子:
Route route = router.routeWithRegex(".*foo");
// This regular expression matches paths that start with something like:
// "/foo/bar" - where the "foo" is captured into param0 and the "bar" is captured into
// param1
route.pathRegex("\\\\/([^\\\\/]+)\\\\/([^\\\\/]+)").handler(routingContext ->
String productType = routingContext.request().getParam("param0");
String productID = routingContext.request().getParam("param1");
// Do something with them...
);
在上面的例子中, 如果请求的路径是:/tools/drill123/,然后这个路径会匹配为:productType的值被设置为 tools ,productID的值会被设置为drill123.
抓取和抓取组都使用正则表达式。
捕获指定提交方法的路径
默认情况下 Route会匹配所有提交的HTTP请求,不论是get提交,还是post提交。
如果你想让Route匹配特殊的HTTP提交方式,你可以使用 method()方法:
Route route = router.route().method(HttpMethod.POST);
route.handler(routingContext ->
// This handler will be called for any POST request
);
或者你可以在创建Route的时候指定:
Route route = router.route(HttpMethod.POST, "/some/path/");
route.handler(routingContext ->
// This handler will be called for any POST request to a URI path starting with /some/path/
);
如果你想让 Route匹配特定的HTTP提交方式,你还可以使用其他的方法 比如: get() post() put()。像下面的例子那样:
router.get().handler(routingContext ->
// Will be called for any GET request
);
router.get("/some/path/").handler(routingContext ->
// Will be called for any GET request to a path
// starting with /some/path
);
router.getWithRegex(".*foo").handler(routingContext ->
// Will be called for any GET request to a path
// ending with `foo`
);
如果你想匹配多个提交方式的话,你可以多次调用method()方法:
Route route = router.route().method(HttpMethod.POST).method(HttpMethod.PUT);
route.handler(routingContext ->
// This handler will be called for any POST or PUT request
);
Route的顺序
Route的默认顺序就是你添加时的顺序。
当一个请求到达的时候,Router会遍历每个Route并且检查是否匹配,如果匹配的话对应的处理程序将被唤醒,用来处理请求。
如果处理程序随后调用next()方法的话,那么下一个匹配的处理程序将被调用处理。
这里有一个例子用来说明这件事情:
Route route1 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
// enable chunked responses because we will be adding data as
// we execute over other handlers. This is only required once and
// only if several handlers do output.
response.setChunked(true);
response.write("route1\\n");
// Now call the next matching route
routingContext.next();
);
Route route2 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route2\\n");
// Now call the next matching route
routingContext.next();
);
Route route3 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route3");
// Now end the response
routingContext.response().end();
);
上面的例子的响应是:
route1
route2
route3
这些Route按照添加的顺序来响应来自/some/path的请求。
如果你想改变默认的排序,你仅需要为Route的order()方法指定一个特殊的整数值。
Route在创建的时候被分配一个序号,并且按照这个序号的前后顺序被添加到Router。第一个的编号为0 第二个编号为1 。。。。
通过指定Route的顺序,你可以改变Route的默认顺序。如果你想让一个Route在0之前执行的话,序号可以是一个负数。
让我们改变默认的顺序,让route2在route1前运行:
Route route1 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route1\\n");
// Now call the next matching route
routingContext.next();
);
Route route2 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
// enable chunked responses because we will be adding data as
// we execute over other handlers. This is only required once and
// only if several handlers do output.
response.setChunked(true);
response.write("route2\\n");
// Now call the next matching route
routingContext.next();
);
Route route3 = router.route("/some/path/").handler(routingContext ->
HttpServerResponse response = routingContext.response();
response.write("route3");
// Now end the response
routingContext.response().end();
);
// Change the order of route2 so it runs before route1
route2.order(-1);
现在的响应内容将变成这样的:
route2
route1
route3
如果两个相匹配的Route拥有相同的序号的时候,他们的执行顺序将按照添加的顺序来执行(即默认顺序)。
如果你想要一个Route在最后执行的话,你只需要调用last()方法。
Route设置请求的MIME类型
你可以使用consumes()方法设置一个Route指定匹配的request的MIME类型。
在这种情况下,RquestBody中将包含一个Content-type用于指定请求的MiME的类型,MIME类型的值将被comsumes()设置的值匹配。
基本上。consumes定义了处理程序可以匹配处理的MIME类型。
我们可以精确的匹配MIME类型:
router.route().consumes("text/html").handler(routingContext ->
// This handler will be called for any request with
// content-type header set to `text/html`
);
也可以匹配多个MIME类型:
router.route().consumes("text/html").consumes("text/plain").handler(routingContext ->
// This handler will be called for any request with
// content-type header set to `text/html` or `text/plain`.
);
同样可以支持通配符:
router.route().consumes("text/*").handler(routingContext ->
// This handler will be called for any request with top level type `text`
// e.g. content-type header set to `text/html` or `text/plain` will both match
);
还可以匹配指定结尾的MIME类型:
router.route().consumes("*/json").handler(routingContext ->
// This handler will be called for any request with sub-type json
// e.g. content-type header set to `text/json` or `application/json` will both match
);
如果你不指定带/的类型,它会认为你使用自定义的类型。
组合使用Route匹配
你可以使用不同的方法组合我们上面讲过的Route的方法。
例如:
Route route = router.route(HttpMethod.PUT, "myapi/orders")
.consumes("application/json")
.produces("application/json");
route.handler(routingContext ->
// This would be match for any PUT method to paths starting with "myapi/orders" with a
// content-type of "application/json"
// and an accept header matching "application/json"
);
禁用和启用Route
你可以使用route的disable()方法禁用Route,一个禁用的Route在匹配的时候将会被忽略。
同样你可以使用Route的enable()方法启用它。
上下文数据
如果你想要在一次请求的两个处理程序之间传输数据的话,你可以将数据保存在RoutingContext中。
这里有一个例子,我们在一个处理程序中将一点数据放到上下文中存储并且在下一个处理程序中再次拿到它。
你可以放进上下文中任何对象,并且能从上下文中把它取出来。
请求 /some/path/other将会匹配如下两个Route。
router.get("/some/path").handler(routingContext ->
routingContext.put("foo", "bar");
routingContext.next();
);
router.get("/some/path/other").handler(routingContext ->
String bar = routingContext.get("foo");
// Do something with bar
routingContext.response().end();
);
另一种选择是,你可以通过data()方法获得RoutingContext的上下文的Map集合。
Router挂载
你可以将一个Router挂载到另一个Router下,这样就可以做到Router的重复利用了。这个挂载在别的Router下的Router就被叫为子Router。
如果你需要的话子Router下还可以挂载其他的子Router,这样你就可以有几个级别的Router了。
让我们看一个将子Router挂载到其他Router的例子:
这个子Router维护了一些简单的REST的API的处理程序,我们将它挂载到另一个Router下。在此我们就不实现这个REST API了。
这是子Router:
Router restAPI = Router.router(vertx);
restAPI.get("/products/:productID").handler(rc ->
// TODO Handle the lookup of the product....
rc.response().write(productJSON);
);
restAPI.put("/products/:productID").handler(rc ->
// TODO Add a new product...
rc.response().end();
);
restAPI.delete("/products/:productID").handler(rc ->
// TODO delete the product...
rc.response().end();
);
如果这个Router作为一个顶级的Router,那么GET/PUT/DELETE请求就会被响应。 就像/products/product1234会成功调用API。
比如说,我们有一个被另一个Router描述的网站。
Router mainRouter = Router.router(vertx);
// Handle static resources
mainRouter.route("/static/*").handler(myStaticHandler);
mainRouter.route(".*\\\\.templ").handler(myTemplateHandler);
我们可以定义一个挂载点/productsAPI用于挂载子Router到这个主Router下。
mainRouter.mountSubRouter("/productsAPI", restAPI);
这意味着REST API的可访问路径编程了/productsAPI/products/product1234.
默认的404处理程序
如果没有相匹配的Route.Vert.x将会抛出404响应。
我们可以定义自己的错误处理程序,或者提供可用的错误增强程序,如果没有私有的错误处理程序,Vert.x将会抛出404(Not Found)响应。
本翻译只为抛砖引玉,有什么不到位的地方希望大家能够海涵(小弟英语水平菜鸟一枚),有什么错误还请诸位大大不吝赐教,小弟将不胜感激。
本翻译只为抛砖引玉,有什么不到位的地方希望大家能够海涵(小弟英语水平菜鸟一枚)
以上是关于Vert.x 与 Node.js 都有哪些区别的主要内容,如果未能解决你的问题,请参考以下文章