Vertx 3 核心手册之Verticles

Verticles目录大纲

  1. Verticle是什么
  2. 编写Verticle
  3. 异步Verticle的启动与停止
  4. Verticles类型
  5. 普通的Verticles
  6. 工作者Verticles
  7. 编程部署Verticles
  8. Verticle名称对应Verticle工厂的映射规则
  9. 如何定位Verticle工厂
  10. 等待部署完成
  11. 卸载Verticle部署
  12. 指定Verticle的实例数量
  13. 将配置传递给Verticle
  14. 在Verticle里面访问环境变量
  15. Verticle隔离组
  16. 高可用
  17. 命令行方式运行Verticles
  18. 引起Verticle退出
  19. 上下文对象
  20. 执行周期调度和延时动作
  21. Verticle工作池

Verticles是什么

Vert.x 提供了一个简单的,可伸缩的,类似actor的部署和并发模型,你可以使用它来编写自己的代码。

这个模式是可选的,如果你不想要使用这种模式,Vert.x 也不会强求你非用这种模式不可。

该模型并没有声称是一个严格的角色模型实现,但它确实有共同点,特别是在并发性,扩展性和部署方面。

要使用这个模型,你需要将你的代码设置为verticles。

Verticles的代码是通过Vert.x来运行和部署的。一个Vert.x实例默认维护了N个线程循环(event loop)线程(N默认是内核数量*2)。Verticles能够用Vert.x支持的任何语言,一个应用程序也可以使用多种语言来编写verticles。

你可以把Verticle作为一个 actor模型 中的actor。

一个应用程序通常同一时刻由许多verticle实例运行在相同的Vert.x实例中。

应用程序通常由同一个Vert.x实例中同时运行多个verticle实例组成。

应用程序由...组成。

多个verticle实例运行

在同一个Vert.x实例

应用程序通常由多个Verticle实例同一时间在Vert.x实例中运行组成。(ps:也就是一个Vert.x内包含了多个Verticle实例对象)。不同的verticle实例彼此交互发送消息通过 event bus

编写Verticle

Verticle类必须要实现 Verticle 接口。

如果你不想直接实现这个接口,通常继承至抽象类 AbstractVerticle 更简单哦。

下面举个verticle的栗子:

public class MyVerticle extends AbstractVerticle {
    
    // Called when verticle is deployed
    public void start() {
        
    }
    
    // Optional - called when verticle is undeployed
    public void stop() {
        
    }
}

通常和上文举的栗子一样你可能会覆盖 start 方法。

当 Vert.x 部署verticle时它会调用 start 方法,然后当方法已经完成后,verticle会考虑启动它。

你也可以选择覆写 stop 方法(可根据自己的需求来是否选择覆写该方法)。当 verticle 卸载的时候 stop 方法将会被调用,当这个方法已经完成,verticle将会考虑进行停止它。

异步Verticle的启动与停止

有时候你想在 verticle 启动中搞一些事情,这需要花些时间,在这种情况下你不喜欢 verticle 被部署。例如,你可能想要在 start 方法调用的时候部署其它的 verticle 。

在你的启动方法中你不能阻塞其它的verticle部署因为这可能会打破 黄金法则(Golden Rule)

因此如何是好呢?

这个方法就是实现异步 start 方法。这个 异步(asynchronous ) 方法使用 Future 作为一个参数。当这个方法返回时,verticle不会被考虑部署。

一段时间后,你已经完成了你所需要做的任何事情(例如:启动其它的 verticles ),你可以调用 Future 对象的 complete 方法(或者 fail 方法)通知你已经完成了。

举个栗子:

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 方法。你可以使用它来做一些 verticle 的耗时清理工作。

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 的停止方法里面,你无需手动卸载通过 verticle 部署的子 verticle。当父verticle卸载的时候,Vert.x 会自动卸载任何挂载在上面的子 verticle。

Verticle类型

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

Standard Verticles

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

我们将会在接下来的章节继续讨论。

Worker Verticles

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

Multi-threaded worker verticles

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

Standard verticles

