12.6 微服务平台Vert.x

Vert.x是一个用于JVM开发的现代微型Web服务框架,最初由VMWare开发,但现在是一个Eclipse Foundation项目。Vert.x是一个货真价实的多语言框架,提供了针对Java 、Groovy、Scala、Kotlin等多种JVM语言以及Nashorn(JavaScript)、JRuby和Ceylon的官方文档。

Vert.x的核心目标是高性能和可伸缩性,为此它使用了类似于Node.js的异步编程模型。简而言之,Vert.x有一个等待事件发生的主事件循环,事件发生时,它调用注册的事件处理程序对其进行处理。你在应用程序中编写的事件处理程序应尽快返回,因为这些代码执行时,Vert.x的主事件循环将无法接收并处理新事件。

如果系统中所有的事件处理程序都能快速处理事件,单个Vert.x应用程序实例就能处理数百乃至数千个请求。在Node.js和Python中,故事到这里就结束了。需要更强大的威力时,必须运行多个独立的应用程序实例,因为这些语言无法充分利用多核CPU。这浪费了宝贵的内存和资源。Vert.x运行在JVM上,能够在不同的CPU内核中运行单个应用程序实例中的多个事件循环,从而充分发挥当今CPU的威力。为简化开发工作,一个事件处理程序通常只能在单个事件循环中使用。因此,开发人员通常根本不用考虑复杂的并发和多线程问题。

12.6 微服务平台Vert.x - 图1 还有其他的可能性。例如,可让基于Vert.x的应用程序的多个实例组成集群,集群中的实例可以运行在同一台机器上,也可以运行在不同的服务器上。

有时候,有些操作可能需要很长时间才能完成。例如,向繁忙的数据库服务器查询并对返回的复杂数据结构进行计算时,无法保证事件处理程序将快速返回。对于这样的情形,Vert.x提供了简单的解决方案:将长时间运行的任务委托给独立的工作线程。事件循环将任务委托给工作线程,自己则继续处理其他事件。每过一段时间,它就会检查任务是否已完成,如果已完成,就根据计算得到的结果对相应的事件进行处理。由于可用的线程数是可以配置的,因此在基于Vert.x的应用程序中,能够妥善地管理可用的服务器资源(内存、线程等)。

12.6.1 在文件ivy.xml中添加Vert.x依赖

