Vert.x-学习记录

vert.x

异步非阻塞

在这里插入图片描述

在这里插入图片描述

1.Verticle对象和处理器(Handler)是什么关系?Vert.x如何保证Verticle内部线程安全?

一个应用程序通常是由在同一个 Vert.x 实例中同时运行的许多 Verticle 实例组合而成。不同的 Verticle 实例通过向 Event Bus 上发送消息来相互通信。一个JVM里可含多个Vert.x的实例
在这里插入图片描述

Verticle对象往往包含有一个或者多个处理器(Handler),在Java代码中,后者经常是以Lambda也就是匿名函数的形式出现

vertx.createHttpServer().requestHandler(req->{
    //hander1           
}).listen(8080);
vertx.createHttpServer().requestHandler(req->{
    //hander2           
}).listen(8080);

在Vert.x中,完成Verticle的部署之后,真正调用处理逻辑的入口往往是处理器(Handler),Vert.x保证同一个普通Verticle(也就是EventLoop Verticle,非Worker Verticle)内部的所有处理器(Handler)都只会由同一个EventLoop线程调用,由此保证Verticle内部的线程安全。所以我们可以放心地在Verticle内部声明各种线程不安全的属性变量,并在handler之间分享他们

public class MyVerticle extends AbstractVerticle {
    int i = 0;//属性变量

    public void start() throws Exception {
        vertx.createHttpServer().requestHandler(req->{
            i++;
        req.response().end();//要关闭请求,否则连接很快会被占满
        }).listen(8080);

        vertx.createHttpServer().requestHandler(req->{
            System.out.println(i);
        req.response().end(""+i);
        }).listen(8081);
    }
}

Handler内部是atomic/原子操作,Verticle内部是thread safe/线程安全的,Verticle之间传递的数据是immutable/不可改变的。

2.Verticle类型

这里有3种不同类型的verticle:

Standard Verticles

这是最常见和通用的类型-他们总是使用一个 event loop thread(事件循环线程) 执行。

Worker Verticles

使用来自 worker pool(工作者线程池) 中的线程运行。一个实例永远不会被多个线程同时执行。

若您想要将 Verticle 部署成一个 Worker Verticle,您可以通过 setWorker 方法来设置:

DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

Worker Verticle 实例绝对不会在 Vert.x 中被多个线程同时执行,但它可以在不同时间由不同线程执行。

Multi-threaded worker verticles

使用来自 worker pool(工作者线程池) 中的线程运行。一个实例可以被多个线程同时执行。

警告:Multi-threaded Worker Verticle 是一个高级功能,大部分应用程序不会需要它。由于这些 Verticle 是并发的,您必须小心地使用标准的Java多线程技术来保持 Verticle 的状态一致性。

3.EventLoop

EventLoop 是一个在Vert.x生命周期内,不停轮询事件是否发生,并将发生的事件交给Handler予以处理的无限调度循环线程

如果Eventloop在完成一个调度循环的时间过长,就有可能导致新发生的事件得不到及时的处理,进而导致单次事件响应时间过长,影响客户体验。

为了在短时间内完成调度循环,就需要用户正确估算出,哪些程序代码会相对长时间地占用Eventloop线程的执行时间,然后将该部分代码的执行交由其它线程去处理。

一个简单粗暴的判断标准:任何涉及到IO操作的代码,都可以认为是可能造成阻塞的代码,纯粹内存操作的代码,只要执行时间没有明显超长(例如执行循环几万次的处理便可认为是执行时间超长),都可以认为是非阻塞代码

Vert.x提供了除了Eventloop线程池以外的线程池,名曰Worker线程池。此时就需要用户自行将该部分代码包装成Worker线程执行的代码,并交给Worker线程予以执行,执行完成之后再由Eventloop线程执行回调函数处理其结果。注意:Vert.x中将代码交给Worker线程执行的方式有两种,一种是通过executeBlocking函数包装,另外一种是写入Worker Verticle中。

在这里插入图片描述

4.Vert.x中各种Client该如何正确使用,用完是否需要关闭?

答:Vert.x中提供了各种预设客户端,例如HttpClient,JDBCClient,WebClient,MongoClient等,一般情况下,建议将客户端与Verticle对象绑定,一个Verticle对象内保留一个特定客户端的引用,并在start方法中将其实例化,这样Vert.x会在执行deployVerticle的时候执行start方法,实例化并保存该对象,在Verticle生命周期内,不需要频繁创建和关闭同类型的客户端,建议在Verticle的生命周期内对于特定领域,只创建一个客户端并复用该客户端,

