Vert.x 3 核心手册之开头篇

Vert.x的核心是一组我们称之为Vert.x Core的Java API。

知识库。

Vert.x核心库提供了以下功能:

  • TCP客户端和服务器
  • HTTP客户端和服务器同时支持WebSocket
  • 事件总线
  • 共享数据-局部的map和集群下的分布式map
  • 定时和延迟的任务
  • 部署卸载Verticles
  • 数据报套接字
  • DNS客户端
  • 访问文件系统
  • 高可用
  • 本地传输
  • 集群

核心API的功能功能相对较底层以至于你可能找不到你想要的组件,例如数据库访问、权限授权或者高级Web,虽然我们核心包中不提供,但是不代表我们扩展包没有提供,
所有有此需要的开发人员可以在Vert.x ext(扩展)包中找到你所想要的组件。

Vert.x核心包很小而且很轻量级。你可以只使用你想要的部分即可。它能够很轻松的集成到你现有的项目中-我们不会强制要求你的应用结构以我们指定的方式使用。

你可以使用Vert.x核心支持的任何语言。但是这里有一个很酷的地方-我们并不强迫你直接从JavaScript或Ruby中使用Java API,毕竟,不同的语言有不同的约定和习惯用法,在Ruby开发者(例如)上强制Java习惯用法是很奇怪的。 与之不同的是,我们会自动为每一种语言生成和Java API相等的约定和习惯用法。(idiomatic)

从现在开始,我们将使用 核心(core) 来指代 Vert.x core。

如果你使用Maven或者Gradle,在你的项目描述文件中添加以下依赖便可以轻松访问Vert.x 核心API了。

  • Maven (在你的pom.xml文件中添加以下内容)
<dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-core</artifactId>
      <version>3.5.0</version>
</dependency>
  • Gradle (在你的build.gradle文件添加以下内容):
dependencies {
      compile 'io.vertx:vertx-core:3.5.0'
}

废话不多说,开车啦~

没时间解释啦,快上车

Vert.x 之千里之行始于足下

在Vert.x-land大陆你能做的不多,除非你用Vertx对象进行操作!(ps:没有Vertx对象意味着你啥也干不了,哈哈)

我差点没忍住笑出声来

Vertx对象是Vert.x的控制中心,你几乎可以用它来做任何事。(感觉有点像上帝(God))

包括创建客户端和服务器,从事件总线中获取一个引用,设置定时器,以及许多其他的事情。

你要如何获取一个Vert.x的实例呢?

如果你想要集成Vert.x并获取一个简单的实例,那么你可以像下面这么做:

Vertx vertx = Vertx.vertx();

注意 :大多数应用可能只需要一个Vert.x实例,但是如果你有需要它也可以创建多个Vert.x实例,举个栗子,服务器和客户端可以隔离成不同的事件总线或者不同的组。

当创建Vertx对象时指定配置选项

如果默认的配置选项你觉得不适用,那你可以在创建Vertx对象的时候指定配置选项:

Vertx vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(40));

VertxOptions 对象有很多设置选项供你选择,允许你配置集群、高可用、池大小和其它各种设置。了解更多配置细节请参考Java文档。

创建Vert.x集群

如果你想创建 Vert.x集群 (获取更多信息请参考 事件总线 集群章节),然后通常你会使用异步变量创建 Vert.x 对象。

这是因为在一个集群组里面创建不同的 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 会通过发送事件调用你。

下面举了一些事件的栗子:

  • 定时器触发
  • 数据到达套接字
  • 从磁盘读取数据完成
  • 发生异常
  • HTTP服务器接收了一个请求

你可以通过提供一个处理器给 Vert.x 的API来处理事件。下面举个栗子来说明接收一个定时器事件每秒打印输出:

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 the server
    request.response().end("Hello,World!");
});

当 Vert.x 传递一个事件给你的处理程序,一段时间后,Vert.x 并会异步的调用它。

所以在 Vert.x 中我们有一些重要的概念需要注意:

不要阻塞我

除去少有的情况(例如:以"Sync"结尾的文件系统操作),Vert.x 的API都是不会阻塞调用线程的。

如果结果可以立即提供,它将会立即返回,通常情况下就需要提供一个处理器在一段时间后来接收事件。

因为 Vert.x 的API是没有阻塞的,这意味着你可以用 Vert.x 处理大量并发但仅使用少量的线程。

当下面的情况发生时调用的线程可能会被阻塞:

  • 从套接字上读取数据

  • 将数据写入磁盘

  • 发送一个消息给收信者然后等待回信

  • …​ 其它多数情况

在上述所有情况下, 当您的线程等待结果时, 它不能做任何其他事情-它实际上是无用的。

这意味着, 如果您希望使用阻塞 API 进行大量的并发, 那么您需要大量的线程来防止应用程序被停止。

线程在所需内存和其它方面都有开销(例如:它们的栈内存数据)和上下文切换。

对于许多现代应用程序所需的并发级别, 阻塞方法是不可扩展的。


反应堆和多反应堆

在此之前我们已经注意到 Vert.x 的API都是事件驱动的 - 当它们可用时,Vert.x 会传递事件给处理器处理。

