VertX学习笔记.docx
《VertX学习笔记.docx》由会员分享,可在线阅读,更多相关《VertX学习笔记.docx(27页珍藏版)》请在冰豆网上搜索。
VertX学习笔记
1.基本概念
Verticle:
Vert.x的代码执行包,它可以用JS、Ruby等多语言来编写,在同一个Vert.x实例中可以同时执行多个Verticle,一个应用可能由多个Verticle组成,他们被部署到不同的网络节点上,彼此之间通过在Vert.x的事件总线(eventbus)上交换信息来通信。
对于具体的verticle可以直接从命令行启动,但是通常会将它打包成modules。
Module:
Vert.X是由一个或多个的Module组合而成,一个模块由多个Verticle来组成,
EventLoops:
即事件循环,是由Vert.x启动的事件处理线程,也是Vert.x项目对外开放的入口,Vert.x由此接收请求事件。
一个Vert.x有一个或多个事件循环线程组成,线程最大数量为主机有效的CPU核数。
EventLoopVertical:
事件的业务处理线程,存在于EventLoop中,用于处理非阻塞短任务。
WorkerVertical:
事件的业务处理线程,用于处理长任务阻塞任务。
EventBus:
它是Vert.X的核心,在集群中容器之间的通信,各个Verticle之间的通讯都是经过EventBus来实现的,后期会推出一篇专门关于这个的文章,敬请等待。
SharedData:
它是Vert.X提供的一个简单共享Map和Set,用来解决各个Verticle之间的数据共享。
通常Tomcat会在100个并发长请求(这个请求要求做很多事长任务)下堵塞,而Vertx将长任务委托给另外一个线程来执行,从而不会堵塞当前线程,与NodeJS的原理非常类似,如下图:
事件循环的线程和长任务工作线程之间是通过事件总线EventBus通讯的,这个事件总线是一个JVM之中,Vert.x以非阻塞IO的思想来实现高性能,非阻塞IO的实现,基于EventLoopVertical和WorkerVertical的分离,在Vert.x中,EventLoop用于接收,并将短业务操作交由其内部的Vertical来处理,该模块是非阻塞的,这样可以保证请求的处理效率;阻塞任务通过Vert.x的事件机制脱离当前线程,转移到WorkerVertical中执行,并执行结果返回给EventLoopVertical。
这一过程完成的核心是EventBus,EventBus中注册了所有的事件,通过事件匹配完成事件转移和结果返回,从而将整个流程衔接起来。
如下图:
多层之间也可以通过EventBus通讯实现调用:
多个客户端或服务器端。
Vert.x启动时,会将WorkerVertical的事件处理函数加载到EventBus,当一个HTTP请求发送到Vert.x构建的应用时,EventLoop首先接收到请求,并对请求做分析、包装,然后将事件交给EventBus来处理,EventBus为此次请求事件添加一个事件ID,然后根据注册的WorkerVertical事件寻找已经注册的监听函数,若未找到则会抛弃该事件,若找到则会对处理类进行实例化,并同时使用事件ID在EventBus中注册一个返回结果处理事件,该事件为EventVertical类型。
下一步由WorkerVertical实例执行事件处理函数,事件处理函数中通常包含业务处理、数据库操作等。
WorkerVertical实例处理结束后,将返回结果和事件信息返回给EventBus,EventBus找到在其中注册的EventVertical实例,然后将返回数据交给该实例处理,EventVertical实例进一步处理数据并将结果返回给浏览器。
如下图:
2.Vert.x-Web
Vert.x-Web是vert.x的web构建模块。
你可以使用它构建现代的可扩展的web应用程序。
Vert.x提供了一些相当底层的方式处理HTTP请求,但是对于大多数的应用程序这些也就够用了。
Vert.x-Web基于Vert.x-Core为更容易的构建真正的web应用程序提供了丰富的功能。
关于一些Vert.x的关键特征:
路由(基于方法、路径等)、正则表达式模式匹配的路径、从路径提取参数、内容协商、请求主体处理、身体大小限制、Cookie解析和处理、多表单上传、多文件上传、子路由器支持、会话支持——本地和集群、交叉起源资源共享支持、错误页面处理程序、基本身份验证、基于重定向的身份验证、授权处理程序、JWT基于授权、用户/角色/权限授权、标识处理、服务器端呈现的模板支持(包括支持以下模板引擎的:
Handlebars、Jade、MVEL、Thymeleaf)、响应时间处理程序、静态文件服务,包括缓存逻辑和目录清单。
请求超时的支持、SockJS支持、事件总线桥
大多数的Vert.x的特性被实现为处理事件(Handler),这样你就可以自己实现它。
随着时间的推移我们将加入更多特性。
这里有一个使用Vert.x核心API编写的《helloworld》的web服务器:
HttpServerserver=vertx.createHttpServer();
server.requestHandler(request->{
//Thishandlergetscalledforeachrequestthatarrivesontheserver
HttpServerResponseresponse=request.response();
response.putHeader("content-type","text/plain");
//Writetotheresponseandendit
response.end("HelloWorld!
");
});
server.listen(8080);
Router是Vert.x的核心概念之一。
它的一个对象可以维护零个或多个路径,处理请求和调用下一个处理程序:
HttpServerserver=vertx.createHttpServer();
Routerrouter=Router.router(vertx);
router.route(“/”).handler(routingContext->{
//Thishandlerwillbecalledforeveryrequest
HttpServerResponseresponse=routingContext.response();
response.putHeader("content-type","text/plain");
//Writetotheresponseandendit
response.end("HelloWorldfromVert.x-Web!
");
});
Routeroute1=router.route("/some/path/").handler(routingContext->{
HttpServerResponseresponse=routingContext.response();
//enablechunkedresponsesbecausewewillbeaddingdataas
//weexecuteoverotherhandlers.Thisisonlyrequiredonceand
//onlyifseveralhandlersdooutput.
response.setChunked(true);
response.write("route1\n");
//Callthenextmatchingrouteaftera5seconddelay
routingContext.vertx().setTimer(5000,tid->routingContext.next());
});
Routeroute2=router.route("/some/path/").handler(routingContext->{
HttpServerResponseresponse=routingContext.response();
response.write("route2\n");
//Callthenextmatchingrouteaftera5seconddelay
routingContext.vertx().setTimer(5000,tid->routingContext.next());
});
Routeroute3=router.route("/some/path/").handler(routingContext->{
//Thishandlerwillbecalledforthefollowingrequestpaths:
//`/some/path`
//`/some/path/`
//`/some/path//`
//
//butnot:
//`/some/path/subdir`
HttpServerResponseresponse=routingContext.response();
response.write("route3");
//Nowendtheresponse
routingContext.response().end();
});
Routeroute=router.route().path("/some/path/*");
route.handler(routingContext->{
//Thishandlerwillbecalledforanypaththatstartswith
//`/some/path/`,e.g.
//`/some/path`
//`/some/path/`
//`/some/path/subdir`
//`/some/path/subdir/blah.html`
//
//butnot:
//`/some/bath`
});
//如果post请求的地址是:
/catalogue/products/tools/drill123/然后Route会匹配为:
productType的值为tools和productID的值为drill123.
Routeroute=router.route(HttpMethod.POST,"/catalogue/products/:
productype/:
productid/");
route.handler(routingContext->{
StringproductType=routingContext.request().getParam("producttype");
StringproductID=routingContext.request().getParam("productid");
//Dosomethingwiththem...
});
server.requestHandler(router:
:
accept).listen(8080);
使用阻塞处理程序
可以为一个路径(route)添加一个blockingHandler。
比如这样:
router.route().blockingHandler(routingContext->{
//Dosomethingthatmighttakesometimesynchronously
service.doSomethingThatBlocks();
//Nowcallthenexthandler
routingContext.next();
});
3.Vert.x中EventBus中的使用
eventbus是vert.x的神经系统。
每一个vert.x的实例都有一个单一的eventbus实例。
它是使用vertx.eventBus()方法获得的。
eventbus允许程序中的不同语言编写的模块进行通信,不论他们是相同的vert.x实例,还是不同的vert.x实例。
它甚至可以桥接浏览器中运行的Javascript通信。
eventbus可以在分布式系统中的多个服务器节点之间进行点对点通信和多个浏览器。
eventbus支持发布/订阅模式,点对点模式,和请求/响应模式。
eventbus的API是非常容易的,它主要包括注册消息处理事件,取消处理事件,发送和发布消息。
EVENTBUS的API让我们跳进eventbus的API。
获得eventbus的对象
你可以通过如下代码获得eventbus的单一对象:
EventBuseb=vertx.eventBus();
注册处理事件
使用下面这个简单方法注册一个消费处理程序:
EventBuseb=vertx.eventBus();
eb.consumer(“news.uk.sport”,message->{
System.out.println(“Ihavereceivedamessage:
”+message.body());
});
当一个消息到达你的处理事件是。
你的事件将被激活,并处理这个消息。
consumer()方法返回一个MessageConsumer的对象实例。
这个对象随后用于注销处理程序,或者用处理程序作为流。
然而您也可以使用consumer()返回MessageConsumer没有处理程序,然后单独设置处理程序。
例如:
EventBuseb=vertx.eventBus();
MessageConsumerconsumer=eb.consumer(“news.uk.sport”);consumer.handler(message->{
System.out.println(“Ihavereceivedamessage:
”+message.body());
});
当在集群事件总线上注册一个处理程序时,它可以花一些时间登记到集群的所有节点上。
如果你希望在注册完成时得到通知的话,你可以在MessageConsumer上注册一个注册完成的处理程序:
pletionHandler(res->{
if(res.succeeded()){
System.out.println(“Thehandlerregistrationhasreachedallnodes”);
}else{
System.out.println(“Registrationfailed!
”);
}
});
注销处理事件
去除处理事件,叫做注销。
如果你是集群事件总线,如果你想当这个过程完成时通知注销,你可以使用下面的方法:
consumer.unregister(res->{
if(res.succeeded()){
System.out.println(“Thehandlerun-registrationhasreachedallnodes”);
}else{
System.out.println(“Un-registrationfailed!
”);
}
});
发布消息
发布消息非常简单,只需要把它发布到指定地址即可:
eventBus.publish(“news.uk.sport”,“Yay!
Someonekickedaball”);
这一消息将被交付所有订阅news.uk.sport地址处理。
发送消息
发送消息将导致只有一个注册地址的处理程序接收到消息(多个注册地址也只有一个能收到)。
这就是点对点模式,选择处理程序的方法采用非严格循环方式。
你可用用send()方法发送一条消息。
eventBus.send(“news.uk.sport”,“Yay!
Someonekickedaball”);
未解决的指令包括在-include:
:
override/eventbus_headers.adoc[]====TheMessageobject
你的消息处理程序收到的是一个Message。
消息的body对应着是应该发送还是应该发布。
消息的headers是可用的。
回复消息
有时你发送消息后希望得到接收到消息的人的回复。
这就需要你使用请求-响应模式。
要做到这一点,在消息发送的时候,你可以指定一个回复事件。
当你接收到消息的时候,你可以通过调用reply()方法来应答。
当这一切发生的时候它会导致一个答复发送回发送方,发送方收到应答消息再做处理。
接收方:
MessageConsumerconsumer=eventBus.consumer(“news.uk.sport”);
consumer.handler(message->{
System.out.println(“Ihavereceivedamessage:
”+message.body());
message.reply(“howinteresting!
”);
});
发送方:
eventBus.send(“news.uk.sport”,“Yay!
Someonekickedaballacrossapatchofgrass”,ar->{
if(ar.succeeded()){
System.out.println("Receivedreply:
"+ar.result().body());
}
});
对应答也可以做应答。
这样你就可以在两个不同的程序中创建一个包含多个回合的对话。
发送超时
当你发送消息时和指定应答事件时你可以通过DeliveryOptions指定超时时间。
如果应答事件不少于超时时间,这个应答事件将失败。
默认的超时时间是30S。
发送失败
消息发送失败的其他原因,包括:
没有可用的事件去发送消息
接收者已经明确使用失败:
失败的消息
在所有情况下,应答事件将回复特定的失败。
未解决的指令包含在–include:
:
override/eventbus.adoc[]====ClusteredEventBus
eventbus不仅仅存在于一个单一的Vert.x实例中,在一个集群中不同的Vert.x实例也可以形成一个单一的,分布的事件总线。
集群编程
如果你创建一个Vert.x实例用于集群编程,你需要的得到一个关于集群事件总线配置
VertxOptionsoptions=newVertxOptions();
Vertx.clusteredVertx(options,res->{
if(res.succeeded()){
Vertxvertx=res.result();
EventBuseventBus=vertx.eventBus();
System.out.println(“Wenowhaveaclusteredeventbus:
”+eventBus);
}else{
System.out.println(“Failed:
”+res.cause());
}
});
你应该确保在你的类路径中实现了一个ClusterManager,例如默认的:
HazelcastClusterManager。
使用命令集群
你可以使用命令行运行集群:
vertxrunmy-verticle.js-cluster
Automaticclean-upinverticles
4.Vert.x的微服务verticle
而Vert.x的verticle本身就是很好的一种服务定义,你可以把verticle看成一个service,也可以把verticle看成一个actor。
这样你的视角会切到Actor模型里。
本文我们将讨论如何基于Vert.x实现远程调用。
Vert.xEventBus两宗罪
但Vert.x的EventBus有两点不太好的地方,导致不能原生支持面向接口的服务调用。
EventBus是Vert.x的核心,因为它的存在可以使得系统模块化解耦成为可能,同时也可以将业务水平扩展。
我们只需要定义EventBus地址然后传入Json对象就可以,所有的事情都很流畅。
但是这里有一点不太好的地方,默认传输协议走Json,而Json本身不太好定义数据类型。
如果了解Vert.x历史的同学肯定知道为什么EventBus一定要使用Json作为主要的传输对象——为了兼容其他JVM上的弱语言。
可是现实情况中80%的项目都是基于Java跑的,根本就没有考虑去兼容其他JVM上的语言。
这就造成了一种尴尬,大家都传Json对象,然后在Handler里先做一次转换,将Json对象转换成POJO,之后再做业务上的逻辑。
这里明显在Handler里做了很多与业务无关的事情,服务定位与对象转换,这些本质上应该是框架层面去解决的。
不能隐试的将Json对象转成POJO这是EventBus的一宗罪。
(Vert.x3可以直接接受POJO,但是针对一个address,只能接收一种POJO格式。
)另外当我们使用EventBus的时候不能很方便的确定方法逻辑,简单的讲我只能EventBus。
send()后面跟地址参数以及JSON对象,而这并不适合描述业务,这是EventBus的二宗罪。
举个例子,有一个业务逻辑,对用户的账户进行存款与取款这两个操作,如果用Java接口来描述就很Easy。
Java代码
interfaceAccount{
voidsaveMemory(inttotal);
voidwithdrawMemory(inttotal);
}
如果换成EventBus你会发现没办法定义行为的名称(Java的方法),只能通过eb.send("address",json)来调用目标接口。
这里的json也许得包含method的描述。
这样会显得很啰嗦,关键是对IDE不友好,重构起来不方便,严重影响团队开发流畅度。
5.Vertx-RPC介绍
介于上面的原因,我们开发了一个简单的RPC框架,它简单的封装了EventBus,使之可以包装成Java的接口。
这样客户端与服务端之间调用就像本地接口一样。
vertx-rpc其实做的非常简单,他只依赖protostuff,作为数据传输的协议,当然也可以使用JSON协议。
接着只需要定义好接口,然后在服务端实现接口,而客户端只依赖接口,单独将项目打包成jar暴露给出来就可以使用了。
我们继续上面的例子,根据接口我们会把项目分成两个Maven模块。
SPI,SPI-impl,在parent的pom.xml定义好即可。
下面我们先在impl的项目里启动好service并通过EventBus暴露服务。
Java代码
Vertxvertx=Vertx.vertx();
Stringaddress