Vertx从零开始

Vertx从零开始-java core Manual

vertx:一个异步框架

许多网络库和框架依靠一个简单的线程策略:每个网络客户端在链接时被分配一个线程,这个线程处理这个客户端的请求,直到链接断开。这是Servlet以及使用java.io和java.net包编写网络代码的情况。虽然这种同步I/O线程模型具有简单易懂的优点,但是当存在太多并发链接时,它会损害可伸缩性,因为系统线程并非廉价,并且在高负载的情况下,操作系统内核在线程调度管理上花费显著的时间。在这种情况下,我们需要转移到“异步I/O”,Vert.x则为“异步I/O”提供了坚实的基础。

  • 同步与异步添加链接描述
  • 同步,可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。

创建vertx对象

所有操作都是通过调用vertx对象实现的(?)

创建vertx对象的三种方法

  • 缺省配置
Vertx vertx = Vertx.vertx();
  • 指定配置项(很多种)
Vertx vertx = Vertx.vertx(new VertxOptions().setWorkerPoolSize(40));
  • 集群模式

采用链式调用(连点方法)

连点方法
连点方法-英文

Vert.x中的所有API都不会阻塞调用线程

如果可以立即提供结果,它将立即返回,否则您需要提供一个处理器(Handler)来接收稍后回调的事件。因为Vert.x API不会阻塞线程,所以通过Vert.x您可以只使用少量的线程来处理大量的并发。

Reactor 模式和 Multi-Reactor 模式

Reactor模式

Vertx使用Event Loop 的线程来调用处理器
由于Vert.x或应用程序的代码块中没有阻塞,Event Loop 可以在事件到达时快速地分发到不同的处理器中。
由于没有阻塞,Event Loop 可在短时间内分发大量的事件。例如,一个单独的 Event Loop 可以非常迅速地处理数千个 HTTP 请求。

Multi-Reactor 模式

每个 Vertx 实例维护的是多个Event Loop 线程

即使一个 Vertx 实例维护了多个 Event Loop,任何一个特定的处理器永远不会被并发执行。(除了 Worker Verticle 以外)

黄金法则:不要阻塞Event Loop

不要手动阻塞Eventloop

这些阻塞做法包括:

  • Thead.sleep()
  • 等待一个锁
  • 等待一个互斥信号或监视器(例如同步的代码块)
  • 执行一个长时间数据库操作并等待其结果
  • 执行一个复杂的计算,占用了显著的时长
  • 在循环语句中长时间逗留

如何运行阻塞式代码

JVM生态系统中有很多同步API,这些API中许多方法都是阻塞式的。一个很好的例子就是 JDBC API,它本质上是同步的,Vert.x无法将它转换成异步API。

  • 可以通过调用 executeBlocking 方法来指定阻塞式代码的执行以及阻塞式代码执行后处理结果的异步回调。
