Vert.x核心是一个JAVA应用程序接口(API)集合,我们称之为内核
Vert.x内核提供了像以下一此功能。
内核中的功能是相对低层次的,在里面你可能找不到数据访问,认证和高层次的web功能,这此功能你将在Vert.x扩展中找到
Vert.x内核是小而轻量的,你仅使用你想要用到的。它也是全部被嵌进你现存的应用中。我们不强迫你以特别的方式结构化你的应用去使用Vert.x。
你可以在任何其他Vert.x支持的编程语言中使用Vert.x内核,但是有点较酷的是,我们不强迫你直接使用JAVA API,javascript或者Ruby,毕竟不同的语言有不同的方式与语法,并且强迫在Ruby中使用java语法显得奇怪。相返,我们自动为每个语言生成语和java API相同的语言习惯。
从现在开始,我们次使用内核替代Vert.x核心模块
如果你正使用Maven或Gradle,请添加以下依赖包到你的应用中的依赖(dependencies)小节,便于访问Vert.x核心应程接口(API)
compileio.vertx:vertx-core:3.3.2
让我们开始讨计内核中不同的概念及特性。
起初有个Vert.x
在Vert.x-land,你只能做很少的事,除非你能与Vertx对象交流!
内核是Vert.x的控制中心并你可做好很多事情,包括创建客户端和服务器,获取事件总线的引用,设置定时器诸我此类的其他事情。
现在,怎样去获取一个Vertx实例呢?
如果你引入了 Vert.x,然后则可以像下面一样简单创建一个实例。
Vertx vertx = Vertx.vertx();
如果你正在使用Verticles
备注:大多数应用将不仅只需要单个Vert.x实例,而是根据需要创建多个Vert.x实例,例如,将事件总线与不同组的服务器与客户端隔离(不是很明白)
在创建Vertx对象时指定选项
当创建一个Vertx对象,如果默认值不合适,你也可以指定选项:
Vertx vertx = Vertx.vertx(newVertxOptions().setWorkerPoolSize(40));
VertxOptions对象有多个设置,允许你配置类似集群,高可用,池大小和一些其他设置。Javadoc详细说明了所有设置。
创建一个集群的Vert.x对象
如果你正在创建一个集群的Vert.x(关于更多集群事件总线的信息,参看事件中心(Event bus)小节),然后你将正常使用异步变量去创建Vertx对象。
原因是,Vertx通常花费一些时间(或许是几秒种)将群集中的不同的Vert.x实例组合在一起。在这期间,我们不想阻塞正在调用的线程,所以我们异步的给出一个结果。
你用流了吗?
在前面的便子中,你可能注意到了流式API的使用。流式API指的是多个方法调用链式连在一起。例如:
request.response().putHeader("Content-Type","text/plain").write("some text").end();
在整个Vert.x API中,这很常用。所以我们经常这样用。
类似这样的链式调用,允许你编写有点冗长的代码。当然,如果你不喜欢流式方法,我们不强迫你那样做,你可以开心的忽视它,如果你喜欢并编写像下面的代码:
HttpServerResponse response =request.response();
response.putHeader("Content-Type","text/plain");
response.write("some text");
response.end();
不要调我,我将调用你
Vert.x API主要是事件驱动。这意味着当你感兴趣的事情发在Vert.x中时,Vert.x将通事发送你的事件调用你。
一些例子事件是:
· 定时器触发
· 一些数据到达指定的Socket
· 一些数已经从磁盘读取
· 一个异常出现
· 一个HTTP服务器已经收到请求
你提通过向Vert.x APIS提供处理器(handlers)处理事件。例如每两秒接收一个定时器事件,你应该这样做:
vertx.setPeriodic(1000, id -> {
//This handler will get called every second
System.out.println("timer fired!");
});
或者接收一个HTTP请求:
server.requestHandler(request -> {
//This handler will be called every time an HTTP request is received at theserver
request.response().end("hello world!");
});
当Vert.x有一个事件要传递到你的处理器时,Vert.x将异步地调用它有些延迟。
这将使我们引入一些Vert.x的重要概念:
不要阻塞我!
伴随几乎没有异常(比如一些文件系统操作以同步结速),在Vert.x中没有API阻塞调用细程。
我如一个结果可能立既提供,它将立既返回,另外你通常将提供一个处理器用于接收随后的事件。
因为没有任何Vert.x APIs阻塞线程,这意味着使用省数量的线程可以处理大量的并发。
由于习惯阻塞API,以下情况调用线程可能阻塞:
· 从Socket中读取数据
· 向磁盘写数据
· 向接收者发消息并等待返馈
· 等一些其他情况
在以上案例中,当你的线程正在等待一个结果时,这个线程不能做任何事情,这是有效无法使用。
这意味着,如果你想使用阻塞API有大量并发,你需工大量的线程阻止你的应用变弱直到挂起。
线程的开花板是对内存需求的限制(例如线程栈)及上下文切换。
鉴于在一些现代应用中的并发层次的要求,某种阻塞的将不俱有伸缩性。
反应器及多反应器
在前面我们注意到Vert.x APIs是事件驱动的,当有可用的处理器时,Vert.x将事件传递给处理器。
大多数情况下Vert.x使用一个叫事件循环(event loop)的线程调用你的处理器。
因为在Vert.x或你的应用中没有东西阻塞,事件循环能快乐的按顺序提交到达的事件到不同的处理器。
因为没有什么可阻塞,一个事件循不在很短的时间里能潜在地处理大量的事件,例如,单一的事件循环能非常快速地处事成千上万的HTTP请求。
我们称这个为反应器模式
之前你可能听说过,例如Node.js实现了这个模式。
在一个标准的返应器实现中,有单一的事件循环线程,在一个循环中不断提交所有到达的事件到所有处理器。
单一线程的问题是,此线程在任何时候仅能运行在单一的运算核心上(指得是CPU的核),所以如果你想单一线程化的反应器应用(如Node.js应用)伸展到你的多核服务器上,你必须启动并管理多个不同的进程。
Vert.x这里的工作方式不同。与单一事件循环不同,每个Vert.x实例维护多个事件循环。默认我们选择可以使用的机器上的运算核心数目,但是这可以修改。
这意味着一个单一的Vertx进程可以延伸到多个服务器,而与Node.js不同
我们称这个模式为多反应器模式,用以与单线程反应器模式进行区分。
备注:
尽管一个Vertx实例维护着多个事件循环,一些特定的处理器将从未被并发地执行,并且在大多数情况下(伴随着工作Verticle的出错),处理器总是由相同的事件循环调用。
黄金法则-不要阻塞事件循环
我们已经知道Vert.x APIs是非阻塞并非不能阻塞事件循环,如果你阻塞了Vertx实例中的所有事件循环,你的应用程序将减弱直到挂起。
所以不要这样做!我们提醒过你。
阻塞的例子有:
· Thread.sleep();
· 等待一把锁
· 等待互拆或监控(例如,同步块)
· 执行一个长时间的数据操作并等待结果
· 执行一个复杂并花费很多时间计算
· 死循环
如果执行一些前面重要大量时间的操作阻止事件循环,然后你将立既去讨厌步骤,且等待进一步指念。
所以…什么时重要时间量。
这个时间是多长?,这取决于你的应用和你需要的并发数。
如果你单一事件循环,并且你想每秒处理一万个http请求,显然每个请的处理不能超过0.1毫秒。所以你不能阻塞比它更多的时间。
此计算不复杂,我们把这个留给读者作为一个练习。
如果你的应用不响应,可能是因为你在某个地方阻塞了事件循环。为了帮助你诊断这样的问题,如果Vert.x地现事件循环在一定时间没有返回,Vert.x自动记录报警。如果你在日志看到像这样的报警,你应该去分析。
Thread vertx-eventloop-thread-3 has beenblocked for 20458 ms
Vert.x也提供栈跟踪精确定位到阻塞出现的点。
如果你想关闭这些设置或改变设置,在创建Vertx对时通过VertxOptions对象进行设置。
运行阻塞代码
在一个完美的事界,没有战争和饥饿,所有APIs是异步地编写,它们相互连接构建整个应用。 all APIs will be written asynchronously and bunny rabbits willskip hand-in-hand with baby lambs across sunny green meadows.
但是…那远非真实世界。(你看了最近的新闻吗!)
事实是,一些,大多数开发库,特别是在jvm生态系统中有同步APIst和一些方法是阻塞的。一个恰当的例子是JDBC API,它天生的同步,并且无论多困难,Vert.x魔法都不能让它异步化。
所前讨论,你不能在事件循环中直接调用阻塞操作,因为阻止其做一些其他有用的工作。那我们怎样做呢?
通过调用executeBlocking,指定阻塞代码和异步回调的结果处理器,此处理器将在阻塞代码被执行后调用。
vertx.executeBlocking(future-> {
// Call some blocking API that takes asignificant amount of time to return
String result =someAPI.blockingMethod("hello");
future.complete(result);
}, res -> {
System.out.println("The result is:" + res.result());
});
如果executeBlocking在同一个上下文中(同一个verticle实例中)调用多次。不同的executeBlocking会被默认串行执行(一个接一个);
如果你不关心调用executeBlocking的顺序,可以批定ordered参数为false.在这种情况下,一些executeBlocking或许会在(工作池类似线程池)worker pool中并行执行。
一个替代执行阻塞代码的方法是使用worker verticle
一个Worker verticle总从工作池中取出一个线程去执行。
使用setWorkerPoolSize配置,默认的阻塞代码会在Vert.x阻塞工作池之前执行。
为了不同的目的可以创建另外的池。
WorkerExecutorexecutor = vertx.createSharedWorkerExecutor("my-worker-pool");
executor.executeBlocking(future-> {
// Call some blocking API that takes asignificant amount of time to return
String result = someAPI.blockingMethod("hello");
future.complete(result);
}, res -> {
System.out.println("The result is:" + res.result());
});
工人执行器在不在需要时,必须关闭。
executor.close();
当多个工作线程以同一个命名被创建时,他们将共享同一个池。工作线程池中的所有执行器关闭时,这个池也将销毁。
当在Verticle中创建一个执行器时,Vert.x将在Verticle御载时自动将期销毁。
工作执行器在创建时是可以配置的。
int poolSize = 10;
// 2 minutes
long maxExecuteTime = 120000;
WorkerExecutor executor =vertx.createSharedWorkerExecutor("my-worker-pool", poolSize,maxExecuteTime);
备注:当工作线程池创建时进行参数设置。
异步协调
多个异步结果的协调通过Vert.x futures完成。它支持并发组合(并行或者联合运行多个异步操作)也可以顺序组合(链式异步操作)
CompositeFuture.all用多达6个futures参数,在所有Futures执行并失败,返回一个成功的Future。
Future
httpServer.listen(httpServerFuture.completer());
Future
netServer.listen(netServerFuture.completer());
CompositeFuture.all(httpServerFuture,netServerFuture).setHandler(ar -> {
if(ar.succeeded()) {
// All servers started
}else {
// At least one server failed
}
});
操作并行运行,且被连接。处理器被添加到返回的future,此Future在组合完成时被调用。如其中之一操作失败(某个传递的future被标识为失败),结果Future也将被标识为失败。如果所有操作成功,结果future也将成功完成。
可能传弟一个Futrue列表作为替代,列表可以为空。
CompositeFuture.all(Arrays.asList(f1, f2,f3));
all组合(composition)等待直到所有Future执行完成
any组合等待第一个执行future执行完成。
CompositedFuture.any可以有多个future参数(多达6个)如果一个future成功执行,则返回。
Future
Future
CompositeFuture.any(future1,future2).setHandler(ar -> {
if(ar.succeeded()) {
// At least one is succeeded
}else {
// All failed
}
});
Future列表也可作为参数传入
CompositeFuture.any(Arrays.asList(f1, f2,f3));
All和any 正实现了并发组给,compose方法可被用于链接future(如顺序组合)
FileSystem fs = vertx.fileSystem();
Future
fs.createFile("/foo",fut1.completer());
fut1.compose(v -> {
//When the file is created (fut1), execute this:
Future
fs.writeFile("/foo", Buffer.buffer(), fut2.completer());
return fut2;
}).compose(v -> {
//When the file is written (fut2), execute this:
fs.move("/foo", "/bar", startFuture.completer());
},
// mark the start future as completed when all the chain has beencompleted,
// or mark it as failed if any step fails.
startFuture);
在这个例子中,三个操作被链接:
1,一个文件创建(fut1)
2,向文件写入一些东西(fut2)
3,移动文件(fut3)
当三步完成时,最终的Future(startFuture)被成功完成。然而如果其中任一步失败,最终的Future以失败结束。
这个例子使用了:
· compose:当并行future完成时,运行一个给定的功能并返回一个future.个返回的future完成时,将完成此组合(composition)
· compose:当并发future完成时,运行给定的处理器完成给定的下一个future.
在此第二个例子中,处理器应该完成下一个future来汇报它的状态,成功或失败。
You can use completer thatcompletes a future with the operation result or failure. It avoids having towrite the traditional: if success then complete the futureelse fail the future.