在大多数情况下 Vert.x 使用一个事件循环线程调用你的处理器。

因为Vert.x和你的应用中不存在阻塞,所以事件循环线程可以在事件到达时持续不断地将其分发给不同的处理器。

因为没有阻塞代码,事件循环线程能够在短时间内分发大量的事件。例如,单个事件循环线程能够快速地处理成上千个HTTP请求。

我们将这个模式称之为 Reactor Pattern (反应堆模式)。

在此之前你可能听说过这个 - 例如,Node.js(实现了反应堆模式)

在一个标准的反应堆模式实现中,有一个单独的事件循环线程 (single event loop),它在一个循环中运行,处理事件到达时传递所有的事件到所有的处理器。

问题是单线程任何时候都只能运行在单核心上面,因此如果你想要单线程反应堆应用(例如:你的Node.js应用)在您的多核服务器上扩展,则必须启动并管理许多不同的进程。

Vert.x 不同之处并在这里。每个 Vartx 实例不是一个事件循环而是维护好多个事件循环。默认情况下我们选择机器可用的内核数量作为默认数量,但是这是可以覆写的。

这意味着与Node.js不同,单个 Vertx 进程可以跨服务器进行扩展。

我们将这种模式称为多反应堆模式 (Multi-Reactor Pattern),将其与单线程反应堆模式区分开来。

注意 :虽然Vertx实例维护了多个事件循环,任何特定的处理程序也不会同时执行,并且在大多数情况下(除了wokrer verticle)将始终使用完全相同的事件循环来调用。


黄金法则-不要阻塞事件循环线程

我们已经知道 Vert.x API是非阻塞同时也不会阻塞事件循环,但是如果你在你自己的处理器中阻塞了事件循环这将毫无用处。

如果你要这样做,事件循环被阻塞时它将不能处理任何事情,如果在 Vertx 实例中阻塞了所有的事件循环,那么你的应用将会完全停止!

因此不要这样搞,小伙子!在此已经警告过你啦。

咏春拳警告

举几个阻塞的栗子:

  • Thread.sleep()

  • 等待一个锁

  • 正在等待互斥或监视器(例如 synchronized 部分)

  • 做一个长时间的数据库操作,并等待一个结果

  • 需要长时间来做一个复杂的计算

  • 死循环

以上任何的一种情况都会在很长一段时间内阻塞事件循环线程(event loop),你应经立即去下一步,并等待进一步的指示。

那么……什么是相当长的时间呢?

这段时间是多久呢?这实际上取决于您的应用程序和您需要的并发量。

如果你有一个事件循环,并且你想每秒处理10000个HTTP请求,那很明显每个请求的处理时间不能超过0.1ms,所以阻塞时间不能超过这个时间。

这道数学题目又不难,就留给读者来计算吧。

如果您的应用程序没有响应,则可能是您在某处阻塞了事件循环而导致的。为了帮助您诊断这些问题,如果检测到事件循环在一段时间未返回,Vert.x 将会自动记录警告。如果您在日志中看到这样的警告,那么您应该进行代码检查了。

  线程 vertx-eventloop-thread-3 已被阻塞20458毫秒

Vert.x 也会提供堆栈跟踪来准确确定阻塞发生的位置。

如果想要关闭这些警告或设置,可以在创建Vert.x对象之前通过设置 VertxOptions 对象来更改。

运行阻塞代码

在一个完美的世界里面,不会有战争或饥饿,所有的操作都是异步的,小兔子会在阳光明媚的绿色草地上和小羊羔手拉手。

但是现实不是这样的。(你最近有看新闻吗?(ps:发生了什么?))

鬼知道我经历了什么

事实上,大多数的库都是同步操作,尤其是JVM生态系统中有许多同步API,并且许多方法都可能阻塞。一个很好的栗子就是JDBC API-它本质上是同步的,不管多么努力,Vert.x 都不可能使用魔法在上面撒盐使其变为异步。

我们不会把所有的东西都改为异步,因此我们需要为您提供一种在 Vert.x 应用程序中安全的使用 “传统” 阻塞API的方法。

正如前面所讨论的,你不能直接从事件循环中调用阻塞操作,因为这将阻塞它做其它有用的工作。那么你怎么做到这一点呢?

这是通过调用 executeBlocking 指定要执行阻塞代码以及在阻塞代码执行返回异步结果处理程序来完成的:

