Vert.x Core 中文使用手册(3.5版)(持续更新)

2019年11月15日 阅读数:23
这篇文章主要向大家介绍Vert.x Core 中文使用手册(3.5版)(持续更新),主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

1. 开始使用

    使用该框架第一步是建立Vertx对象,该框架的功能都依赖于Vertx,例如建立client,servers,获取event bus,设置timers等等操做。html

  • 简单建立Vertx

    能够简单建立Vertx,代码以下:java

Vertx vertx =Vertx.vertx()
  • 建立时作某些配置

    若是想配置一些信息,能够这么建立,代码以下:react

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

    更多配置信息请阅读VertxOptions文档。git

  • 建立集群的Vertx对象

后续补充。web

注意:多数状况下只须要建立一个Vertx对象就能够了,不过也有些状况须要建立多个Vertx对象,例如两个总线须要隔离或者客户端服务器须要分组时。(具体解释后续补充)算法

2. 链式操做

    Vertx的方法会返回自身对象的引用,因此你能够链式调用函数数据库

    链式调用代码:编程

request.response().putHeader("Content-Type", "text/plain").write("some text").end()

    若是不喜欢链式调用,也能够拆开,代码以下:json

HttpServerResponse response = request.response();
response.putHeader("Content-Type", "text/plain");
response.write("some text");
response.end();

    上面两段代码没有区别。api

3. 回调

    Vertx APIs是事件驱动的,也就是当你感兴趣的事情发生后,Vertx会以发送events的方式通知你

    下面是部分events:

  • 定时器被触发
  • socket接收到了一些数据
  • 数据已经从硬盘中读取
  • 产生了exception
  • HTTP server 接到了请求

    你能够经过向Vertx APIs注册处理函数(handler)的方式处理这些events,例如启动一个每秒触发一次的定时器,并注册一个处理这个事件的函数,代码以下:

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!");
});

    注意:当事件发生时,Vertx会异步调用处理函数(handler)。因为是异步的,下面将介绍一些在Vertx中重要的概念。

4.拒绝阻塞

    绝大部分Vertx APIs是不会阻塞当前线程的。(有很是少的会,例如后续补充)。

    若是代码可以当即获得结果,能够在当前线程执行。若是不能,你一般应该注册一个处理函数(handler),等代码执行完后由Vertx调用处理函数(handler)作后续处理。

    一般须要较长时间的操做会发生在如下场景:

  • 从socket中读取数据
  • 向硬盘写数据
  • 发送数据并等待回复
  • 许多其余的状况

    若是在在当前线程执行以上操做,并等待操做执行完(期间该不能作其余事)再作后续的处理,这会很低效(这种方式我称为阻塞式)。

  •    阻塞式的例子
/*
好比说,你以上面所说的方式实现了一个服务器。
如今每秒共有100个请求同时到达,而且其中50个请求须要读取硬盘数据(用时2秒),其他50个能当即完成。
那么10秒内共有1000个请求到达,
第一秒须要开启51个线程,其中50个在等待数据读取操做的完成,其中1个线程处理其他操做
第二秒开始,有1个线程的工做已经完成,50个线程在忙,因此须要再开50个线程处理须要读取硬盘数据的操做
第三秒开始~~~~~~~~~~
以此类推,阻塞式的处理方式须要开启大量线程才能使你的服务器不卡死
*/
  • 非阻塞式
/*
和上面例子状况相关,不过当进行数据读取操做时,线程不会一直等待,而是继续处理其余请求
第一秒须要开启1个线程,其中50请求会开启数据读取操做,当开启后不阻塞,会继续处理其余请求
第二秒开始,有1个线程的工做已经完成,接着向第一秒同样处理请求
第三秒~~~~~~~~~~~
~~~~~~~~~~~~~~~~~~~~
以此类推,非阻塞式的处理方式只须要少数线程就能让服务器不卡死
*/

    补充说明:在计算机中,有专门的硬件处理硬盘读写操做,当操做完成后会发出中断通知对应程序,也就是硬盘读写不占用cpu。不仅是硬盘读写,不少操做也是有专门的硬件处理的,不占用cpu。具体内容请看计算机组成原理(大概是这样,具体是什么我忘了,欢迎知道的补充完整,上面有错误的也欢迎指出)

    线程占用的内存以及不一样线程间的切换会带来开销。如今的应用的并发量会很大,阻塞式的处理方式难以知足处理大量并发请求的需求。