Standard verticles 在创建时被分配一个 事件循环线程(event loop),并且使用该事件循环调用 start 方法。当你从事件循环上获取核心API的处理程序调用其它任何方法的时候,Vert.x 保证这些处理程序在调用时将在相同的 事件循环线程(event loop) 中执行。

这意味着我们可以保证你的 Verticle 实例中的所有代码总是在相同的 事件循环线程(event loop) 上执行的。(只要你不创建自己的线程并调用它!)。

这意味着你的应用可以用一个线程编写所有的代码,然后让 Vert.x 处理线程与扩展的问题。然后妈妈再也不用担心你现有的项目与传统的多线程应用出现的同步和变量可见性问题了,同时还能避免线程的竞争与死锁的问题。

Worker verticles

Worker verticle 其实和 Standard verticle差不多,但是它不是使用 事件循环(event loop) 线程来执行的,它主要使用一个来自 Vert.x 工作线程池(worker thread pool) 中的线程来执行的。

Worker verticles 主要是为调用阻塞代码而设计的,所以它并不会阻塞 事件循环线程(event loop)

如果你想使用一个 worker verticle 运行阻塞代码,你还可以在 事件循环(event loop) 上面直接运行内部阻塞代码。

如果你想要部署一个worker verticle,你需要设置 setWorker

DeploymentOptions options = new DeploymentOptions().setWorker(true);

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

Worker verticle 实例永远不会由Vert.x的多个线程并发执行,但是它可以被不同的线程在不同的时间执行。

Multi-threaded worker verticles

Multi-threaded worker verticle 和普通的 worker verticle差不多,但是它可以被不同的线程并发执行。

警告Multi-threaded worker verticles 是一个高级特性,以至于大多数应用可能不需要它。因为这些Verticle中的并发性,您必须非常小心地使用标准的Java技术来保持Verticle处于一致的状态,以便进行多线程编程。

编程实现部署

你可以使用 deployVerticle 方法来部署一个 verticle,可以指定一个 verticle 的名称或者传递一个你创建好的 verticle 实例对象。

注意:仅支持Java部署Verticle实例.

Verticle myVerticle = new MyVerticle();

vertx.deployVerticle(myVerticle);

同样你还可以通过指定 verticle 的 名称(name) 来部署。

verticle的名称是用来查找用于真正实例化 verticle 对象 VerticleFactory 工厂。

不同的 Verticle 工厂可用于实例化不同语言的 Verticle,也可用于其它各种方面,例如加载服务以及在运行时从Maven获取 Verticle。

这种特性允许你部署 Vert.x 支持任何语言开发的verticle。

不同类型的 Verticle 部署,举个栗子:

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

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

// Deploy a Ruby verticle veticle
vertx.deployVerticle("verticles/my_verticle.rb");

Verticle名称对应Verticle工厂的映射规则

当使用名称部署 verticle 的时候,这个 名称(name) 用来查找真正实例化 verticle 对象的 verticle 工厂。

Verticle 的名称能够根据前缀的方式来查找对应的Verticle工厂,例如:

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

如果没有设置前缀,Vert.x 将会根据后缀名来查找对应的工厂,例如:

foo.js // 将使用 JavaScript Verticle 工厂
SomeScript.groovy // 将使用Groovy Verticle 工厂

如果在没有前缀和后缀的情况下,Vert.x 将会尝试以 Java完全限定类名(FQCN)并实例化。

如何定位Verticle工厂

大多数的 Verticle 工厂都是从类路径中加载并在 Vert.x 启动的时候注册的。

You can also programmatically register and unregister verticle factories using registerVerticleFactory and unregisterVerticleFactory if you wish.

如果你希望使用编程的方式注册和注销 verticle 工厂可以使用 registerVerticleFactory 和 unregisterVerticleFactory。

等待部署完成

Verticle是异步部署的,所以在调用部署后返回可能会需要一段时间。

如果你想要在部署完成的时候被通知到,你可以指定一个完成的处理器:

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

如果部署成功的话,这个完成处理器将会传递一个包含 string 类型的字符串部署 ID。