vertx.executeBlocking(future -> {
  // 调用一些需要耗费显著执行时间返回结果的阻塞式API
  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 中并行执行。

扫描二维码关注公众号,回复: 11114448 查看本文章
  • 另外一种运行阻塞式代码的方法是使用 Worker Verticle。
    默认的阻塞式代码会在 Vert.x 的 Worker Pool 中执行,通过 setWorkerPoolSize 配置
WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool");
executor.executeBlocking(future -> {
  // 调用一些需要耗费显著执行时间返回结果的阻塞式API
  String result = someAPI.blockingMethod("hello");
  future.complete(result);
}, res -> {
  System.out.println("The result is: " + res.result());
});

在不用时关闭executor.close();

异步协调

Vert.x 中的 Future 可以用来协调多个异步操作的结果。它支持并发组合(并行执行多个异步调用)和顺序组合(依次执行异步调用)(Future即异步开发模式中的 Future/Promise 模式)

并发合并 Concurrent composition

CompositeFuture.all 方法接受多个 Future 对象作为参数(最多6个,或者传入 List)。

  • 当所有的 Future 都成功完成,该方法将返回一个 成功的 Future
  • 当任一个 Future 执行失败,则返回一个 失败的 Future
  • 具有短路特性,当一个future失败时,方法立刻返回失败
Future<HttpServer> httpServerFuture = Future.future();
httpServer.listen(httpServerFuture.completer());

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

CompositeFuture.all(httpServerFuture, netServerFuture).setHandler(ar -> {
  if (ar.succeeded()) {
    // 所有服务器启动完成
  } else {
    // 有一个服务器启动失败
  }
});

CompositeFuture.any 方法的合并会等待第一个成功执行的Future。CompositeFuture.any 方法接受多个 Future 作为参数(最多6个,或传入 List)。

  • 具有短路特性当任意一个 Future 成功得到结果,则该 Future 成功,返回成功;
  • 当所有的 Future 都执行失败,则该 Future 失败。
CompositeFuture.any(future1, future2).setHandler(ar -> {
  if (ar.succeeded()) {
    // 至少一个成功
  } else {
    // 所有的都失败
  }
});
  • join 方法的合并会等待所有的 Future 完成,无论成败。CompositeFuture.join 方法接受多个 Future作为参数(最多6个),并将结果归并成一个 Future
  • 当全部 Future 成功执行完成,得到的 Future是成功状态的;
  • 当至少一个 Future 执行失败时,得到的 Future 是失败状态的。
CompositeFuture.join(future1, future2, future3).setHandler(ar -> {
  if (ar.succeeded()) {
    // 所有都成功
  } else {
    // 至少一个失败
  }
});

顺序合并 Sequential composition

Verticle

编写 Verticle

Verticle 的实现类可以通过实现 Verticle 接口。但是通常直接从抽象类 AbstractVerticle 继承更简单。

  • 通常需要重写 start 方法。
  • 当 Vert.x 部署 Verticle 时,它的 start 方法将被调用,这个方法执行完成后 Verticle 就变成已启动状态。
  • 同样可以重写 stop 方法,当Vert.x 撤销一个 Verticle 时它会被调用,这个方法执行完成后 Verticle就变成已停止状态了。
public class MyVerticle extends AbstractVerticle {

  // Called when verticle is deployed
  // Verticle部署时调用
  public void start() {
  }

  // Optional - called when verticle is undeployed
  // 可选 - Verticle撤销时调用
  public void stop() {
  }

}

Verticle 异步启动和停止
This Future can be used to asynchronously tell Vert.x if the Verticle was deployed successfully.
Future可以用来异步地通知Vertx关于Verticles是否被成功部署

public class MyVerticle extends AbstractVerticle {

  public void start(Future<Void> startFuture) {
    // 现在部署其他的一些verticle
    vertx.deployVerticle("com.foo.OtherVerticle", res -> {
      if (res.succeeded()) {
        startFuture.complete();
      } else {
        startFuture.fail(res.cause());
      }
    });
  }
}

关于同步启动和异步启动;
如果调用start方法,启动仍然是异步的,但是不会有future对象来提示verticle是否成功被部署。

Verticle 种类

有三种不同类型的 Verticle:

  • Stardand Verticle:这是最常用的一类 Verticle —— 运行在 Event Loop 线程上。
  • Worker Verticle:这类 Verticle 会运行在 Worker Pool 中的线程上。一个实例不会被多个线程同时执行。
  • Multi-Threaded Worker Verticle:这类 Verticle 也会运行在 Worker Pool
    中的线程上。一个实例可以由多个线程同时执行(译者注:因此需要开发者自己确保线程安全)。

编程方式部署Verticle

  • 通过 Verticle 实例 来部署 Verticle
Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);

Event Bus

Verticles can communicate with each other via the Vert.x event bus.
每一个 Vert.x 实例都有一个单独的 Event Bus 实例。可以通过 Vertx 实例的 eventBus 方法来获得对应的 EventBus 实例。
EventBus用来分发消息和处理Verticle之间的通信
在这里插入图片描述

寻址

消息会被 Event Bus 发送到一个 地址(address)。
ert.x中的地址是一个简单的字符串,任意字符串都合法。当然,使用某种模式来命名仍然是明智的。如:使用点号来划分命名空间
一些合法的地址形如:europe.news.feed1、acme.games.pacman、sausages和X。

Handler

消息在处理器(Handler)中被接收。可以在某个地址上注册一个Handler来接收消息。
同一个地址可以注册许多不同的处理器,一个处理器也可以注册在多个不同的地址上。

发布/订阅消息

Event Bus支持 发布消息 功能。

消息将被发布到一个地址中,发布意味着会将信息传递给 所有 注册在该地址上的处理器。这和 发布/订阅模式 很类似。
观察者模式 vs 发布-订阅模式
在这里插入图片描述

点对点模式/请求-响应模式

消息将被发送到一个地址中,Vert.x将会把消息分发到某个(仅一个)注册在该地址上的Handler。若这个地址上有不止一个注册过的Handler,它将使用 不严格的轮询算法 选择其中一个。
当接收者收到消息并且已经被处理时,它可以选择性决定回复该消息,若选择回复则绑定的应答处理器将会被调用。当发送者收到回复消息时,它也可以回复,这个过程可以不断重复。通过这种方式可以
允许在两个不同的 Verticle 之间设置一个对话窗口
。这种消息模式被称作 请求-响应 模式。

尽力传输

Vert.x会尽它最大努力去传递消息,并且不会主动丢弃消息。这种方式称为 尽力传输(Best-effort delivery)。

消息类型

Vert.x 默认允许任何基本/简单类型、String 或 Buffer 作为消息发送。不过在 Vert.x 中的通常做法是使用 JSON 格式来发送消息

EventBus的使用

接收消息

1. 获取EventBus

EventBus eb = vertx.eventBus();

2. 注册Handler

使用 consumer 方法

EventBus eb = vertx.eventBus();

eb.consumer("news.uk.sport", message -> {
  System.out.println("I have received a message: " + message.body());
});

注销处理器

通过 unregister() 方法

consumer.unregister(res -> {
  if (res.succeeded()) {
    System.out.println("The handler un-registration has reached all nodes");
  } else {
    System.out.println("Un-registration failed!");
  }
});

发布消息

使用 publish 方法

eventBus.publish("news.uk.sport", "Yay! Someone kicked a ball");

发送消息

发送send的消息只会传递给在该地址注册的其中一个处理器,这就是点对点模式(通过不严格的轮询算法来选择绑定的处理器)

eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");

设置消息头

Event Bus 上发送的消息可包含头信息。这可通过在发送或发布时提供的 DeliveryOptions 来指定

DeliveryOptions options = new DeliveryOptions();
options.addHeader("some-header", "some-value");
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options);