5.反应器和多个反应器

    上面提到过,Vertx是事件驱动的,也就是当某个事件发生时,会调用相应的处理函数(handler)

    大多数状况下,Vertx使用一个称为event loop的线程调用你的处理函数(handler)

    这个event loop会在events到达时将其(指events)分派给不一样的处理函数(handler),这样你的程序就不会阻塞。

    由于不会阻塞,一个event loop能够在短期内分发大量的events。例如单个event loop能够很是快的处理数以千计的HTTP 请求。

    咱们称这种模式为 Reactor Pattern(反应器模式)

    补充:上面那个连接是维基百科的,应该须要翻墙才能看,这里给个国内博客的连接:Reactor(反应器)模式初探

    标准的反应器模式实现是单线程的,也就是一个event loop循环监听events的到来,并将其分发到对应的处理函数(handler)。(换句话说,一个event loop处理所有的events)

    这种实现方式的缺陷是,任什么时候候它只能运行在cpu的单核上,若是你想使用cpu的其余核,你必须启动和管理多个进程。下面给出原文,由于怕理解不对

The trouble with a single thread is it can only run on a single core at any one time, so if you want your single threaded reactor application (e.g. your Node.js application) to scale over your multi-core server you have to start up and manage many different processes.

    Vertx的实现方式和上面的不一样。每个Vertx实例持有多个event loop而不是单个event loop。默认状况下会根据机器上可用核的数量决定event loop的数量,固然这个能够被重写。(意思是默认状况下Vertx框架会自动根据机器上cpu的核数决定event loop的数量,默认event loop的数量是cpu核数*2,下面给出原文)

Vert.x works differently here. Instead of a single event loop, each Vertx instance maintains several event loops. By default we choose the number based on the number of available cores on the machine, but this can be overridden.

     为了和单线程的反应器模式区别,咱们称这个模式为多反应器模式(Multi-Reactor Pattern)。

    注意:虽然一个Vertx实例会持有多个event loop,可是任何一个处理函数(handler)不会被并发调用。在大多数状况(除了后续补充)下,处理函数会由固定的event loop调用。(换句话说,如今有处理函数A,B,C以及event loop a,b。一般不会发生a和b同时调用A的状况,通常是A固定由b调用,B固定由a调用)

6. 黄金法则 -不要阻塞Event Loop

    上面提到过,Vertx APIs是非阻塞的而且不会阻塞event loop。可是若是你在处理函数内写了阻塞代码,event loop仍是会被阻塞。

    若是所有event loop被阻塞了,你的程序就会彻底中止。因此别在处理函数内写阻塞代码!

    下面给出一些阻塞代码例子:

  • Thread.sleep()
  • 等待锁
  • 作长时间的数据库操做或者等待结果
  • 作复杂运算
  • 大量循环(不知到理解对没,原文是Spinning in a loop)

    多长时间的操做算阻塞?这个根据实际状况定。例如你但愿你的程序每秒能处理10000个HTTP请求,那么单个请求处理时间不能超过0.1ms。也就是说执行时间超过0.1ms就是阻塞了。

    若是检测到某个event loop有必定时间没有返回了,Vertx能自动以日志的形式发出提醒。若是你看到相似下面的日志,你就要注意是否在event loop的哪一个地方发生阻塞。

Thread vertx-eventloop-thread-3 has been blocked for 20458 ms

    Vertx也提供栈式追踪,从而精肯定位发生阻塞的位置。

    若是你想关闭提醒或者更改设置,能够在VertxOptions对象上作相应设置。注意:应该在建立Vertix对象前设置。

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

7.运行阻塞式代码

    理想状况下所有APIs是异步的,而后实际上并非。

    在JVM技术圈,有许多异步的APIs也有许多阻塞式的APIs。一个很好的例子就是JDBC,它是自然同步的,即便Vertx再牛逼,也不能把它变成异步的。

    Vertx不打算将所有都改写成异步的,而是提供一种安全运行阻塞代码的方式。

    前面咱们说过,不要在处理函数(handler)内写阻塞式代码,那如何执行阻塞代码呢?

    答案是经过调用excuteBlocking来执行指定的阻塞式代码,而且当阻塞式代码执行完后,处理函数会被异步调用。例子以下:

/*
第一个参数是阻塞式代码,第二个参数是处理函数
*/
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在同一个上下文(context)中屡次被调用(例如在同一个Vertix实例),不一样的executeBlocking会被顺序执行(一个接着一个)。

    若是你不在乎执行顺序,你能够将参数ordered设为false。这样,任何executeBlocking均可能会在工做池中并行执行(一些名词解释后续补充

<T> void executeBlocking(Handler<Future<T>> blockingCodeHandler,
                         boolean ordered,
                         Handler<AsyncResult<T>> resultHandler)

    另外一种运行阻塞式代码的可选方式是使用worker verticleworker verticle老是由来自工做池的线程执行(A worker verticle is always executed with a thread from the worker pool.)。(具体状况后续补充

    默认状况下,阻塞代码式会在Vertx的工做池中执行,这个工做池由VertxOptions对象的setWorkerPoolSize配置。

    你也能够建立新的工做池用来执行阻塞式代码(除了执行阻塞式代码,应该还有其他用途,后续补充)。示例代码以下:

/*
建立一个WorkerExecuter,对象持有新的线程池来执行代码?
具体状况后续补充
*/
WorkerExecutor executor = vertx.createSharedWorkerExecutor("my-worker-pool");
executor.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());
});

    注意:新建立的工做池再也不使用的时候必须关闭