public class TheVerticle extends AbstractVerticle{
    //将客户端对象与Verticle对象绑定,这里选取了三种不同的客户端作为示范
    HttpClient httpClient;
    WebClient webClient;
    JDBCClient jdbcClient;

    public void start(){
        //
    }
}

使用客户端完之后,无需调用client.close();方法关闭客户端,频繁创建销毁客户端会在一定程度上消耗系统资源,降低性能,同时增加开发人员的负担,Vert.x提供客户端的目的就在于复用连接以减少资源消耗,提升性能,同时简化代码,减轻开发人员的负担。如您关闭客户端,在下一次使用该客户端的时候,需要重新创建客户端。

5.Vert.x中Future该如何正确使用,怎样规避回调地狱?

Future是Handler的子接口,以提供链式调用,但无法封装异步调用的结果,借助Handler的另一子接口Promise来封装异步调用的结果,并生成Future

  • compose(mapper):当前 Future 完成时,执行相关代码,并返回 Future。当返回的 Future 完成时,组合完成。
  • compose(handler, next):当前 Future 完成时,执行相关代码,并完成下一个 Future 的处理。

对于第二个例子,处理器需要完成 next future,以此来汇报处理成功或者失败。

您可以使用 completer 方法来串起一个带操作结果的或失败的 Future ,它可使您避免用传统方式编写代码:如果成功则完成 Future,否则就标记为失败

  • Future对象提供了一种异步结果的包装,用户可使用Future类中的setHandler方法来保存回调函数,然后在原先使用该回调函数的地方用completer方法予以填充,这样便可将回调函数从原参数中取出,以减少回调缩进,从而规避回调地狱。简单点说就是包装异步代码,使代码简洁。只适用于返回数据为 AsyncResult 的函数
    在这里插入图片描述
future.compose(message ->
  Future.<Message<String>>future(f ->
    vertx.eventBus().send("address", message.body(), f)
  )
);
//以上和以下两种写法是等效的,上述写法3.4.0+版本支持
future.compose(message ->{
  Future<Message<String>> f = Future.<Message<String>>future();//可简写为Future<Message<String>> f = Future.future();
  vertx.eventBus().send("address",message.body(),f.completer());//可简写为vertx.eventBus().send("address",message.body(),f);
  return f;
});
  • Future 接口提供了 compose 方法来链式地组合多个异步操作。

每一个 compose 方法需要传入一个 FunctionFunction 的输入是前一个异步过程的返回值,需要返回一个新的需要链接的 Future。 该 Function方法当且仅当前一个异步流程执行成功时才会被调用。

首先通过 eventbus 发送消息 messageaddress1

如果第一步成功,则发送第一步的消息的返回值到 address2

如果以上任何一步失败,则不会继续执行下一个异步流程,直接执行最终的 Handler ,并且 res.succeeded()false,可以通过 res.cause() 来获得异常对象

.如果以上三步全都成功,则同样执行 Handler,res.succeeded()true,可以通过 res.result() 获取最后一步的结果。

Future.<Message<String>>future(f ->
  vertx.eventBus().send("address1", "message", f)
).compose((msg) ->
  Future.<Message<String>>future(f ->
    vertx.eventBus().send("address2", msg.body(), f)
  )
).setHandler((res) -> {
  if (res.failed()) {
    //deal with exception
    return;
  }
  //deal with the result
});;

此处的setHander方法相当于java中的finally

6.Reactor 模式和 Multi-Reactor 模式

Vert.x 的 API 都是事件驱动的,当有事件时 Vert.x 会将事件传给处理器来处理。

事件:

  • 触发一个计时器
  • Socket 收到了一些数据
  • 从磁盘中读取了一些数据
  • 发生了一个异常
  • HTTP 服务器收到了一个请求

在多数情况下,Vert.x使用被称为 Event Loop 的线程来调用您的处理器。

由于Vert.x或应用程序的代码块中没有阻塞,Event Loop 可以在事件到达时快速地分发到不同的处理器中。

由于没有阻塞,Event Loop 可在短时间内分发大量的事件。例如,一个单独的 Event Loop 可以非常迅速地处理数千个 HTTP 请求。

我们称之为 Reactor 模式

每个 Vertx 实例维护的是 多个Event Loop 线程。默认情况下,我们会根据机器上可用的核数量来设置 Event Loop 的数量,您亦可自行设置。

