Vert.x 核心模块 特性介绍(一)

Vert.x核心是一个JAVA应用程序接口(API)集合,我们称之为内核

Vert.x内核提供了像以下一此功能。

  •  编写TCP协议的客户端与服务器。
  • 编写HTTP协议客户端与服务器,且包含WebSocket的支持
  •  事件总线
  •  共享数据-本地映射和群集分布式映射
  •  周期性和延迟执行,这里周期或延迟执行的动作
  •  数据报Socket (UDP)
  •  DNS (域名服务)客户端
  •  文件系统访问
  •   高可用性
  •  群集

 内核中的功能是相对低层次的,在里面你可能找不到数据访问,认证和高层次的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)

  • Maven(在项目pom.xml中)

 io.vertx

 vertx-core

 3.3.2

  •   Gradle(在项目build.gradle 文件中)

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 httpServerFuture =Future.future();

httpServer.listen(httpServerFuture.completer());

Future netServerFuture =Future.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 future1 =Future.future();

Future future2 =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 fut1 = Future.future();

fs.createFile("/foo",fut1.completer());

fut1.compose(v -> {

  //When the file is created (fut1), execute this:

 Future fut2 = Future.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.

你可能感兴趣的:(Vert.x,Vert.x,core,asynchro)