这个部署ID可以用来你稍后卸载这个部署对象。

卸载Verticle部署

取消部署可以通过 undeploy 方法来卸载。

取消部署同样是异步处理的,所以你想要在取消部署完成的时候收到通知处理,你需要指定一个完成处理器:

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

指定Verticle的实例数量

当使用verticle名称不是一个verticle的时候,你可以指定你想要部署的verticle实例的数量:

DeploymentOptions options = new DeploymentOptions().setInstances(16);

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

这种方式对于多核扩展非常轻松。例如:你有一个 web-server verticle 部署在一台多核心的机器上面,这时你想要部署多个实例以便充分利用所有的核心,这时它并可以大展手脚了。

将配置传递给Verticle

可以在部署的时候将JSON形式的配置传递给Verticle:

JsonObject config = new JsonObject().put("name", "tim").put("directory", "/blah");

DeploymentOptions options = new DeploymentOptions().setConfig(config);

vertx.deployVerticle("com.mycompany.MyOrderProcessVerticle", options);

之后可以通过 Context 对象或直接使用 config 方法来获得此配置。

配置文件返回一个JSON对象,因此你可以使用下面的这种方式获取数据:

System.out.println("Configuration: " + config().getString("name"));

在Verticle里面访问环境变量

使用Java API可以访问环境变量和系统属性:

System.getProperty("prop");
System.getenv("HOME");

Verticle隔离组

默认情况下,Vert.x 有一个 扁平类路径(flag classpath)。也就是说,当 Vert.x 使用当前类加载器部署 Verticle 时,它不会创建一个新的 classloader。多数情况下,这是最简单,最清楚,最令人激动的事情。

但是, 在某些情况下, 你可能希望部署一个 Verticle, 以便该 Verticle 的类与应用程序中的其他类隔离开来。

情况可能是这样的,例如:你想要在同一个 Vert.x 实例中部署两个同名不同版本的 verticle 实例,或者你有两个不同版本但是却使用相同的 jar 库。

当你想要使用隔离组时,设置 setIsolatedClasses 并提供一个你想要隔离的类名列表。一条记录能够以全限定类名类似 com.mycompany.myproject.engine.MyClass 或者以通配符的方式匹配在包中和子包下面的任何类,例如:com.mycompany.myproject.* 将会匹配到在这个包 com.mycompany.myproject 及其子包下面的任何类。

请注意只有匹配到的类才会被隔离 - 任何其它的类将会使用当前的类加载器进行加载。

还可以使用 setExtraClasspath 提供额外的类路径项, 因此, 如果要加载在主类路径中尚未存在的类或资源, 可以添加此项。

警告:谨慎使用这个特性,
类加载器可能会出现一系列问题并且还会增加调试的困难度,以及其它未知的事情发生。

举个栗子说明使用分离组部署verticle:

DeploymentOptions options = new DeploymentOptions().setIsolationGroup("mygorup");

options.setIsolatedClasses(Arrays.asList("com.mycompany.myverticle.*", "com.mycompany.somepkg.SomeClass", "org.somelibrary.*"));

vertx.deployVerticle("com.mycompany.myverticle.VerticleClass", options);

高可用

可以在部署Verticle的情况下启用高可用性(HA)。 启用高可用的情况下,当 Vert.x 实例上部署的 Verticle 突然宕机,Verticle 会自动从集群中重新部署到另一个 Vert.x 实例以保证其服务继续运行。

以高可用的方式运行 verticle,只需要添加 -ha 开关即可:

vertx run my-verticle.js -ha

当开启了高可用性,就无需追加 -cluster

更多关于高可用特性和配置在高可用和故障转移章节。

命令行方式运行Verticles

你可以在Maven或者Gradle项目中添加 Vert.x cor库的依赖之后并可以直接使用它。

然而你可能还想要从命令行来直接运行 Vert.x verticles。

如果你要这样搞的话,首先你需要下载安装 Vert.x 发布包,然后增加 安装的 bin 到你系统的 PATH 环境变量。同时确保 Java 8 JDK也同样在你的系统环境变量中。

注意:JDK要求支持代码的即时编译。