executor.close();

    若是多个同名的WorkerExcutor被建立,那么这些WorkerExcutor使用相同的线程池。当所有使用这个线程池的WorkerExcutor被关闭,该线程池会被销毁。

    若是WorkerExcutor是在Verticle中建立的,那么当这个Verticle被unDeployed后,Vertx会自动帮你关闭对应的WorkerExcutor。

    WorkerExcutors是可配置的,实例代码以下:

int poolSize = 10;

// 2 minutes
long maxExecuteTime = 120000;

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

8.异步协做

    可使用Vertx的futures实现多个异步结果的协做。Vertx支持并行组合(并发运行数个异步操做)和顺序组合(链式异步操做)。(个人理解是支持并行和顺序两种方式整合异步操做的结果下面给出原文)

Coordination of multiple asynchronous results can be achieved with Vert.x futures. It supports concurrent composition (run several async operations in parallel) and sequential composition (chain async operations).

  •  并行组合

    CompositeFuture.all接收数个Future参数(最多6个),返回一个Future(若是前面接受的Future参数的状态都是succeed,返回的这个Future的状态也是succeed,若是前面接受的Future有一个或者多个的状态式failed,那么返回的这个Future的状态式failed),下面给出官方示例代码:

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()) {
    // All servers started
  } else {
    // At least one server failed
  }
});

    补充说明:下面给出本身写的例子,辅助理解

