Vert.x Verticle简介、简单使用及相关用法
Verticle
Vert.x 通过开箱即用的方式提供了一个简单便捷的、可扩展的、类似 Actor Model 的部署和并发模型机制。我们可以用此模型机制来保管您自己的代码组件。
这个模型不能说是严格的 Actor 模式的实现,但它确实有相似之处,特别是在并发、扩展性和部署等方面。
要使用该模型,需要将我们的代码使用 Verticle封装起来。
Verticle 是由 Vert.x 部署和运行的代码块。默认情况一个 Vert.x 实例维护了N(默认情况下N = CPU核数 x 2)个 Event Loop 线程。Verticle 实例可使用任意 Vert.x 支持的编程语言编写,而且一个简单的应用程序也可以包含多种语言编写的 Verticle。
一个应用程序通常是由在同一个 Vert.x 实例中同时运行的许多 Verticle 实例组合而成。不同的 Verticle 实例通过向 Event Bus 上发送消息来相互通信。
编写 Verticle
Verticle 的实现类必须实现 Verticle
接口。在正常情况下我们都是通过继承Verticle的子类 AbstractVerticle
来实现的。
package cn.yzstu.first;
import io.vertx.core.AbstractVerticle;
/**
* @author Baldwin
*/
public class VerticleCode extends AbstractVerticle {
@Override
public void start() throws Exception {
super.start();
}
@Override
public void stop() throws Exception {
super.stop();
}
}
一般情况下我们需要在自己的Verticle中重写start和stop方法,当然也可以选择调用父类中的start与stop。在Verticle被部署时,会执行start方法,start执行完成之后这个Verticle才是真正启动起来了。当Vert.x 撤销一个 Verticle 时stop会被调用,这个方法执行完成后 Verticle 就变成已停止状态了。
Verticle 异步启动和停止
不能在 start
方法中阻塞等待其他的 Verticle 部署完成,这样做会破坏 黄金法则。
AbstractVerticle
提供了异步版本的start。有些时候 Verticle 启动会耗费一些时间,我们想要在这个过程做一些事,并且这些事并不想等到所有Verticle部署完成过后再发生。如:在 start
方法中部署其他的 Verticle。
可以实现 异步版本 的 start
方法来做这个事。这个方法会以一个 Future
作参数被调用。方法执行完时,VerticleExample实例并没有部署好(状态不是 deployed)。稍后,您完成了所有您需要做的事(VerticleExample启动完成),就会调用startFuture.complete()或startFuture.fail(res.cause())来说明我们这个Verticle是否部署完成。
@Override
public void start(Future<Void> startFuture) throws Exception {
// 现在部署其他的一些verticle
vertx.deployVerticle(VerticleExample.class.getName(), res -> {
if (res.succeeded()) {
startFuture.complete();
} else {
startFuture.fail(res.cause());
}
});
}
同样的,这儿也有一个异步版本的 stop
方法,如果需要做一些耗时的 Verticle 清理工作,可以使用它。
@Override
public void stop(Future<Void> endFuture) throws Exception {
obj.doSomethingThatTakesTime(res -> {
if (res.succeeded()) {
stopFuture.complete();
} else {
stopFuture.fail();
}
});
}
不需要在一个 Verticle 的 stop
方法中手工去撤销启动时部署的子 Verticle,当父 Verticle 在撤销时 Vert.x 会自动撤销任何子 Verticle。
Verticle 种类
- Stardand Verticle:这是最常用的一类 Verticle —— 它们永远运行在 Event Loop 线程上。
- Worker Verticle:这类 Verticle 会运行在 Worker Pool 中的线程上。一个实例绝对不会被多个线程同时执行。
- Multi-Threaded Worker Verticle:这类 Verticle 也会运行在 Worker Pool 中的线程上。一个实例可以由多个线程同时执行(因此需要开发者自己确保线程安全)。
Standard Verticle
当 Standard Verticle 被创建时,它会被分派给一个 Event Loop 线程,并在这个 Event Loop 中执行它的 start
方法。当在一个 Event Loop 上调用了 Core API 中的方法并传入了处理器时,Vert.x 将保证用与调用该方法时相同的 Event Loop 来执行这些处理器。
这意味着我们可以保证在 Verticle 实例中 所有的代码都是在相同Event Loop中执行(只要不创建自己的线程并调用它!)
同样意味着可以将应用中的所有代码用单线程方式编写,让 Vert.x 去考虑线程和扩展问题。不用再考虑 synchronized 和 volatile 的问题,也可以避免传统的多线程应用经常会遇到的竞态条件和死锁的问题。
Worker Verticle
Worker Verticle 和 Standard Verticle 很像,但它并不是由一个 Event Loop 来执行,而是由Vert.x中的 Worker Pool 中的线程执行。
Worker Verticle 被设计来调用阻塞式代码,它不会阻塞任何 Event Loop。
如果不想使用 Worker Verticle 来运行阻塞式代码,也可以在一个Event Loop中直接使用内联阻塞式代码。
若您想要将 Verticle 部署成一个 Worker Verticle,您可以通过 setWorker
方法来设置:
DeploymentOptions options = new DeploymentOptions().setWorker(true);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle", options);
Worker Verticle 实例绝对不会在 Vert.x 中被多个线程同时执行,但它可以在不同时间由不同线程执行。
Multi-threaded Worker Verticle
一个 Multi-threaded Worker Verticle 近似于普通的 Worker Verticle,但是它可以由不同的线程同时执行。
警告:Multi-threaded Worker Verticle 是一个高级功能,大部分应用程序不会需要它。由于这些 Verticle 是并发的,必须小心地使用标准的Java多线程技术来保持 Verticle 的状态一致性。
编程方式部署Verticle
可以指定一个 Verticle 名称或传入您已经创建好的 Verticle 实例,使用任意一个 deployVerticle
方法来部署Verticle。
请注意:通过 Verticle 实例 来部署 Verticle 仅限Java语言。
Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);
同样可以通过指定 Verticle 的 名称 来部署它。
这个 Verticle 的名称会用于查找实例化 Verticle 的特定 VerticleFactory
。
不同的 Verticle Factory 可用于实例化不同语言的 Verticle,也可用于其他目的,例如加载服务、运行时从Maven中获取Verticle实例等。
这可以实现部署用任何使用Vert.x支持的语言编写的Verticle实例。
这儿有一个部署不同类型 Verticle 的例子:
vertx.deployVerticle("cn.yzstu.VerticleExample");
vertx.deployVerticle(VerticleExample.class.getName());
// 部署JavaScript的Verticle
vertx.deployVerticle("verticles/myverticle.js");
// 部署Ruby的Verticle
vertx.deployVerticle("verticles/my_verticle.rb");
Verticle名称到Factory的映射规则
当使用名称部署Verticle时,会通过名称来选择一个用于实例化 Verticle 的 Verticle Factory。
Verticle 名称可以有一个前缀 —— 使用字符串紧跟着一个冒号,它用于查找存在的Factory,参考例子。
js:foo.js // 使用JavaScript的Factory
groovy:cn.yzstu.VerticleExample // 用Groovy的Factory
service:cn.yzstu:myorderservice // 用Service的Factory
如果不指定前缀,Vert.x将根据提供名字后缀来查找对应Factory,如:
foo.js // 将使用JavaScript的Factory
SomeScript.groovy // 将使用Groovy的Factory
若前缀后缀都没指定,Vert.x将假定这个名字是一个Java 全限定类名(FQCN)然后尝试实例化它。
如何定位Verticle Factory?
大部分Verticle Factory会从 classpath 中加载,并在 Vert.x 启动时注册。
同样可以使用编程的方式去注册或注销Verticle Factory:通过 registerVerticleFactory
方法和 unregisterVerticleFactory
方法。
等待部署完成
Verticle的部署是异步方式,可能在 deploy
方法调用返回后一段时间才会完成部署。
如果想要在部署完成时被通知则可以指定一个完成处理器:
vertx.deployVerticle(VerticleExample.class.getName(), res -> {
if (res.succeeded()) {
System.out.println("Deployment id is: " + res.result());
} else {
System.out.println("Deployment failed!");
}
});
如果部署成功,这个完成处理器的结果中将会包含部署ID的字符串。这个部署 ID可以在之后撤销它时使用。
撤销Verticle
我们可以通过 undeploy
方法来撤销部署好的 Verticle。
撤销操作也是异步的,因此如果想要在撤销完成过后收到通知则可以指定另一个完成处理器:
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(VerticleExample.class.getName(), options);
这个功能对于跨多核扩展时很有用。例如,有一个实现了Web服务器的Verticle需要部署在多核的机器上,可以部署多个实例来利用所有的核。
向 Verticle 传入配置
可在部署时传给 Verticle 一个 JSON 格式的配置
JsonObject config = new JsonObject().put("name", "tim").put("directory", "/blah");
DeploymentOptions options = new DeploymentOptions().setConfig(config);
vertx.deployVerticle("cn.yzstu.VerticleExample", options);
传入之后,这个配置可以通过 Context
对象或使用 config
方法访问。
这个配置会以 JSON 对象(JsonObject
)的形式返回,因此可以用下边代码读取数据:
System.out.println("Configuration: " + config().getString("name"));
在 Verticle 中访问环境变量
环境变量和系统属性可以直接通过 Java API 访问:
System.getProperty("prop");
System.getenv("HOME");
Verticle 隔离组
默认情况,当Vert.x部署Verticle时它会调用当前类加载器来加载类,而不会创建一个新的。大多数情况下,这是最简单、最清晰和最干净。
但是在某些情况下,可能需要部署一个Verticle,它包含的类要与应用程序中其他类隔离开来。比如想要在一个Vert.x实例中部署两个同名不同版本的Verticle,或者不同的Verticle使用了同一个jar包的不同版本。
当使用隔离组时,需要用 setIsolatedClassed
方法来提供一个想隔离的类名列表。列表项可以是一个Java 限定类全名,如 cn.yzstu.VerticleExample
;也可以是包含通配符的可匹配某个包或子包的任何类,例如 cn.yzstu.*
将会匹配所有 cn.yzstu
包或任意子包中的任意类名。
请注意仅仅只有匹配的类会被隔离,其他任意类会被当前类加载器加载。
若想要加载的类和资源不存在于主类路径(main classpath),您可使用 setExtraClasspath
方法将额外的类路径添加到这里。
警告:谨慎使用此功能,类加载器可能会导致您的应用难于调试,变得一团乱麻(can of worms)。
以下是使用隔离组隔离 Verticle 的部署例子:
DeploymentOptions options = new DeploymentOptions().setIsolationGroup("mygroup");
options.setIsolatedClasses(Arrays.asList("cn.yzstu.*", "cn.yzstu.other.VerticleExample"));
vertx.deployVerticle("cn.yzstu.other.VerticleExample", options);
高可用性
Verticle可以启用高可用方式(HA)部署。在这种方式下,当其中一个部署在 Vert.x 实例中的 Verticle 突然挂掉,这个 Verticle 可以在集群环境中的另一个 Vert.x 实例中重新部署。
若要启用高可用方式运行一个 Verticle,仅需要追加 -ha
参数:
vertx run my-verticle.js -ha
当启用高可用方式时,不需要追加 -cluster
参数。
关于高可用的功能和配置的更多细节可参考高可用和故障转移。
从命令行运行Verticle
可以在 Maven 或 Gradle 项目中以正常方式添加 Vert.x Core 为依赖,在项目中直接使用 Vert.x。
但是,也可以从命令行直接运行 Vert.x 的 Verticle。
需要下载并安装 Vert.x 的发行版,并且将安装的 bin
目录添加到您的 PATH
环境变量中,还要确保您的 PATH
中设置了Java 8的JDK环境。
请注意:JDK需要支持Java代码的运行时编译(on the fly compilation)。
现在可以使用 vertx run
命令运行Verticle了,这儿是一些例子:
# 运行JavaScript的Verticle
vertx run my_verticle.js
# 运行Ruby的Verticle
vertx run a_n_other_verticle.rb
# 使用集群模式运行Groovy的Verticle
vertx run FooVerticle.groovy -cluster
甚至可以不必编译 Java 源代码,直接运行它:
vertx run SomeJavaSourceFile.java
Vert.x 将在运行它之前对 Java 源代码文件执行运行时编译,这对于快速原型制作和演示很有用。不需要设置 Maven 或 Gradle 就能跑起来!
欲了解有关在命令行执行 vertx
可用的各种选项完整信息,可以直接在命令行键入 vertx
查看帮助。
退出 Vert.x 环境
Vert.x 实例维护的线程不是守护线程,因此它们会阻止JVM退出。
如果通过嵌入式的方式使用 Vert.x 并且完成了操作,可以调用 close
方法关闭它。这将关闭所有内部线程池并关闭其他资源,允许JVM退出。
FAQ
黄金法则:不要阻塞Event Loop
Vert.x 的 API 都是非阻塞式的并且不会阻塞 Event Loop,但是这并不能避免我们自己的处理器中阻塞 Event Loop 的情况发生。如果阻塞了 Vertx
实例中的所有 Event Loop,我们的应用就挂掉了
所以不要这样做!这是一个警告!
这些阻塞做法包括:
Thead.sleep()
- 等待一个锁
- 等待一个互斥信号或监视器(例如同步的代码块)
- 执行一个长时间数据库操作并等待其结果
- 执行一个复杂的计算,占用了可感知的时长
- 在循环语句中长时间逗留