现在你可以使用 vert.x 运行命令来运行 verticles。举个栗子:

# 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源代码而无需先编译它们!

vertx run SomeJavaSourceFile.java

运行Vert.x之前,Vert.x将立即编译Java源文件。 这对快速构建Verticle原型非常有用,而且对于演示非常有用。 再也不用先建立一个Maven或Gradle项目来开始了!

获取所有的 vert.x 执行命令行的变量选项,在命令行上输入 vertx 即可。

引起Verticle退出

由于Vert.x 的实例线程不是守护线程,因此它们会阻止JVM退出。

如果你正在嵌入Vert.x,并且已经完成了,你可以调用 close 来关闭它。

它会关闭所有的内部线程池并清理其它资源,然后允许JVM退出。

上下文对象

当 Vert.x 向处理程序提供事件或调用 Verticle 的启动或停止方法时,执行与Context相关联。 通常,上下文是一个事件循环上下文,并绑定到特定的事件循环线程。 所以这个上下文的执行总是发生在完全相同的事件循环线程上。 在工作者 Verticle 和运行内联阻塞代码的情况下,工作者上下文将与将使用来自工作者线程池的线程的执行相关联。

要想获得 上下文(context) ,使用 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 - multi thread worker");
} else if (!Context.isOnVertxThread()) {
    System.out.println("Context not attached to a thread managed by vert.x");
}

当你检索到上下文时,你可以在上下文中异步运行代码。换而言之,你提交一个任务最终将会在同一个上下文中运行,但之后:

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

当多个 handlers 在同一个上下文中运行,它们可能想要共享数据。上下文对象提供了存储和检索 方法来共享数据。例如,它可以让你传递数据给一些使用 runOnContext 运行的动作:

final Context context = vertx.getOrCreateContext();

context.put("data", "hello");

context.runOnContext( (v) -> {
    String hello = context.get("data");
});

上下文对象允许你使用 config 方法 访问verticle配置。查看 Passing cofniguration to a verticel 章节获取更多关于配置的细节。

执行周期调度和延时动作

在Vert.x 中想要执行一个一次性动作或者周期动作是非常简单的事情。

在 标准 verticle(standard verticle) 你不能让线程休眠来做到延迟,因为这样会阻塞 事件线程(event loop thread)。

你需要使用 Vert.x 的 timer 来取代它。Timer 能够一次性或者周期的执行。我们将会讨论下两者。

一次性定时器

一次性定时器在一定的延迟之后调用事件处理程序,以毫秒表示。

设置一个定时器触发,你需要设置 setTimer 方法然后传入延迟时间和一个处理程序。

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

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

返回值是一个唯一的Timer id,它能够在稍后的时间用于取消定时器。handler 也同样传递了这个 timer id。

周期定时器

你可以设置定时器周期的执行通过 setPeriodic

初始延迟时间等于周期调度时间。

setPeriodic 的返回值是一个唯一的 timer id(long)。它能用于稍后的时间取消定时器。

传递给定时器事件处理程序的参数也是唯一的定时器ID:

请记住,计时器会定期启动。 如果您的定期处理需要很长时间才能继续,那么您的计时器事件可能会持续运行甚至更糟糕:叠加。

在这种情况下,你应该考虑使用 setTimer。 一旦你的调度完成,你可以设置下一个计时器。

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

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

取消定时器

取消周期性的定时器,调用 cacelTimer 指定的定时器id。举个栗子:

vertx.cancelTimer(timerID);

自动卸载定时器

如果你从verticle的内部创建的定时器,那它们会自动关闭当verticle卸载的时候。

Verticle工作池

Verticle 使用 Vert.x 工作池(worker pool) 执行阻塞动作,例如:executeBlocking 或者 worker verticle。

不同的是 worker pool 能够在部署选项中指定不同的工作池:

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

The end

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,560评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,104评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,297评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,869评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,275评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,563评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,833评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,543评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,245评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,512评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,011评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,359评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,006评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,062评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,825评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,590评论 2 273
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,501评论 2 268

推荐阅读更多精彩内容