Verticles
Vert.x俱有简单,伸缩,actor-like部署和并发模型的创新,这样可以省去自己去实现。
这个模型是完全可选的,Vert.x不强迫你使用这种方式创建应用,如果你不想那样做。
此模型没有被申明为严格的actor-model实现,但是它在并发,伸缩和部署方面都特别相似。
为了使用此模型,你编写verticle集合的代码。
Verticle是一个代码块,此代码块由Vert.x部署和运行。一个Vert.x实例维护N个事件循环线程(默认N为CPU核数*2)。Verticles可以使用Vert.x支持的任一语言编写,并且单一应用可以包含用多种语言编写的verticles.
可以将verticles想成一个Actor模型中的一个actor.
一个应用可能是这样一个典型组合,在同一时间同一Vert.x实例中运行多个verticles。不同的verticle实例通过向事件总线发送信息相互通迅。
编写Verticles
Verticle类必须实现Verticle接口。如果你喜欢你可以直接实现Verticle接口,但是通常是简单扩展抽象类AbstractVerticle.
这是一个verticle例子
public class MyVerticle extendsAbstractVerticle {
//Called when verticle is deployed
public void start() {
}
//Optional - called when verticle is undeployed
public void stop() {
}
}
正常情况,你上面例子,你将覆盖start方法。当Vert.x布署Verticles时,Vert.x将调用start方法,并且当此方法结束时,Veticle将被认为启动成功。
你可以选择性重写stop方法。这将被Vert.x调用,在verticle被御载时,stop方法完成执行,Verticle将被认为停止。
异步Vertical开始与停止
有时你想在你的Verticle启动的时候做一些费时的事情,并且不想这个verticle已经部署成功。例如,你想在start方法中部署其他veticles
你不能阻塞等待其他verticles在你的verticle的start方法中进行部署,因为这将打破黄金法则
如果这样,我们应怎样做呢?
方法是实现异步的start方法.此方法的这个版本用一个future作为参数。当这个方法返回时,此Veticle不会被认为部署完成。
在你做完你需要做的工作之后过一会(比如开始其他verticles),在完成或失败的future上调用完成通知你已经做完。
这儿是一个例子
public class MyVerticle extendsAbstractVerticle {
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方法。如果你想做一些耗时的Verticle清除工作可以使用它。
public void start() {
// Do something
}
public void stop(Future<Void> stopFuture) {
obj.doSomethingThatTakesTime(res -> {
if (res.succeeded()) {
stopFuture.complete();
} else {
stopFuture.fail();
}
});
}
信息:在verticle的stop方法中,你不必手工操作御载子verticle,这些子verticle由某个verticle启动。Vert.x在你父verticle御载时自动御载子verticles.
Verticle类型
有三个不同类型的Verticles:
标准Verticles:
这种类型是常用并且有用的类型,他们总是通过事件循不被执行。在下一节我们将对此讨论更多。
WorkerVerticles
此类型的verticle通过从workerpool中取出线程执行。一个verticle实例从不被多个线程并发执行(由单一线程执行)
多线程workerVerticles
此类Verticles从Workerpool中取得线程执行,一个verticle实例可被多个线程并发执行.
标准verticles
当标准Verticles在创建且start方法被事件循不调用,将被赋给一个事件循环线程。当你从事件循环中使用内核APIs调用持有处理器的方法时,Vert.x将保证这些处理器在同一个事件循环中被执行。
这意味着,我们能保证你的Verticles实例中的所有代码,将在同一个事件循环中执行(只要你不创建自己的线程并调用它)。
这意味着,你可以在你的应用中编写像单线程的代码,并且让Vert.x关心线程和伸缩。不需要关心异步和多变,并且你避免一些竞争条件和列锁,所以能熟练进行传统手工多线程应用开发。
Workerverticles
一个Work verticles类似于标准verticle,但是不是由事件循环执行,而是从Vert.x 工作线程池中获取线程。
Worker verticles被设计于调用阻塞代码。因为他们不阻塞任何事件循环。
如果你不想使用Worker verticle运行阻塞代码,你也可以直接在一个事件循环中执行内联阻塞代码。
如果你想布署一个Worker verticle 你可以使用setWorker方法完成
DeploymentOptions options = newDeploymentOptions().setWorker(true);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle",options);
Worker verticle 实例从不被Vert.x并发多线程执行,但是可以被不同的线程在不同的时间执行。
多线程worker verticles
一个多线程worker verticle与普通的worker verticle类似,除了它可以并发被多线程执行。
警示:多线程worker verticle是一个高级特性,大多数应用并不需要。因为在这此verticles中并发运行,你必须非常小心保持verticle处于一致状态,正如多线程编程中标准的java技术。
程序化布署verticles
可以使用deployVerticle方法中的任一个布署Verticle,你可以指定一个verticle名称或者,传入一个已经创建好的verticle实例。
备注:仅有Java可以布署Verticle实例。
Verticle myVerticle = new MyVerticle();
vertx.deployVerticle(myVerticle);
可以指定Verticle名称进行布署。
Verticle名称被用于查找特定的用于初始化实际Verticle实例的VerticleFactory。不同的Verticle工厂用于不同的语言初始化不同的Verticle实例,也用于其他原因如装载服务,在运行时从Maven获取verticles. 这就允许布署Vert.x支持的语言编写的verticle.
下面是一个布署不同类型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(s)时,名称用于选择一个初始化verticle(s)的verticle工厂。Verticle名称可以有个前缀一个后面带冒号字符串,如果存在则用于寻找verticle工厂,例如:
js:foo.js // Use the JavaScript verticlefactory
groovy:com.mycompany.SomeGroovyCompiledVerticle// Use the Groovy verticle factory
service:com.mycompany:myorderservice //Uses the service verticle factory
如果存在前缀,Vert.x将会查找一个后缀并且用于查找verticle工厂,例如:
foo.js // Will also use the JavaScriptverticle factory
SomeScript.groovy // Will use the Groovyverticle factory
如果没有前缀或者后缀,Vert.x将假设名称是一个全java类路径并且偿试初始化。
怎样定位Verticle工厂?
大多数Verticle工厂是在Vert.x启动时从类路径中加载并注册。也可以用程序的方式注册或者解注册Verticle,这需要调用registerVerticleFactory和unregisterVerticleFactory两个方法。
等待布署完成
Verticle布署是异布的 并且可以在调用布署返回之后需要一些时间完成布署。如果你想在Verticle布署完成之后得到通知,可以在布署时指定一个处理器:
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle",res -> {
if (res.succeeded()) {
System.out.println("Deployment id is:" + res.result());
} else {
System.out.println("Deploymentfailed!");
}
});
如果布署完成,完成处理器将会得到一个包含布署ID的字符串结果。布署ID在稍后御载verticle方法中会用到。
御载Verticle
可以使用undeploy方法进行verticle御载。御载本身是异步的,因此,如果你想得到御载完成的通知,你可以指定一个完成处理器:
vertx.undeploy(deploymentID, res -> {
if(res.succeeded()) {
System.out.println("Undeployed ok");
}else {
System.out.println("Undeploy failed!");
}
});
指定Verticle实例数量
当用Verticle名称布署verticle时,你根据需要可以指定verticle实例的数量:
DeploymentOptions options = newDeploymentOptions().setInstances(16);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle",options);
这为方便伸缩到不同的CPU运算内核是有用的。例如,你可能有一个web-server的Verticle布署到多核的机器上,所以你想布署多个verticle实例,充份利用所有CPU运算核心。
向verticle传递配置
JSON格式的配置可以在布署时传给verticle:
JsonObject config = newJsonObject().put("name", "tim").put("directory","/blah");
DeploymentOptions options = newDeploymentOptions().setConfig(config);
vertx.deployVerticle("com.mycompany.MyOrderProcessorVerticle",options);
然后可能通过Contex对象或者直接使用config方法得到这个配置。因为配置以JSON对向被返回,所以可用下面的代面进行检查:
System.out.println("Configuration:" + config().getString("name"));
在Verticle中访问环境变量
使用Java API可以访问环境变量和系统属性:
System.getProperty("prop");
System.getenv("HOME");
Verticle隔离组
默认Vert.x有一个扁平的类路径(classpath).例如,当Vert.x布署verticles时使用当前的类加载器(classloader),Vert.x并不创建一个新的类加载器。在大部分情况下,这是在做很简单,很清晰,明智的的事。然而,在有些情况下,你也许想在应用中布署一个隔离于其他verticles的verticle。这是或许是一种情况,例如,你想在同一个Vert.x实例中布署一个具有相同名称两个不同版的verticle,或者你有两个不同的verticle,它们都使用了同一个jar包的不同版本。
在使用隔离组时,你提供一个需要隔离的类名的列表作为setIsolatedClasses方法的参数,类名必符是有效全路径类名,如com.mycompany.myproject.engine.MyClass或者是一个通配符用于匹配本包或者子包内任何类,如,com.mycompany.myproject.*,会匹配com.mycompany.myproject包或子包中的任意类。注意只有匹配的类将全隔离,其他的类将有当前类加载器加载。外部类可以通过setExtraClasspath方法提供,所以你可以添加需要加载的当前不在主类路径中的类和资源。
警示:小心使用此特性。类加载器是系统的bug瓶子,而且在其他东西上,让调试团难。
这是一个使用隔离组隔离verticle的例子:
DeploymentOptionsoptions = 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可以进行高可用布署。在那个上下文中,一个部署前瞻verticle的vert.x实例突然死亡,这个verticle会重新布署到这个群集中的其他Vert.x实例上。为了运行verticle高可用,添加-ha开关项:
vertx run my-verticle.js –ha
在使用可高用时,不需要添加-cluster参数。关于更多的高可用特性及设置可参见高可用和容错小节。
从命令行运行Verticles
可以通过直接在maven或Gradle项目中添加Vert.x 核心库的正常方式使它。但是也可直接从命令行运行Vert.x的verticle. 这样做,需要下载并安装Vert.x分发版,并且将bin路径添加到你的PATH环境变量中。并确认JDK8可执行。
注意:需要JDK支持自由编译Java代码。
现在你可以用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
你甚至于不用编译就运行Java源代码的Verticle;
Vertx run SomeJavaSourceFile.java
Vert.x将在运行时编译源代码然后运行。这对于快速原型设计verticlet和演示确实有用。不需要在运行前安装Maven或者Gradle。
导至Vert.x退出
Vert.x实例维护着线程,而非精灵线程,所以他们将阻止JVM退出。如果你正在嵌入Vert.x并且已经使用完,可以调用call方法关闭它。这会关闭所有内部线程池及所有其他资源,并允许JVM退出。
上下文(Context)对象
在Vert.x向一个处理器提供事件或者调用某个Verticle的start/stop方法时,此次执行将与上下文关联。通常上下文指的是事件循环上下文并且与特定的事件循环线程绑定。如果是worker verticle并且运行内联阻塞代码,一个worker上下文与此操作关联。Worker上下文来自于worker线程池。
使用getOrCreateContext方法得到上下文:
Context context =vertx.getOrCreateContext();
如果当前线程已经关联到某个上下文,它将重要此上下文对象。如果不是创建一个新的上下文实例,可以检测你获取的上下文类型:
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 - multithreaded worker");
} else if (! Context.isOnVertxThread()) {
System.out.println("Context not attached to a thread managed byvert.x");
}
在收到上下文对象时,可以异步在此上下文中执行代码。换句话说,就是可以提交一个稍后将执行的任务到相同的上下文中。:
vertx.getOrCreateContext().runOnContext((v) -> {
System.out.println("This will be executed asynchronously in thesame context");
});
当在同一个上下文中运行多个处理器时,处理器间可以共享数据。上下文对象提供了存取共享数据的方法。例如,通过runOnContext方法将数据传到某个行为。
final Context context = vertx.getOrCreateContext();
context.put("data","hello");
context.runOnContext((v) -> {
String hello = context.get("data");
});
通过上下文对象的config方法,也可访问verticle的配置。查看传递配置到verticle小节获取更多细节。
执行周期性和延迟性动作
在Vert.x中执行延迟任务和周期任务是很常见的。在标准verticle中,仅需要使线程暂停就则引入延迟,因为可以阻塞事件循环线程。另外可以使用Vert.x定时器,定时器可以是一次性的或者周期性的。我们将对二者进行讨论。
一次性定时器
一次性定时器在特定的延迟后调用一个事件处理器,定时用毫秒表示。使用setTimer方法传入一个延迟时间和一个处理器设置一次性处理器。
long timerID = vertx.setTimer(1000, id-> {
System.out.println("And one second later this is printed");
});
System.out.println("First this isprinted");
周期定时器
使用setPeriodic方法设置一个周期触发的定时器。初始化延迟与周期相同。setPeriodic方法返回值是一个定时器ID(long),这个ID在定时器取消时要用到。唯一的定时器ID也会作为参数传给定时器事件处理器。记住定时器将基于周期性触发。如果周期任务处理将消耗很长时间,定时器事件将持续运行,最差的情况是定时器事件会累加。在这种情况下,最好用setTimer来代替。一旦作任务完成,你可以设置下一个定时器。
long timerID = vertx.setPeriodic(1000, id-> {
System.out.println("And every second this is printed");
});
System.out.println("First this isprinted");
取消定时器
为了取消周期定时器,可以调用cancelTimer方法,并传入定时器ID。例如
vertx.cancelTimer(timerID);
Verticle中自动清除定时器
如果你在Verticles内部创建定时器,这些定时器将在Verticle御载时自动关闭。
Verticle工作池
Verticle用Vert.x工作线程池执行阻塞动作,如executeBlocking或者worker verticle. 可以在在布署verticle指定一个工作线程池。
vertx.deployVerticle("the-verticle",new DeploymentOptions().setWorkerPoolName("the-specific-pool"));