为了确定必须提供的有关Vert.x的依赖信息,我们将采用前面获悉H2数据库系统依赖信息的方法:使用Maven的在线搜索引擎(http://search.maven.org)。

为获取正确的依赖信息并将其添加到文件ivy.xml中,请执行如下操作。

  • 访问前述URL并搜索vertx-core。
  • 找到Groupid为io.vertx、ArtifactId为vertx-core的条目,并单击其版本号(编写本书期间为3.4.1)。
  • 在产品详情(artifact details)页面中,单击Apache Ivy,并将相应的XML条目(我看到的是复制到剪贴板中:

12.6 微服务平台Vert.x - 图2

  • 在Eclipse中,打开文件ivy.xml file,并将前面复制的内容粘贴到H2数据库的条目后面。

为让Ivy下载必要的依赖并将其添加到项目的类路径中,请执行如下步骤。

(1) 右击文件ivy.xml并选择Add Ivy Library…。

(2) 单击Finish按钮。Ivy将下载必要的依赖。

12.6.2 创建Web服务

首先,在文件Main.groovy的开头(package语句后面)添加必要的import语句:

  1. package webservice
  2. import java.sql.DriverManager
  3. import groovy.sql.Sql
  4. import io.vertx.core.AbstractVerticle
  5. import io.vertx.core.Future
  6. import io.vertx.core.Vertx
  7. import io.vertx.core.http.HttpMethod

能够处理Vert.x事件的类被称为Verticle。通过扩展Vert.x框架提供的抽象类AbstractVerticle,可轻松地让类变成Verticle。为此,请找到下面的代码行:

  1. class Main {
  2. ...
  3. }

将其修改成下面这样:

  1. class Main extends AbstractVerticle {
  2. ...
  3. }

在抽象类AbstractVerticle中,最重要的方法是start(),它在Vert.x初始化Verticle时被调用,你应使用它来启动Vert.x内置的HTTP服务器以及注册事件处理程序。这里也将分步创建这个方法。首先来定义方法start()本身:

  1. public void start(Future<Void> fut) {
  2. }

传入的Future对象用于让Vert.x知道Verticle是否成功地初始化并启动了自己。在这个示例中,我们需要设置一个HTTP服务器,而设置和配置好一切可能需要一段时间。Vert.x不会等待方法start()返回,而是在Verticle初始化期间继续执行其他任务。这要求我们在启动后调用Future对象的方法complete(),而在没有启动时调用fail()

AbstractVerticle类提供了一个名为vertx的变量,我们可使用它来与Vert.x框架通信。下面首先来编写设置内置HTTP服务器的代码,为此在方法start()中添加如下代码:

  1. Vertx
  2. .createHttpServer()
  3. .requestHandler() { request ->
  4. }
  5. .listen(8080) { result ->
  6. if (result.succeeded()) {
  7. fut.complete()
  8. } else {
  9. fut.fail(result.cause())
  10. }
  11. }

抽象类AbstractVerticle提供的变量vertx指向一个实现了io.vertx.core.Vertx接口的类的实例,而这个类的每个方法都返回变量vertx指向的对象。这让你能够串接对这个对象的方法调用。要不是因为这一点,我们就必须在全部三个方法调用前面都加上vertx。

我们让HTTP服务器侦听端口8080。HTTP服务器初始化完毕后,将调用传递给方法listen()的闭包。服务器能够成功地启动时,其方法succeeded()将返回true;在这种情况下,将对传递给方法start()Future对象调用方法complete()。这样Vert.x便知道Verticle已准备就绪了。如果HTTP服务器无法启动(例如,由于端口8080已被其他应用程序占用),将调用Future对象的fail()方法,这将导致应用程序停止执行。

方法requestHandler()指定一个HTTP请求处理程序;Vert.x主事件循环遇到HTTP请求时,将调用通过参数传递给这个方法的闭包。有一些Vert.x扩展,它们添加了复杂的路由器功能,使得能够预先注册URL,这让Vert.x能够与Express(Node.js)和Flask(Python)等流行的Web应用程序框架媲美。这里不会使用这些扩展,因为就这个简单的示例项目而言,根本不需要它们。

下面来编写HTTP请求处理程序,为此在.requestHandler(){ request -> }块中添加如下代码:

  1. if (request.path() == "/blogs/" && request.method() == HttpMethod.GET){
  2. request
  3. .response()
  4. .putHeader("content-type", "application/xml")
  5. .end( generateXML().toString() )
  6. } else {
  7. request
  8. .response()
  9. .setStatusCode(404)
  10. .end("Error 404");
  11. }

request对象的所有方法都返回它,因此也可串接通过这个对象发起的方法调用。这些代码非常简单:如果请求是对URL /blogs/的GET HTTP请求,就调用返回XML数据的函数generateXML();否则返回HTTP代码404(页面未找到)。

再来详细地说说方法generateXML()。它新建一个数据库连接、生成XML并关闭数据库连接。在大型应用程序(尤其是多线程大型应用程序)中,建议不要共享JDBC数据库连接。在这里,我们的做法是为每个请求都新建一个连接,并在处理完请求后关闭连接。就这里这样的小型示例而言,这没什么问题,但在更复杂的Web应用程序中,应使用连接池系统。新建数据库连接的开销很高,而通过使用连接池系统,可确保未用的连接被归还给连接池以便重用。在JVM平台中,可使用的连接池系统有很多。

现在余下的唯一工作是确保Verticle已启动。一种选择是直接在JVM方法static main()中完成这项任务。为此,重写方法main(),使其类似于下面这样(删除代码行println(app.generateXML())并添加突出显示的代码行):

  1. static public void main(String[] args) {
  2. def app = new Main()
  3. def connection = app.createDatabaseConnection()
  4. app.createDatabaseStructure(connection)
  5. app.addDemoRecords(connection)
  6. connection.close()
  7. Vertx vertx = Vertx.vertx()
  8. vertx.deployVerticle(new Main())
  9. }

我们保留了生成数据库结构和创建示例记录的代码。在这些代码后面,我们创建一个Vertx实例,并使用方法deployVerticle来部署它。这将启动Vert.x系统,而这个系统将调用Verticle的方法start()

现在应该可以运行这些代码了,为此可按Ctrl + F11(cmd + F11)。如果一切顺利,你将在控制台窗口中看到如下输出:

  1. SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
  2. SLF4J: Defaulting to no-operation (NOP) logger implementation
  3. SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder for further
  4. details.

由于我们没有在项目中添加Simple Logging Facade For Java(SLF4J)依赖,Vert.x系统不知道如何写入日志。我们完成可忽略这种警告,应用程序将继续运行,而不会有其他的输出。如果出现栈跟踪,请核查代码。现在启动你喜欢的浏览器,并访问如下URL:http://localhost:8080/blogs/

你将在浏览器中看到示例博文的XML表示:

12.6 微服务平台Vert.x - 图3

如果你修改URL,这个Web服务将返回一个404错误页面:

12.6 微服务平台Vert.x - 图4

要停止这个Web服务,可在选项卡Console中单击工具栏上工具提示为“Terminate”的按钮:

12.6 微服务平台Vert.x - 图5