/*
这段代码的做用是:
1.运行两段阻塞式的代码,代码根据当前时间设置状态为运行成功仍是不成功
2.使用CompositeFuture.All判断结果
*/
    //建立Vertx和两个Future
    Future<String> oneFuture=Future.future();
	Future<String> secondFuture=Future.future();
    Vertx vertx=new Vertx();
    
    //运行阻塞式代码
	vertx.executeBlocking(future -> {
		int i = 2;
		while (i-- > 0) {
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
        //根据时间设置运行结果
		if(new Date().getTime()%2==0)
			future.complete("运行成功");
		else
			future.fail("运行失败");
	},oneFuture.completer());
	
	vertx.executeBlocking(future -> {
		int i = 2;
		while (i-- > 0) {
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		if(new Date().getTime()%2==0)
			future.complete("运行成功");
		else
			future.fail("运行失败");
	},secondFuture.completer());
	
	Future rtFuture=CompositeFuture.all(oneFuture,secondFuture);
    rtFuture.setHandler(ar->{
		if(ar.succeeded())
			System.out.println("所有代码运行成功");
		else
			System.out.println("至少有一个运行失败");
	});

    操做是并行执行的,在返回的Future中设置的处理函数(rtFuture.setHandler....)会在操做所有完成后调用。若是传入CompositeFuture.all的Future中有一个或者多个被标记为failed,那么返回的Future也会被标记为failed,若所有被标记为succeed,那返回的Future会被标记为succeed

    简单总结一下,

  1. 建立几个Future,让阻塞式代码运行完后给对应的Future设置一下状态(succed or falied),而后这几个Future就能够表示对应阻塞代码是否运行成功。
  2. 由于阻塞代码须要较长时间运行并且结束时间不必定,为了协做结果,Vertx提供了一个API,这个API返回一个Future,而且这个Future在所有Future被设置了状态后会被设置成相应状态。
  3. 并且Future能够设置一个处理函数,这个处理函数(handler)会在设置了状态后执行。这样就达到了协做异步操做结果的效果。

    CompositeFuture.all还有许多重载方法,具体内容请看文档。

    并行组合的方法除了all外,还有any(CompositeFuture.all)和join(CompositeFuture.join)。这三者用法相同(固然方法名称是不相同的,相同的是传入的参数和返回的参数)。为了方便说明,假设传入了三个Future,分别记为a,b,c,返回的Future记为rt。三者区别以下:

  1. 对于all。若是a,b,c全被设为succeed,那么rt被设为succed。若是出现一个参数被设为failed,那么rt当即被设为failed(当即的意思就是无论其余参数有没被设置,只有有一个被设为failed,就当即给rt设置failed状态)
  2. 对于any。有一个参数被设置为succeed,rt就当即被设置为succeed。若是所有a,b,c都被设为failed,rt就设为failed
  3. 对于join。须要所有a,b,c都被设置了状态,rt才会被设置状态。若是a,b,c都为succeed,rt为succeed。若是a,b,c中有一个为failed,rt就被设为failed。
  •  顺序组合

    顺序组合的方法是compose。下面给出官方示例代码,下文有说明。

FileSystem fs = vertx.fileSystem();
Future<Void> startFuture = Future.future();

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

fut1.compose(v -> {
  // When the file is created (fut1), execute this:
  Future<Void> 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 startFuture it as failed if any step fails.
        startFuture);

    在这个例子中,有三个操做链式执行。

  1. 建立一个文件(fut1)
  2. 向文件写入一些数据(fut2)
  3. 移动文件(startFuture)

    当这三个操做都成功时,最后一个Future(startFuture)会被设置为succeed。如有一个操做不成功,那最后一个Future会被设置为failed。

    简单说明compose的用法(compose是Future的方法,下面所说的future都是指compose所属的Future对象):

    先给出方法原型:

default <U> Future<U> compose(java.util.function.Function<T,Future<U>> mapper)

default <U> Future<U> compose(Handler<T> handler, Future<U> next)
  • 一个参数的compose:当future被设置状态succeed,传入compose的处理函数会被调用。这个处理函数会返回一个Future(不会设置返回的Future的状态)。若是future被设置为failed,处理函数(handler)不会被调用,但处理函数(handler)返回的Future会被设置为failed。
  • 两个参数的compose:当future被设置为succeed,传入compose的处理函数会被调用。在这个处理函数(handler)中应该设置next的状态。当future被设置为failed,处理函数(handler)不会被调用,但next会自动设置为failed

补充说明:下面给出源码

default <U> Future<U> compose(Function<T, Future<U>> mapper) {
    if (mapper == null) {
      throw new NullPointerException();
    }
    Future<U> ret = Future.future();
    setHandler(ar -> {
      if (ar.succeeded()) {
        Future<U> apply;
        try {
          apply = mapper.apply(ar.result());
        } catch (Throwable e) {
          ret.fail(e);
          return;
        }
        apply.setHandler(ret);
      } else {
        ret.fail(ar.cause());
      }
    });
    return ret;
  }


default <U> Future<U> compose(Handler<T> handler, Future<U> next) {
    setHandler(ar -> {
      if (ar.succeeded()) {
        try {
          handler.handle(ar.result());
        } catch (Throwable err) {
          if (next.isComplete()) {
            throw err;
          }
          next.fail(err);
        }
      } else {
        next.fail(ar.cause());
      }
    });
    return next;
  }

    简单的说,若是future被设置为fail,或者处理函数(handler)抛出异常,这个经过compose方法关联的future(例如第一种compose返回的future,第二种方法中的第二个参数)也会被设置为fail,若是future被设置为success,而且处理函数(handler)没有抛出异常,关联的future不会被设置任何状态,你须要在处理函数(handler),或者其余什么地方设置关联的future的状态。

9. Verticles

    Vertx带有一个简单,可扩展,actor-like部署(原文是actor-like deployment)和并发的开箱即用模型。你能够用它来协助你实现本身的功能。

    这个模型式是彻底可选的。若是你不喜欢它,Vertx不会强迫你使用这种方式建立你的应用。

    这个模型不是标准的角色模型(actor-model)的实现。它在并发,扩展和部署这些方面和角色模型(actor-model)类似。

    使用这个模型, 你的代码就像是verticles(不理解不要紧,看下文对这个概念的解释)的集合。

    verticles是一个由Vertx运行和部署的代码块。verticles相似于角色模型(actor-model)中的actor。

    一个由Vertx实现的应用通常是由许多同时运行在同一个Vertx的verticle实例组成。不一样的verticle实例经过在event bus发送消息的方式通讯。

  • 编写Verticles

verticle类必须实现Verticle接口。你能够直接 implement Verticle接口,不过一般咱们会继承抽象类AbstractVerticle

下面给出官方示例代码:

public class MyVerticle extends AbstractVerticle {

  // Called when verticle is deployed
  public void start() {
  }

  // Optional - called when verticle is undeployed
  public void stop() {
  }

}

    通常你须要重写start方法。当Vertx部署这个verticle时候,它会调用start方法。当start方法执行完后,这个verticle就被认为已经启动了。

    你能够根据须要重写stop方法。stop方法会在这个verticle卸载(undeployed)的时候调用。当stop方法执行完,这个verticle就被认为是中止的。

  • 异步Verticle的启动和中止

    你可能想要在start函数内执行耗时比较长的操做或者你想当start函数执行完后,而且某个事情已经发生了(例如等待其余的verticle启动),这个verticle才被认为是启动的。

    上文说过,你不能阻塞线程。那么应该怎么作?

    答案是实现异步的start方法。异步的start方法须要一个Future参数。当异步的start方法执行完后,verticle不会被认为是已经启动的。等你已经执行完阻塞代码(耗时比较长的),经过设置传入的Future参数的状态代表你已经执行完。

    下面给出官方示例代码:

public class MyVerticle extends AbstractVerticle {

  public void start(Future<Void> startFuture) {
    // Now deploy some other verticle:

    vertx.deployVerticle("com.foo.OtherVerticle", res -> {
      if (res.succeeded()) {
        startFuture.complete();
      } else {
        startFuture.fail(res.cause());
      }
    });
  }
}

    相似的,也有一个异步的stop方法。

public class MyVerticle extends AbstractVerticle {

  public void start() {
    // Do something
  }

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

提示:你不须要手动卸载由某个verticle(记为A)启动的verticle(记为As)。当你卸载了A,As会由Vertx自动卸载。

  •  Verticle的类型

    有三种类型的verticle:

     1. 标准Verticle(standard verticles)

这是最多见和有用的类型。它一般使用event loop线程执行。具体内容在下一部分讲。

     2. 工做者Verticle(worker verticles)

这种类型使用工做线程执行,一个实例不会被多个线程并发运行。

     3. 多线程工做者Verticle(multi-threaded worker verticles)

这种类型使用工做线程执行,一个实例能够被多个线程并发执行。

  • 标准Verticle(Standard verticles)

    当标准的verticle被建立时,它会被分配给一个event loop 线程。verticle的启动方法(void start() )由这个event loop调用。当你在event loop中调用在vertx core API中能够接受一个处理函数(handler)的任何方法时,vertx会保证这些处理函数(handler)将由相同的event loop执行。

在event loop中调用在vertx core API.....

     这句话个人理解是:event loop(记为A)执行了一个方法,这个方法调用了一个接受处理函数(handler)的方法。那么之后某个时刻这些处理函数(handler)被调用时,vertx保证是由A调用的。

    下面给出原文:

Standard verticles are assigned an event loop thread when they are created and the start method is called with that event loop. When you call any other methods that takes a handler on a core API from an event loop then Vert.x will guarantee that those handlers, when called, will be executed on the same event loop.

     这意味着咱们能够保证verticle实例中的所有代码老是由相同的event loop 执行的(固然,前提是你没有建立本身的线程并调用它)。

    也意味着你能够把你的应用当作单线程写,至于线程的扩展、并发等问题就交由vertx处理。你不用再担忧同步和稳定性的问题,你也能够避免条件竞争(race conditions)和死锁(deadlock)等问题,而这些问题在编写传统的多线程应用时是很常见的。

  • 工做者verticles(Worker verticles)

    工做者verticles(worker verticles)和标准verticles(standard verticles)是相似的。不一样的是,工做者verticles(worker verticles)不是由event loop执行,而是用一个来自vertx工做者线程池( Vert.x worker thread pool)的线程执行。

    工做者verticles(worker verticles)是为调用阻塞代码而设计的,因此它不会阻塞任何event loops。

    若是你不想使用工做者verticles(worker verticles)运行阻塞代码,你也能够在event loop中直接运行内联的阻塞代码。(内联的阻塞代码就是指上文所说的使用excuteBlocking运行阻塞代码)

    若是你想把一个verticle部署为工做者verticle(worker verticle),你可使用setWorker

      

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

    工做者verticle(worker verticle)实例是从不被多于一个线程并发的执行,不过它能够由不一样的线程在不一样时刻执行。

  • 多线程工做者verticles(multi-threaded worker verticles)

    一个多线程工做verticle (multi-threaded worker verticle)和普通的工做者verticle(worker verticle)相似,不过它能够被不一样的线程并发执行。

    警告:多线程工做verticles(multi-threaded worker verticle)是一个高级的特性。大多数应用是不须要使用它的。由于使用标准的java技术开放多线程程序时,为了保持verticles(多线程工做者verticles)状态的一致性,你必须特别当心在这些verticles(多线程工做者verticles)中的并发

  • 以编程的方式部署verticles

    你可使用deployVerticle方法部署一个verticle。使用这类方法你须要指定verticle的名称或者能够传递一个你已经建立好的verticle实例。

注意:仅能够在java中以传递verticle实例的方式部署verticle(这个框架还有其余语言版的,具体能够看官网:Vert‘x官方网

Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);

     你也能够指定verticle名称的方式部署verticle。

    verticle的名称是用于查找特定的VerticleFactory(这个东西用于实例化实际的verticle实例)。

    不一样的verticle工厂(verticle factory)用于实例化不一样语言版本的verticle,这么作的缘由有不少,例如为了在运行时加载、获取来自maven的services或者verticles。

    这容许你部署使用任何语言编写的verticle(固然,前提是vertix支持这种语言)。

    这有一个部署一些不一样类型的verticle的例子:

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

// Deploy a JavaScript verticle
vertx.deployVerticle("verticles/myverticle.js");

// Deploy a Ruby verticle verticle
vertx.deployVerticle("verticles/my_verticle.rb");
  • 从verticle名字到verticle 工厂(verticle factory)的映射规则

    在“使用verticle的名字部署verticle“这种方式中,verticle的名字是用于选择实例化verticle的verticle工厂(verticle factory)。

    verticle的名字能够有一个前缀,这个前缀是一个后面跟着一个冒号的字符串。若是添加这个前缀,vertic将根据这个前缀选择实例化verticle的verticle工厂(verticle factory)。

js:foo.js // 使用 JavaScript verticle factory groovy:com.mycompany.SomeGroovyCompiledVerticle // 使用 Groovy verticle factory service:com.mycompany:myorderservice // 使用 service verticle factory

    若是没有添加前缀,vertx 将根据后缀选择实例化verticle的verticle工厂(verticle factory)。

foo.js // Will also use the JavaScript verticle factory

SomeScript.groovy // Will use the Groovy verticle factory

     若是既没有前缀也没有后缀,vertix 将假定这个名字是java的类的全名(包名加类名?)并尝试实例化它。

  • verticle 工厂(verticle factory)是怎么定位的?

    大多数verticle 工厂是从类路径加载并在vertic启动的时候注册。

    若是你愿意,你也可使用registerVerticleFactoryunregisterVerticleFactory注册和注销verticle工厂(verticle factory)。

  • 等待部署的完成

    verticle 的部署是异步的,也许会在调用的deploy方法已经return后才完成(完成部署)。

    若是你想在部署完成的时候通知你,你能够注册一个处理函数(handler):

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

    若是部署成果,这个处理函数(handler)会被传递一个deployment ID字符串。

    若是你之后想卸载这个verticle,可使用对应的deployment ID进行卸载操做。

  • 卸载verticle

    可使用undeploy卸载已部署的verticle。

    卸载也是异步的,全部若是你想卸载完成的时候通知你,你能够指定一个处理函数(handler):

vertx.undeploy(deploymentID, res -> {
  if (res.succeeded()) {
    System.out.println("Undeployed ok");
  } else {
    System.out.println("Undeploy failed!");
  }
});
  • 指定verticle实例的数量

    当使用verticle的名字部署时,你能够指定你想要部署的verticle的数量。

DeploymentOptions options = new DeploymentOptions().setInstances(16);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);

    这对于在多核的机器上扩展是颇有用的。好比说,你可能有一个web-server verticle要部署,而且你的服务器是多核的, 很天然的你会想部署多个实例从而充分利用所有核心(cpu的核)。

  • 设置verticle的配置

    能够在部署的时候将JSON格式的配置传给verticle

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

    这个配置能够经过Context对象或者直接使用config方法获取。

    配置会以JSON对象的形式返回,所以你能够像下面这么获取配置数据

System.out.println("Configuration: " + config().getString("name"));
  • 在verticle中访问环境变量

    使用java API获取环境变量和系统属性是很简单的

System.getProperty("prop");
System.getenv("HOME");
  • verticle 隔离组

    默认状况下,vertx有一个扁平的路径(flat classpath)。例如,当vertx部署verticle时,它使用如今的类加载器(classloader)而不是建立一个新的。在大多数状况下,这是最简单,清晰,明了的作法。

    然而,在一些状况下,你也许想部署一个verticle。在你的应用中,这个verticle的class是和其余的分离的。(我理解的意思是:你写了一个verticleA,部署了两个verticleA实例,你想再部署一个verticleA实例,而且这个实例对应的class和另外两个的class不同。这里涉及java虚拟机的一些东西详细解释后续补充)。

    还有一个例子,例如,若是你想在同一个vertix实例中部署两个类名相同版本不一样的verticle,或者你有两个使用同一个类库的不一样版本的verticle。

    当使用一个隔离组时,你须要使用setIsolatedClasses设置你想隔离的类名。类名能够是完整的全限定名,例如:com.mycompany.myproject.engine.MyClass或者是带有通配符的类名(这种会匹配包和子包中任何类),例如com.mycompany.myproject.*会匹配到包com.mycompany.myproject以及其子包下的任何类。

    须要注意的是,只有匹配到的类会被隔离,其他不被隔离的类将被当前的类加载器(class loader)加载。

   若是你想加载不在主类路径上的类或者资源,你可使用setExtraClasspath设置额外的类路径。

警告:须要谨慎的使用这个功能。类加载器多是个蠕虫罐子(装有许多虫子(bug)),它可能会使debug十分困难。

    这里有一个使用隔离组去隔离verticle部署的例子:

DeploymentOptions options = new DeploymentOptions().setIsolationGroup("mygroup");
options.setIsolatedClasses(Arrays.asList("com.mycompany.myverticle.*",
                   "com.mycompany.somepkg.SomeClass", "org.somelibrary.*"));
vertx.deployVerticle("com.mycompany.myverticle.VerticleClass", options);
  • 高可用性

    verticle能够以高可用性(High Availability)的方式部署。在这种方式中,当一个vertx实例中的verticle忽然挂了,集群上的其余vertx实例会从新部署这个verticle。

    使用高能够性方式运行verticle,只须要在后面加上 -ha

vertx run my-verticle.js -ha

    当启用高可用性,再也不须要加上 -cluster

    更多关于高可用性功能和配置的细节在高可用性和故障转移部分.

  • 在命令行中运行verticle

    你能够经过“添加对vertx core 类库的依赖“这种通常的方式在你的项目中直接使用vertx。

    然而,你也可使用命令行直接运行vertx 的verticle。

    为了作到这些,你须要下载和安装vertx distribution,而且把bin目录地址添加到系统环境变量PATH中。固然,你也须要安装java 8 JDK。

注意:为了支持编译java代码,JDK是必须的。

     安装完以上所说的东西后,你就可使用vertx run命令运行verticle了。这里有一些例子:

# Run a JavaScript verticle
vertx run my_verticle.js

# Run a Ruby verticle
vertx run a_n_other_verticle.rb

# Run a Groovy script verticle, clustered
vertx run FooVerticle.groovy -cluster

    你甚至能够先不编译,直接运行verticle的java源码。

vertx run SomeJavaSourceFile.java

    vertx 会在运行它前,动态的编译这个java源码文件。这是对于快速创建原型和部署demo来讲是很是有用的。

   关于执行vertx命令时的所有可选值的资料,能够经过在命令行中输入vertx获取。

  • 退出vertx

    持有vertx实例的线程不是守护线程,全部它会阻止JVM退出。(涉及一些JVM的知识,后续补充

    若是你正以嵌入的方式使用vertx,而且已经使用完了,你能够调用close来结束它。

    这将会关闭所有内部线程池,关闭其余资源和容许JVM退出。

  • 上下文对象(Context object)

    当vertx传递一个事件(event)给一个处理方法(handler)或者调用verticle的start,stop方法时,这些方法会接受一个Context。一般一个context是一个event-loop context而且它捆绑一个特定的event loop 线程。全部关于context的操做老是发生在相同的event loop 线程。至于工做者verticle和运行内联阻塞代码,会有一个worker context与之关联,这些操做都由worker线程池里的线程运行。

    可使用getOrCreateContext获取context

Context context = vertx.getOrCreateContext();

    若是当前线程已经和一个context关联,该线程会重用这个context对象。若是一个新的context实例被建立,你能够测试你所接收到的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中异步运行代码。换句话说,你提交一个任务(task),这个任务将在相同的context中运行。

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

     有时候在相同的context中运行的处理函数(handler)可能想共享数据。这个context对象提供存储和获取数据(数据应该是在这个context对象中分享的,也就是没法操做在其余context对象分享的数据)的方法。例如,能够传递数据给经过runOnContext运行的操做。

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

    你可使用context对象的config方法得到verticle configuration。更多内容请看传递configuration 给一个verticle(Passing configuration to a verticle)章节。

  • 执行按期和延迟操做

    在vertx中,想按期或者延迟执行一些操做是很常见的。

    在标准verticle(standard verticle)中,你不能简单的经过休眠线程来到达延迟执行的目的,由于这样会阻塞event loop 线程。

    不过你可使用vertx 的定时器(timer)。定时器(timer)能够是一次性(one-shot)或者是周期性的(periodic)。咱们将会对二者进行详细说明。

  • 一次性的定时器(one-shot Timer)

    一个一次性定时器(one-shot Timer)会在未来某个时间调用处理函数(handler)。

    可使用setTimer方法设置一个一次性的定时器(one-shot Timer)。这个方法接受两个参数,分别是延迟时间(以毫秒为单位)和处理函数(handler)。

long timerID = vertx.setTimer(1000, id -> {
  System.out.println("And one second later this is printed");
});

System.out.println("First this is printed");

    setTimer方法返回的long值是这个定时器(Timer)的id,这个id是惟一的。你可使用这个id来取消定时器(Timer)。设置定时器(Timer)时,传入的处理函数(Handler)也会接收一个long参数,这个参数就是定时器的id。

  • 按期定时器(Periodic Timer)

    你可使用setPeriodic来设置一个按期定时器(Periodic Timer)。

    setPeriodic方法一样会放回一个long值,该值的含义和用法与setTimer相同。

    setPeriodic接收的参数和setTimer和大体相同,不一样的是接收的参数“延迟时间(以毫秒为单位)“表明隔多久执行一次处理函数(Handler)。

    须要注意的是,这种定时器(指按期定时器)是按期执行的。若是按期执行的操做持续时间较长,定时器可能会持续运行(例如设置一个隔一小时执行一次的定时器,而定时执行的操做刚好须要一小时完成)。还有更糟糕的状况是,定时器可能会叠加运行(例如,以一小时为周期的定时器,定时执行的操做须要耗时2小时)。

    在这种状况,你应该考虑使用setTimer。当操做完成后,再设置下一个定时器。

long timerID = vertx.setPeriodic(1000, id -> {
  System.out.println("And every second this is printed");
});

System.out.println("First this is printed");
  • 取消定时器

    能够调用cancelTimer取消定时器,这个方法须要传入定时器id。

vertx.cancelTimer(timerID)
  • 自动清理verticle中的定时器(Timer)

    若是你在verticle内建立了定时器,那么当这个verticle被卸载时,在这个verticle内建立的定时器也会被清理掉。

  • verticle的工做线程池(Verticle worker pool)

    verticle使用vertx的工做线程池执行阻塞操做,例如executeBlocking和工做者verticle(worker verticle)。

    能够在部署选项中指定不一样的工做池:

vertx.deployVerticle("the-verticle", new DeploymentOptions().setWorkerPoolName("the-specific-pool"));

9.事件总线(Event Bus)

    事件总线(event bus)是vert.x的神经系统。

    每一个vertx实例都拥有一个单独的事件总线实例,你可使用eventBus方法得到它。

    事件总线容许你应用程序的不一样部分相互通讯,即便不一样部分的开发语言不一样,所处的vertx实例不一样,也可以相互通讯。

    它甚至能够被桥接,从而容许运行在浏览器的javaScript代码在相同的事件总线(event bus)上通讯。

    事件总线(event bus)造成了分布式的跨越多个服务器端点和多个浏览器的对等消息系统。

   事件总线支持发布/订阅,端对端,请求-响应式的消息。(The event bus supports publish/subscribe, point to point, and request-response messaging.)

    事件总线(event bus)的API是很是简单的。它主要上涉及注册、注销处理函数(Handler),发生和发布消息等操做。

    首先介绍一些概念。

  • 概念部分

    1.地址

    在事件总线中,消息将被送到一个地址。

    vertx没有选用复杂的选址方案,在vertx中,一个地址就是一个简单的字符串(String)。任何字符串均可以被定义为地址。不够,使用某种格式命名地址是个明智的选择。例如使用限定名命名地址。

    一些有效的地址命名例子:news.feed1,acme,games.pacman,sausages,X.

    2.处理函数(handler)

    消息是由处理函数(handler)接收。你能够在一个地址注册了一个处理函数(handler)。

一个地址能够注册多个处理函数(handler)。一个处理函数能够在多个地址上注册。

    3.发布/订阅消息

    事件总线支持发布消息。消息会被发布到一个地址。“发布”意味着传递消息到注册在对应地址上的全部处理函数(handler)。

    4.端对端和请求-响应式(request-response)的消息

    事件总线也支持端对端的消息。

    这种模式下,消息会被发送到一个地址。vertx接着会把消息路由到这个地址上的其中一个处理函数(handler)上。

    若是在这个地址注册了多个处理函数(handler),vertx将会使用非严格的轮询(non-strict round-robin)算法选择其中的一个。

    在端对端的模式下, 能够在发送消息时,能够设置一个处理函数(handler)来处理回复(若A发消息给B,B可能会对该消息进行回复,也就B接收并处理消息后向A发一条回复消息)。

    当一个消息已经被接收而且处理,接受者能够决定是否回复这个消息。若是接收者回复了消息,发送者设置的处理函数(handler)将被调用。

    在上述情景下,若是发送者接收到了回复消息,发送者也能选择是否进行回复。发送者和接收者的对话能够无限延续,而且对话能够在两个不一样的verticle中进行。

    这是一种普通的消息模式,这种模式被称为请求-响应(request-response)模式。

    5.最大努力交付(Best-effort delivery)

    vertx会尽可能发送消息,不会主动的丢弃消息。这种行为被称为最大努力交付(best-effor delivery)。

    然而,在事件总线所有或者部分的失败例子中,存在消息遗失的可能。

    若是你的应用在意消息的丢失,你应该确保你编写的处理函数(handler)具备幂等性(idempotent)和回复和重试发送。(幂等性资料

   6.消息的类型

    开箱即用的vertx容许消息是任何原始和简单的类型,例如String 或者 buffers。

    不过,在vertx中以json形式发送消息是方便和常见的。

    在全部vertx支持的语言中,JSON是很是容易建立,读取和解析的。因此JSON已经成为一种vertx的通用语(lingua franca)。

    固然,若是你不想用JSON,vertx不会强迫你用。

    事件总线是很是灵活的,它也支持发送抽象任意对象。要作到发送任意对象,你须要为你想发送的对象定义codec

  • 事件总线(Event Bus)API

    1.获取事件总线(event bus)

    你能够像下面这样获取事件总线(event bus)引用

EventBus eb = vertx.eventBus();

    这是一个vertx实例的惟一的事件总线的实例。

    2.注册处理函数

    注册处理函数(handler)的最简单方式是使用consumer。这有个例子:

EventBus eb = vertx.eventBus();

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

    当一个消息到达,你的处理函数(handler)会被调用,而且被传入这个消息。

    调用consumer()返回的对象是一个MessageConsumer实例。