vertx.executeBlocking(future -> {
  // Call some blocking API that takes a significant 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 被会被串行执行(即一个接一个地执行 one by one)。

如果你不关心执行顺序,你可以调用 executeBlocking 时指定 false 作为 ordered 的参数 。在这种设置下,工作池上就可以并发执行任何 executeBlocking 了。

运行阻塞代码的另一种方法是使用 Worker Verticle

Worker Verticle 始终使用来自工作池的线程来执行的。

默认情况下,阻塞代码都在 Vert.x 工作池上执行,需要配置 setWorkerPoolSize

可以为不同的业务创建额外的线程池:

WorkerExecutor executot = vertx.createSharedWorkerExecutor("my-worker-pool");
executot.executeBlocking(future -> {
    // Call some blocking API that takes a significant amount of time to return
   String result = someAPI.blockingMethod("hello");
   future.complete(result);
}, res -> {
   System.out.println("The result is: " + res.result());
});

当worker executor不再需要的时候必须要将其关闭掉。

executor.close();

当几个工作者使用相同的名称创建线程池时,它们将会共享同一个线程池。工作者线程池关闭的时候所有工作者线程也会被关闭掉。

当在 Verticle 中创建一个执行器时,Verticle 卸载的时候它也会自动将执行器关闭掉。

Worker executor 可以在被创建时候进行配置:

int poolSize = 10;

// 2 minutes
long maxExecuteTime = 120000;

WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool", poolSize, maxExecuteTime);

注意: 这个需要在创建工作池的时候进行配置。

异步协调

Vert.x 可以实现多个异步结果future的协调。它还支持并发组合(并发运行好几个异步操作)和顺序组合(异步链操作)。

并发组合

CompositeFuture.all 接受多个future参数(最多6个);当所有的future都成功了,就返回成功的future,否则只要有一个失败就会返回失败(failed)的future:

Future<HttpServer> httpServerFuture = Future.future();
httpServerFuture.listen(httpServerFuture.completer());

Future<NetServer> netServerFuture = Future.future();
netServer.listen(netServerFuture.complete());

CompositeFuture.all(httpServerFuture, netServerFuture).setHandler(ar -> {
  if (ar.succeeded()) {
    // All servers started
  } else {
    // At least one server failed
  }
});

这个操作时同时运行的,在组合完成后,处理器 (Handler) 将会追加到返回的future上。

handler:处理器

invoke:调用

upon:在...之上

当其中的一个操作失败(被传递的future会标记为失败)同时结果future也会标记为失败。当所有的操作都成功了,结果future也会是成功完成的。

同时,你还可以传递一个List集合future(可能我空):

CompositeFuture.all(Arrays.asList(future1, future2, future3));

CompositeFuture.all 是当所有的future都成功了才返回成功,其中的一个future失败就代表着失败。(要俺们都成功了那才是成功,不然都算是失败)。CompositeFuture.any 与此不同的是只要有一个成功了,并返回成功的future,只有当所有的future都失败那就表示失败了。(也就是说只要有一个成功,那咱们就是成功的)。

CompositeFuture.any(future1, future2).setHandler(ar -> {
  if (ar.succeeded()) {
    // At least one is succeeded
  } else {
    // All failed
  }
});

同样你也可以使用一个list列表的方式:

CompositeFuture.any(Arrays.asList(f1, f2, f2));

CompositeFuture.join 是等待所有的future完成,不论成功失败(不以成功失败论英雄),可以支持多个参数(至多6个)。当所有的future都成功了才返回一个成功的future,

CompositeFuture.join 需要等待所有的future完成,无论是成功还是失败(不以成功失败论英雄)。 CompositeFuture.join 可以有好几个future参数(最多6个),当所有future成功时并返回成功的future,当所有future都完成并且但是其中一个失败,那就代表着失败:

CompositeFuture.join(future1, future2, future3).setHandler(ar -> {
  if (ar.succeeded()) {
    // All succeeded
  } else {
    // All Complete and at least one failed
  }
});

同样你也可以使用list集合的方式:

CompositeFuture.join(Arrays.asList(future1, future2, future3));

顺序组合

allany 都实现了并发组合, compose 可以使用链的方式设置组合future(因此这种方式叫顺序组合)。

FileSystem fs = vertx.fileSystem();
Future<Void> startFuture = Future.future();
 
Future<Void fut1 = Future.future();
fs.createFile("/foo", fut1.completer());

fut1.compose(v -> {
   // What the file is created (fut1), execute this:
   Future<Void> fut2 = Future.future();
   fs.writeFile("/foo", Buffer.buffer(), fut2.complter());
   return fut2;
}).compose(v -> {
    // When the file is written (fut2), execute this:
    fs.remove("/foo", "/bar", startFuture.completer());
}, 
// mark startFuture it as failed if any step fails.
startFuture);

在上面的例子中,这3个操作都是链式的:

  1. 创建文件(fut1 )
  2. 写入数据(fut2 )
  3. 删除文件(startFuture )

当这3个步骤都成功了,最终的future (startFuture)就成功了。然而,如果其中的一个步骤失败了,最终的future并也是失败的。

这个例子使用:

  • compose:当现有的future完成时,运行给定的方法会返回一个future。当返回的future完成时,它并完成了这个组合操作。

  • compose:当现有的future完成时,运行给定的处理器完成给定的 future (下一个)。

在第二种情况下, 处理程序应完成下一个future, 以报告其成功或失败。

你可以使用 completer 来完成一个future操作结果成功还是失败。它避免了传统的不得不的写操作:如果成功了则返回完成的future否则返回失败的future。

THE END
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,053评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,527评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,779评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,685评论 1 276
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,699评论 5 366
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,609评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,989评论 3 396
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,654评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,890评论 1 298
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,634评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,716评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,394评论 4 319
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,976评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,950评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,191评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 44,849评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,458评论 2 342

推荐阅读更多精彩内容