这意味着 Vertx 进程能够在您的服务器上扩展,与 Node.js 不同。我们将这种模式称为 Multi-Reactor 模式(多反应器模式),区别于单线程的 Reactor 模式(反应器模式)。

Node.js 实现了这种模式。

在一个标准的反应器实现中,有 一个独立的 Event Loop 会循环执行,处理所有到达的事件并传递给处理器处理。

单一线程的问题在于它在任意时刻只能运行在一个核上。如果您希望单线程反应器应用(如您的 Node.js 应用

)扩展到多核服务器上,则需要启动并且管理多个不同的进程。

请注意:即使一个 Vertx 实例维护了多个 Event Loop,任何一个特定的处理器永远不会被并发执行。大部分情况下(除了 Worker Verticle 以外)它们总是在同一个 Event Loop 线程中被调用

7.运行阻塞式代码

  • 可以通过调用 executeBlocking 方法(将代码交给Worker线程执行的其中一种方法)来指定阻塞式代码的执行以及阻塞式代码执行后处理结果的异步回调。
vertx.executeBlocking(future -> {
  // 调用一些需要耗费显著执行时间返回结果的阻塞式API
  String result = someAPI.blockingMethod("hello");
  future.complete(result);
}, res -> {
  System.out.println("The result is: " + res.result());
});

默认情况下,如果 executeBlocking 在同一个上下文环境中(如:同一个 Verticle 实例)被调用了多次,那么这些不同的 executeBlocking 代码块会 顺序执行(一个接一个)。

  • 另外一种运行阻塞式代码的方法是使用 Worker Verticle。(将代码交给Worker线程执行的其中一种方法,具体见2.Verticle类型)

一个 Worker Verticle 始终会使用 Worker Pool 中的某个线程来执行。

默认的阻塞式代码会在 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());
});

Worker Executor 在不需要的时候必须被关闭:

executor.close();

当使用同一个名字创建了许多 worker 时,它们将共享同一个 pool。当所有的 worker executor 调用了 close 方法被关闭过后,对应的 worker pool 会被销毁。

8 .创建Vertx

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

VertxOptions对象有很多配置,包括集群、高可用、池大小等。在Javadoc中描述了所有配置的细节。

集群模式

// 注意要添加对应的集群管理器依赖,详情见集群管理器章节
VertxOptions options = new VertxOptions();
Vertx.clusteredVertx(options, res -> {
  if (res.succeeded()) {
    Vertx vertx = res.result(); // 获取到了集群模式下的 Vertx 对象
    // 做一些其他的事情
  } else {
    // 获取失败,可能是集群管理器出现了问题
  }
});

9.创建Verticle

  • Verticle 是由 Vert.x 部署和运行的代码块。默认情况一个 Vert.x 实例维护了N(默认情况下N = CPU核数 x 2)个 Event Loop 线程。Verticle 实例可使用任意 Vert.x 支持的编程语言编写,而且一个简单的应用程序也可以包含多种语言编写的 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() {
  }

}
  • 您可以实现 异步版本start 方法来做这个事。这个版本的方法会以一个 Future 作参数被调用。方法执行完时,Verticle 实例并没有部署好(状态不是 deployed)。稍后,您完成了所有您需要做的事(如:启动其他Verticle),您就可以调用 Futurecomplete(或 fail )方法来标记启动完成或失败了。

这儿有一个例子:

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());
      }
    });
  }
}

同样的,这儿也有一个异步版本的 stop 方法,如果您想做一些耗时的 Verticle 清理工作,您可以使用它。

public class MyVerticle extends AbstractVerticle {

  public void start() {
    // 做一些事
  }

  public void stop(Future<Void> stopFuture) {
    obj.doSomethingThatTakesTime(res -> {
      if (res.succeeded()) {
        stopFuture.complete();
      } else {
        stopFuture.fail();
      }
    });
  }
}

请注意:您不需要在一个 Verticle 的 stop 方法中手工去撤销启动时部署的子 Verticle,当父 Verticle 在撤销时 Vert.x 会自动撤销任何子 Verticle。

10.部署Verticle

  • 部署方式
Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);

也可以使用名称

vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle");

// 部署JavaScript的Verticle
vertx.deployVerticle("verticles/myverticle.js");

// 部署Ruby的Verticle
vertx.deployVerticle("verticles/my_verticle.rb");
DeploymentOptions options = new DeploymentOptions().setInstances(16);//指定实例数量,跨多核扩展时很有用
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