应答消息/发送回复

当使用 send 方法发送消息时,Event Bus会尝试将消息传递到注册在Event Bus上的 MessageConsumer中。在某些情况下,消费者可以通过调用 reply 方法来应答这个消息。

接收者

MessageConsumer<String> consumer = eventBus.consumer("news.uk.sport");
consumer.handler(message -> {
  System.out.println("I have received a message: " + message.body());
  message.reply("how interesting!");
});

发送者

eventBus.send("news.uk.sport", "Yay! Someone kicked a ball across a patch of grass", ar -> {
  if (ar.succeeded()) {
    System.out.println("Received reply: " + ar.result().body());
  }
});

Vert.x Buffers

编写 TCP 服务端和客户端

使用所有默认配置项创建 TCP 服务端

NetServer server = vertx.createNetServer();

配置 TCP 服务端

可以在创建时通过传入一个 NetServerOptions 实例来配置服务器:

NetServerOptions options = new NetServerOptions().setPort(4321);
NetServer server = vertx.createNetServer(options);

启动服务端监听

要告诉服务端监听传入的请求可以使用其中一个 listen 方法。

让服务器监听配置项指定的主机和端口:

NetServer server = vertx.createNetServer();
server.listen();

或在调用 listen 方法时指定主机和端口号

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost");

默认主机名是 0.0.0.0,它表示:监听所有可用地址。默认端口号是 0,这也是一个特殊值,它告诉服务器随机选择并监听一个本地没有被占用的端口

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost", res -> {
  if (res.succeeded()) {
    System.out.println("Server is now listening!");
  } else {
    System.out.println("Failed to bind!");
  }
});

从Socket读取数据

您可以在Socket上调用 handler 方法来设置用于读取数据的处理器。

每次 Socket 接收到数据时,会以 Buffer 对象为参数调用处理器。

NetServer server = vertx.createNetServer();
server.connectHandler(socket -> {
  socket.handler(buffer -> {
    System.out.println("I received some bytes: " + buffer.length());
  });
});

向Socket中写入数据

使用 write 方法写入数据到Socket
写入操作是异步的,可能调用 write 方法返回过后一段时间才会发生。

Buffer buffer = Buffer.buffer().appendFloat(12.34f).appendInt(123);
socket.write(buffer);

// 以UTF-8的编码方式写入一个字符串
socket.write("some data");

// 以指定的编码方式写入一个字符串
socket.write("some data", "UTF-16");

关闭 TCP 服务端

您可以调用 close 方法关闭服务端。关闭操作将关闭所有打开的连接并释放所有服务端资源。

关闭操作也是异步的,可能直到方法调用返回过后一段时间才会实际关闭。若您想在实际关闭完成时收到通知,那么您可以传递一个Handler。

当关闭操作完成后,绑定的处理器将被调用:

server.close(res -> {
  if (res.succeeded()) {
    System.out.println("Server is now closed");
  } else {
    System.out.println("close failed");
  }
});

创建 TCP 客户端

使用所有默认选项

NetClient client = vertx.createNetClient();

配置 TCP 客户端

也可以在创建实例时传入 NetClientOptions 给客户端

NetClientOptions options = new NetClientOptions().setConnectTimeout(10000);
NetClient client = vertx.createNetClient(options);

编写 HTTP 服务端和客户端

创建 HTTP 服务端

HttpServer server = vertx.createHttpServer();

手动配置 HTTP 服务端

HttpServerOptions options = new HttpServerOptions().setMaxWebsocketFrameSize(1000000);

HttpServer server = vertx.createHttpServer(options);

记录服务端网络活动

为了进行调试,可记录网络活动。

HttpServerOptions options = new HttpServerOptions().setLogActivity(true);

HttpServer server = vertx.createHttpServer(options);

开启服务端监听

以使用其中一个 listen 方法告诉服务器监听传入的请求

HttpServer server = vertx.createHttpServer();
server.listen();

或在调用 listen 方法时指定主机和端口号

HttpServer server = vertx.createHttpServer();
server.listen(8080, "myhost.com");

默认主机名是0.0.0.0,它表示:监听所有可用地址;默认端口号是80。(实际的绑定也是异步的)

发布了9 篇原创文章 · 获赞 0 · 访问量 183

猜你喜欢

转载自blog.csdn.net/qq_36629741/article/details/103381209
今日推荐