可在部署时传给 Verticle 一个 JSON 格式的配置

JsonObject config = new JsonObject().put("name", "tim").put("directory", "/blah");
DeploymentOptions options = new DeploymentOptions().setConfig(config);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

Verticle 名称可以有一个前缀 —— 使用字符串紧跟着一个冒号,它用于查找存在的Factory,不指定前缀,Vert.x将根据提供名字后缀来查找对应Factory

js:foo.js // 使用JavaScript的Factory

  • 同时Verticle的部署是异步方式,可能在 deploy 方法调用返回后一段时间才会完成部署。

如果您想要在部署完成时被通知则可以指定一个完成处理器:

vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", res -> {
  if (res.succeeded()) {
    System.out.println("Deployment id is: " + res.result());
  } else {
    System.out.println("Deployment failed!");
  }
});

11.撤销Verticle

我们可以通过 undeploy 方法来撤销部署好的 Verticle。

撤销操作也是异步的,因此若您想要在撤销完成过后收到通知则可以指定另一个完成处理器:

vertx.undeploy(deploymentID, res -> {
  if (res.succeeded()) {
    System.out.println("Undeployed ok");
  } else {
    System.out.println("Undeploy failed!");
  }
});

12.Context 对象

每个 Verticle 在部署的时候都会被分配一个 Context(根据配置不同,可以是Event Loop Context 或者 Worker Context),之后此 Verticle 上所有的普通代码都会在此 Context 上执行(即对应的 Event Loop 或Worker 线程)。一个 Context 对应一个 Event Loop 线程(或 Worker 线程),但一个 Event Loop 可能对应多个 Context

您可以通过 getOrCreateContext 方法获取 Context 实例:

Context context = vertx.getOrCreateContext();
if (context.isEventLoopContext()) {
  System.out.println("Context attached to Event Loop");
} else if (context.isWorkerContext()) {
  System.out.println("Context attached to Worker Thread");
} else if (context.isMultiThreadedWorkerContext()) {
  System.out.println("Context attached to Worker Thread - multi threaded worker");
} else if (! Context.isOnVertxThread()) {
  System.out.println("Context not attached to a thread managed by vert.x");
}

当您获取了这个 Context 对象,您就可以在 Context 中异步执行代码了。换句话说,您提交的任务将会在同一个 Context 中运行:

vertx.getOrCreateContext().runOnContext(v -> {
  System.out.println("This will be executed asynchronously in the same context");
});

当在同一个 Context 中运行了多个处理函数时,可能需要在它们之间共享数据。 Context 对象提供了存储和读取共享数据的方法

final Context context = vertx.getOrCreateContext();
context.put("data", "hello");
context.runOnContext((v) -> {
  String hello = context.get("data");
});

Event Bus

概念

Event Bus 是 Vert.x 的神经系统。可实现不同verticle实例间的异步通信,可传递不同类型的数据,但最好使用json

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

支持三种通信模式

点到点

请求/响应模式

发布/订阅消息模式

  • 每一个 Vert.x 实例都有一个单独的 Event Bus 实例。您可以通过 Vertx 实例的 eventBus 方法来获得对应的 EventBus 实例。
EventBus eb = vertx.eventBus();//对于每个Vertx来说,是单例的
  • 注册处理器。当一个消息达到您的处理器,该处理器会以 message 为参数被调用。
eb.consumer("news.uk.sport", message -> { 
    System.out.println("I have received a message: " + message.body());
});

或者

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

同一个地址可以注册许多不同的处理器,一个处理器也可以注册在多个不同的地址上。

  • 撤销处理器
consumer.unregister(res -> {  
    if (res.succeeded()) {    
        System.out.println("The handler un-registration has reached all nodes");  
    } 
    else {   
        System.out.println("Un-registration failed!"); 
    }
});
  • 发布消息

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

这个消息将会传递给所有在地址 news.uk.sport 上注册过的处理器。

  • 发送消息(点对点:发送(send)的消息只会传递给在该地址注册的其中一个处理器)
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball");
  • 添加消息头
DeliveryOptions options = new DeliveryOptions();
options.addHeader("some-header", "some-value");
eventBus.send("news.uk.sport", "Yay! Someone kicked a ball", options);
  • 应答/回复消息:当发送者需要知道消费者何时收到消息并 处理 了消息。

接收者:

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()); 
    }
});
  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值