在Java中构建响应式微服务系统——第二章 理解响应式微服务和Vert.x

13 篇文章 0 订阅
11 篇文章 4 订阅

第二章 理解响应式微服务和Vert.x

版权声明:本文为博主自主翻译,转载请标明出处。 https://blog.csdn.net/elinespace/article/details/807933651

微服务实际上不是一个新事物。它们起源于20世纪70年代的研究,由于微服务是一种更快迁移、更容易交付价值以及提升敏捷的方法,最近已经成为人们关注的焦点。然而,微服务源于基于Actor的系统、服务设计、动态和自治系统、领域驱动设计以及分布式系统。微服务的细粒度模块化设计不可避免的会导致开发人员创建分布式系统。我敢肯定,你已经注意到,分布式系统是很难的。它们故障、缓慢,受到CAP和FLP原则的约束。换句话说,构建和维护它们非常复杂。这是响应式发挥作用的地方。

三十多年的进化

Actor模型在1973年由C. Hewitt、P. Bishop和R.Steiger引入。自主计算——2001年创造的术语,是指分布式计算资源的自管理特性(自恢复、自优化等)。

但什么是响应式(reactive)?现在响应式(reactive)是一个重载的术语。牛津词典对reactive的定义为“对刺激表现的反应。”reactive软件基于它收到的激发作出响应以及适应它的行为。尽管如此,这一定义所提倡的响应性和适应性是一种编程挑战,因为计算流不是由程序员而是系统激发控制。在本章中,我们将看到Vert.x如何帮助你实现响应式,通过结合:

  1. 响应式编程——一种以数据流监视、变更响应以及传播为中心的开发模型。
  2. 响应式系统——一种架构类型,用于构建响应式的健壮的分布式系统,基于异步消息传递。

响应式微服务是响应式微服务系统的构建块。尽管如此,由于它们的异步因素,这些微服务的实现是具有挑战性的。响应式编程降低了这种复杂性。怎么使用?让我们现在就回答这个问题。

响应式编程

这里写图片描述
图2-1 响应式编程与数据流及它的响应相关

响应式编程是一种开发模型,它面向数据流和数据传播。在响应式编程(reactive programming)中,激发(stimuli)是在流中传输的数据,称为streams。有许多方法可以实现响应式编程模型。在本报告中,我们将使用Reactive Extensions,在该情况下,streams称为observables。消费者订阅(subscribe)这些observables并对其做出响应(图2-1)。

为了使这项概念不那么抽象,让我们看一个示例,它使用了RxJava——一个在Java中实现了Reactive Extensions的库。这些示例位于代码仓库的reactive-programming目录。

observable.subscribe(
    data -> { // onNext
        System.out.println(data);
    },
    error -> { // onError
        error.printStackTrace();
    },
    () -> { // onComplete
        System.out.println("No more data");
    }
);

在这个片段中,代码观察(subscriber)一个Observable并且在流中的值传递时通知它。订阅者可以接收到三种类型的事件。onNext当有一个新值时调用,onError当stream中发出一个错误或者一个阶段抛出异常时调用。onComplete回调在stream到达结束时执行,无限stream不会发生这种情况。RxJava包含一个操作集用于产生、变换和协调Observables,如map转换值为另外的值,flatMap产生一个Observable或者链式执行另一个异步动作:

// sensor is an unbound observable publishing values.
sensor
    // Groups values 10 by 10, and produces an observable
    // with these values.
    .window(10)
    // Compute the average on each group
    .flatMap(MathObservable::averageInteger)
    // Produce a json representation of the average
    .map(average -> "{'average': " + average + "}")
    .subscribe(
        data -> {
            System.out.println(data);
        },
        error -> {
            error.printStackTrace();
        }
    );

RxJava v1.x定义了不同类型的streams如下:

  1. Observables是有界的或者无界的stream,包含一系列的值。
  2. Singles是包含单个值的stream,一般而言是一个操作的延迟结果,类似于future和promise。
  3. Completables是不包含值的stream,但是包含操作完成或者失败的标示。

RxJava2

虽然RxJava 2.x最近已经发布,本报告仍旧使用以前的版本(RxJava 1.x)。RxJava 2.x提供了相似的概念。RxJava 2添加了两个新的stream类型。Observable用于不支持背压(back-pressure)的stream,而Flowable是带有背压的Observable。RxJava 2还引入了Maybe类型,它建模了一类stream,这类stream可以有0个或者1个条目或者一个错误。

我们可以使用RxJava做什么?例如,我们可以描述异步动作序列并且编排它们。让我们设想你需要下载一个文档,处理它,并且上传它。下载和上传操作是异步的。为了开发这个序列,你需要使用一些东西如:

// Asynchronous task downloading a document
Future<String> downloadTask = download();
// Create a single completed when the document is downloaded.
Single.from(downloadTask)
    // Process the content
    .map(content -> process(content))
    // Upload the document, this asynchronous operation
    // just indicates its successful completion or failure.
    .flatMapCompletable(payload -> upload(payload))
    .subscribe(
        () -> System.out.println("Document downloaded, updated and uploaded"),
        t -> t.printStackTrace()
    );

你还可以编排异步任务。例如,合并两次异步操作的结果,你使用zip操作合并不同stream的值:

// Download two documents
Single<String> downloadTask1 = downloadFirstDocument();
Single<String> downloadTask2 = downloadSecondDocument();
// When both documents are downloaded, combine them
Single.zip(downloadTask1, downloadTask2,
    (doc1, doc2) -> doc1 + "\n" + doc2)
    .subscribe(
        (doc) -> System.out.println("Document combined: " + doc),
        t -> t.printStackTrace()
    );

这些操作的使用给了你超能力:你可以以一种声明式的和优雅的方式协调异步任务和数据流。这与响应式微服务有什么关系?要回答这个问题,让我们看一下响应式系统.

响应式Stream

(注:因为文章中同时出现了Flow和Stream,为了避免混淆,使用英文单词)

你可能听说过响应式Stream(http://www.reactive-streams.org/)。响应式Stream是一份倡议,以提供一套具有背压的异步Stream处理标准。它提供了一组最小的接口和协议,以描述用非阻塞背压实现异步数据Stream的操作和实体。它没有定义控制Stream的操作,主要用作互操作层。这一倡议得到了Netflix、LealDead和Red Hat等的支持。

响应式系统

响应式编程(reactive programming)是一种开发模型,而响应式系统(reactive systems)是一种架构类型,用于构建分布式系统(http://www.reactivemanifesto.org/);它是一组原则,用于实现响应式(responsiveness)并构建系统,即使在故障或负载下也能及时响应请求。

要构建这样的系统,响应式系统(reactive systems)采用了消息驱动的方法。所有组件使用异步的消息接收和发送进行交互。为了解耦发送者和接收者,组件向虚拟地址(virtual addresses)发送消息。它们也向虚拟地址(address)注册以接收消息。一个地址(address)是一个目的地标识,如隐性字符串或者URL。相同的地址可以注册数个接收者——传递语义取决于底层技术。发送者不阻塞并等待一个响应。发送者可以稍后接收响应,但是同时,它可以接收和发送其它消息。这种异步特性非常重要并且影响了你的应用程序如何开发。

使用异步消息传递交互为响应式系统提供了两个极重要的特性:

  1. 弹性(Elasticity)——水平伸缩的能力
  2. 还原能力(Resilience)——处理故障和恢复的能力

弹性源自消息交互所提供的解耦。发送到一个地址的消息可以使用一种负载均衡策略由一组消费者消费。当响应式系统在负载中面临尖峰时,它可以产生新的消费者实例并随后处理它们。

这种快速恢复的特性是由无阻塞的故障处理能力和组件复制能力所提供的。首先,消息交互允许组件本地处理故障。多亏异步特性,组件不主动等待响应,因此在一个组件中发生故障不影响其它组件。复制也是处理快速恢复的关键能力。当一个处理节点消息失败时,消息可以由注册在相同地址上的其它节点处理。

由于这两个特性,系统变为响应式。它可以适应更高或者更低的负载,并且在高负载或者故障时持续服务请求。在构建高度分布式的微服务系统时以及处理超出调用方控制的服务时,这一原则是基本的。有必要运行服务的数个实例,以平衡负载和处理故障而不破坏可用性。我们将在接下来的章节看到Vert.x如何解决这些问题。

响应式微服务

构建一个微服务(从而分布式)系统,每个服务可能在任何时候变更、演变、故障、呈现缓慢或者撤回。这样的问题不能影响整个系统的行为。你的系统必须接收变更并且能够处理故障。你可以在降级模式下运行,但你的系统应该仍然能够处理请求。

为了确保这种行为,响应式微服务系统由响应式微服务构成。这些微服务有四个特性:

  1. 自治
  2. 异步
  3. 快速恢复能力
  4. 弹性

响应式微服务是自治的。它们可以应付它们周围的服务的可用或者不可用。然而,自治与隔离是成对的。响应式微服务可以本地处理故障,独立工作,并且必要时与其它协作。响应式微服务使用异步消息传递与同伴交互。它还接收消息并且拥有对这些消息产生响应的能力。

由于异步消息传递,响应式微服务可以面对故障并相应的调整自己的行为。故障不应被传播,而是接近根源处理。当一个微服务故障(原文是blows up 爆炸、炸开),消费者微服务必须处理故障而不要传播它。这种隔离原则是防止故障冒泡并且破坏整个系统的关键特性。快速恢复能力不仅仅是管理故障,还关于自愈。响应式微服务应该实现故障发生时的恢复或补偿策略。

最后,一个响应式微服务必须是弹性的,因此系统可以改变实例的数量来管理负载。这意味着一组约束,如避免内存状态,实例间共享状态如果需要,或者能够将消息路由到有状态服务的相同实例。

Vert.x怎么样?

Vert.x是一个使用异步非阻塞开发模型构建响应式和分布式系统的工具包。由于它是一个工具包而不是一个框架,你可以像使用任何其它库一样使用Vert.x。它不会约束你如何构建或组织你的系统;你按照你的需要使用它。Vert.x是非常灵活的;你可以使用它作为一个单独的应用或者嵌入到一个更大的应用当中。

从开发者角度看,Vert.x是一组JAR文件。每个Vert.x模块是一个添加到你的类路径的JAR文件。从HTTP服务器和客户端、到消息、到底层协议如TCP或UDP,Vert.x提供了大量的模块来按照你想要的方式构建你的应用程序。除了Vert.x Core(主Vert.x组件),你还可以选择这些模块中的任何一个来构建你的系统。图2-2展示了Vert.x生态系统的一个摘要视图:

这里写图片描述
图2-2 Vert.x生态系统的不完全概览

Vert.x还提供了一个杰出的栈来帮助构建微服务系统。Vert.x在它流行之前已在推动微服务方法。它被设计和构建以提供一种直观和有力的方法来构建微服务系统。而且这不是所有,使用Vert.x,你可以构建响应式微服务。当使用Vert.x构建微服务时,它向微服务注入了它的一个核心特性:它变得完全异步。

异步开发模型

使用Vert.x构建的所有应用程序都是异步的。Vert.x应用程序是事件驱动和非阻塞的。你的应用程序在某些感兴趣的事情发生时被通知。让我们来看一个具体例子。Vert.x提供了一种简单的方法创建一个HTTP服务器。这个HTTP服务器在每次接收到HTTP请求时被通知:

vertx.createHttpServer()
    .requestHandler(request -> {
        // This handler will be called every time an HTTP
        // request is received at the server
        request.response().end("hello Vert.x");
    })
    .listen(8080);

在这个例子中,我们设置了一个requestHandler来接收HTTP请求(事件)并且返回“hello Vert.x”。Handler是事件发生时调用的函数。在我们的例子中,Handler的代码对每个接收请求都会执行。注意,Handler没有返回结果。然而,Handler可以提供一个结果。这个结果如何提供依赖于交互的类型。在上一片段中,它只是将结果写入到HTTP响应。Handler被链接到Socket上的一个监听请求。执行这个HTTP端点产生一个简单的HTTP响应:

HTTP/1.1 200 OK
Content-Length: 12
hello Vert.x

很少有例外,Vert.x中的API都不会阻塞调用线程。如果可以立即提供结果,它将被返回;否则使用一个Handler接收稍后的事件。Handler在事件准备好被处理时或者异步操作结果已被计算时被通知。

在传统的命令式编程中,你会写一些东西像:

int res = compute(1, 2);

在该代码中,你要等待方法的结果。当切换到一个异步非阻塞开发模型,你需要传递一个Handler,在结果准备好后执行(注释1 这个代码使用了Java 8引入的lambda表达式。关于这种标记法的更多细节可以在http://bit.ly/2nsyJJv找到):

compute(1, 2, res -> {
    // Called with the result
});

在上一片段中,compute不再返回一个结果,因此你不用等到结果计算完成并返回。你可以传递一个Handler,在结果准备好后调用。

由于这种非阻塞的开发模型,你可以使用少量线程处理高度并发的工作负载。在大多数情况下,Vert.x使用一个称为Eventloop的线程调用你的Handler。EventLoop在图2-3中描述。它消费事件队列,并且分发每个事件到感兴趣的Handler。

这里写图片描述
图2-3 EventLoop原理

EventLoop提出的线程模型具有巨大的效益:它简化了并发。由于只有一个线程,所以总是由同一线程调用,从不并发。然而,它也有一个非常重要的规则,你必须遵守:

不要阻塞EventLoop
——Vert.x重要原则

由于没有阻塞,EventLoop可以在短时间内递送大量事件。这称为响应式模式(https://en.wikipedia.org/wiki/Reactor_pattern)。

让我们想象一下,你一度违反了这个原则。在前面的代码片段中,请求Handler总是从相同的EventLoop中调用。因此,如果HTTP请求处理被阻塞,而不是立即答复用户,其它请求将不会及时被处理,而是被排队等待线程释放。你将失去Vert.x的可伸缩性和效率的优势。那么什么可能阻塞?第一个明显的例子是JDBC数据库访问。 它们天然是阻塞的。长计算也会阻塞。例如,计算PI到小数点后200000位的代码肯定是阻塞的。不要担心——Vert.x还提供了用于处理阻塞代码的结构。

在一个标准的Reactor实现中,有一个唯一的EventLoop线程,它在一个循环中运行,传递所有事件到所有处理器,当事件到达时。单个线程的问题很简单:它在同一时刻只能运行在单个CPU内核上。此处Vert.x工作方式是不同的。每个Vert.x实例维护了数个EventLoop,以代替单个EventLoop,这称为Multireactor模式,如图2-4所示。

这里写图片描述
图2-4 Multireactor原理

事件由不同的EventLoop分发。然而,一旦一个Handler由一个EventLoop执行,它将总是由该EventLoop执行,强制执行Reactor模式的并发效益。假如像图2-4中,你有几个EventLoop,它可以在不同CPU内核上平衡负载。这是怎样与我们的HTTP示例一起工作的呢?Vert.x注册Socket监听器一次,并且分发请求到不同的EventLoop上。

Verticle-构建块

Vert.x在如何塑造应用程序和代码方面给了你很多自由。但是它还是提供了单元(bricks)以方便开始编写Vert.x应用程序,并且它带有一个开箱即用的、简单的、可伸缩的、Actor风格的部署和并发模型。Verticle是由Vert.x部署和运行的代码块。一个应用,诸如微服务,通常由同时运行在同一个Vert.x实例中的许多Verticle实例组成。一个Verticle通常创建服务器或者客户端,注册一组Handler,封装了系统业务逻辑的一部分。

正常的(Regular)Verticle在Vert.x EventLoop中执行,它永远不阻塞。Vert.x确保每个Verticle总是由相同的线程执行,永不并发,因此避免同步构造。在Java中,Verticle是一个扩展自AbstractVerticle的类:

import io.vertx.core.AbstractVerticle;
public class MyVerticle extends AbstractVerticle {
    @Override
    public void start() throws Exception {
        // Executed when the verticle is deployed
    }
    @Override
    public void stop() throws Exception {
        // Executed when the verticle is un-deployed
    }
}

Worker Verticle

与正常的Verticle不同,Worker Verticle不在事件队列中执行,这意味着它们可以执行阻塞代码。然而这限制了你的可伸缩性。

Verticle可以访问vertx成员(由AbstractVerticle类提供)来创建服务器、客户端以及与其它Verticle交互。Verticle还可以部署其它Verticle,配置它们,设置创建的实例数。Verticle实例与不同的EventLoop相关联(实现Multireactor模式),Vert.x平衡这些实例间的负载。

从回调到Observable

如前面章节所示,Vert.x开发模型使用了回调。当协调几个异步操作时,基于回调的开发模型往往会产生复杂的代码。例如,让我们看一下如何从数据库获取数据。首先我们需要一个到数据库的链接,然后我们向数据库发出一个查询,处理结果集,释放链接。所有这些操作都是异步的。使用回调的情况下,你可能需要使用Vert.x JDBC客户端编写以下代码:

client.getConnection(conn -> {
    if (conn.failed()) {
        /* failure handling */
    }
    else {
        SQLConnection connection = conn.result();
        connection.query("SELECT * from PRODUCTS", rs -> {
            if (rs.failed()) {
                /* failure handling */
            }
            else {
                List<JsonArray> lines = rs.result().getResults();
                for (JsonArray l : lines) {
                    System.out.println(new Product(l));
                }
                connection.close(done -> {
                    if (done.failed()) {/* failure handling */}
                });
            }
        });
    }
});

虽然仍然是可管理的,但示例显示回调会很快导致不可读的代码。你还可以使用Vert.x Future来处理异步动作。与Java Future不同,Vert.x Future是非阻塞的。Future提供了更高层次的组合操作来构建顺序动作或者并行执行动作。通常如下一个片段所示,我们组合Future来构建异步动作序列:

Future<SQLConnection> future = getConnection();
future.compose(conn -> {
    connection.set(conn);
    // Return a future of ResultSet
    return selectProduct(conn);
})
// Return a collection of products by mapping
// each row to a Product
.map(result -> toProducts(result.getResults()))
.setHandler(ar -> {
    if (ar.failed()) { /* failure handling */ }
    else {
        ar.result().forEach(System.out::println);
    }
    connection.get().close(done -> {
        if (done.failed()) { /* failure handling */ }
    });
});

不管怎样,Future使得代码更具声明性,我们在一个批次中获取所有行并且处理它们。这个结果可能非常大并且花费大量时间去获取。同时,你不需要整个结果来启动处理。我们可以逐条处理每一行,一旦你拥有它。幸运的是,Vert.x为这种开发模型挑战提供了一种解决办法,并为你提供了一种方法来使用响应式程序开发模型实现响应式微服务。Vert.x提供了RxJava API来:

  1. 合并及协调异步任务
  2. 作为输入流对传入消息作出反应

让我们使用RxJava API编写前面的代码:

// We retrieve a connection and cache it,
// so we can retrieve the value later.
Single<SQLConnection> connection = client.rxGetConnection();
connection.flatMapObservable(conn ->
    conn
    // Execute the query
    .rxQueryStream("SELECT * from PRODUCTS")
    // Publish the rows one by one in a new Observable
    .flatMapObservable(SQLRowStream::toObservable)
    // Don't forget to close the connection
    .doAfterTerminate(conn::close)
)
// Map every row to a Product
.map(Product::new)
// Display the result one by one
.subscribe(System.out::println);

除了提高可读性之外,响应式编程允许你订阅结果Stream,并且它们一旦可用就会处理。使用Vert.x,你可以选择你喜欢的开发模型。在本报告中,我们将使用回调和RxJava。

让我们开始编码!

是你亲自动手参与的时候了。我们将使用Apache Maven和Vert.x Maven插件来开发我们的第一个Vert.x应用。然而,你可以使用任何你想要的工具(Gradle、Apache Maven以及其它打包插件、Apache Ant)。你将在代码仓库中找到不同的示例(在packaging-examples目录)。本节展示的代码位于hello-vertx目录。

项目创建

创建一个目录名为my-first-vertx-app,进入这个目录:

mkdir my-first-vertx-app
cd my-first-vertx-app

然后,执行以下命令:

mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \
-DprojectGroupId=io.vertx.sample \
-DprojectArtifactId=my-first-vertx-app \
-Dverticle=io.vertx.sample.MyFirstVerticle

这个命令生成了Maven项目结构,配置vertx-maven-plugin并且创建一个Verticle类(io.vertx.sam
ple.MyFirstVerticle),该类未做任何事情。

编写你的第一个Verticle

现在是为你的第一个Verticle编写代码的时候了。使用以下内容修改src/main/java/io/vertx/sample/MyFirstVerticle.java:

package io.vertx.sample;
import io.vertx.core.AbstractVerticle;
/**
* A verticle extends the AbstractVerticle class.
*/
public class MyFirstVerticle extends AbstractVerticle {
    @Override
    public void start() throws Exception {
        // We create a HTTP server object
        vertx.createHttpServer()
        // The requestHandler is called for each incoming
        // HTTP request, we print the name of the thread
        .requestHandler(req -> {
            req.response().end("Hello from " + Thread.currentThread().getName());
        })
        .listen(8080); // start the server on port 8080
    }
}

要运行这个应用,启动:

mvn compile vertx:run

如果一切顺利,你应该能够通过在浏览器中打开http://localhost:8080看到你的应用程序。vertx:run goal启动Vert.x应用并且还监视代码变更。因此,如果你编辑源代码,应用程序将自动重新编译并重启。

现在让我们看一下应用程序输出:

Hello from vert.x-eventloop-thread-0

请求由event loop 0处理。你可以尝试发送更多请求。请求将总是由相同的EventLoop处理,强制执行Vert.x的并发模型。按下Ctrl+C停止执行。

使用RxJava

现在,让我们看一下Vert.x提供的RxJava支持,以便更好的理解它是如何工作的。在你的pom.xml文件中,添加以下依赖:

<dependency>
    <groupId>io.vertx</groupId>
    <artifactId>vertx-rx-java</artifactId>
</dependency>

接下来,修改

package io.vertx.sample;
// We use the .rxjava. package containing the RX-ified APIs
import io.vertx.rxjava.core.AbstractVerticle;
import io.vertx.rxjava.core.http.HttpServer;
public class MyFirstRXVerticle extends AbstractVerticle {
    @Override
    public void start() {
        HttpServer server = vertx.createHttpServer();
        // We get the stream of request as Observable
        server.requestStream().toObservable()
            .subscribe(req ->
                // for each HTTP request, this method is called
                req.response().end("Hello from " + Thread.currentThread().getName())
            );
        // We start the server using rxListen returning a
        // Single of HTTP server. We need to subscribe to
        // trigger the operation
        server
        .rxListen(8080)
        .subscribe();
    }
}

Vert.x API的RxJava变体在名字包含rxjava的包中提供。RxJava方法以rx作为前缀,如rxListen。另外,API通过提供Observable对象的方法得到了增强,你可以在这些对象上订阅,以接收传送的数据。

打包你的应用程序为一个FatJar

Vert.x Maven插件打包应用为一个FatJar。一旦打包完成,你可以轻易的使用java -jar .jar启动应用程序:

mvn clean package
cd target
java -jar my-first-vertx-app-1.0-SNAPSHOT.jar

应用程序再次启动,在指定的端口上监听HTTP流量。按下Ctrl+C停止。

作为一个非固执己见的(unopinionated)工具包,Vert.x并未提升一种打包模型超过另一种(译者注:即平等对待各种打包模型)——你可以自由的使用你喜欢的打包模型。例如,你可以使用FatJar——一种在特定目录中包含库的文件系统方式,或者将应用嵌入到一个war文件并且以编程的方式启动Vert.x。

在本报告中,我们将使用FatJar,即自包含的JAR,它嵌入了应用程序代码、它的资源以及它的所有依赖。这包含Vert.x,你使用的Vert.x组件以及它的依赖。这种打包模型使用了一种扁平的类加载器机制,这使得理解应用程序启动、依赖顺序以及日志更加容易。更重要的是,它有助于减少需要在生产环境中安装的移动组件的数量。你不必部署应用到一个已存在的应用服务器。一旦应用被打包为FatJar,便可以使用一个简单的java -jar

日志、监控以及产品其它组成部分

对于微服务和其它类型的应用,FatJar是一种非常好的打包模型,因为它们简化了部署和启动。但是通常由应用服务器提供的使得你的应用程序生产就绪(production ready)的特征怎么样?一般,我们期望能够编写和收集日志、监控应用、推送外部配置、添加健康检查等等。

不要担心——Vert.x提供了所有这些特征,并且由于Vert.x是中立的,它提供了好几种选项,以让你选择或实现自己的。例如日志,Vert.x没有推行一种特定的日志框架,代之以允许你使用任何你想要的日志框架,如Apache Log4J 1或者2、SLF4J或JUL(JDK日志API)。如果你对Vert.x自己记录的日志消息感兴趣,内部的Vert.x日志可以配置为使用这些框架的任何一个。监控Vert.x应用通常使用JMX。你也可以选择发布这些度量到监控服务器,诸如Prometheus
(https://prometheus.io/) 或 CloudForms (https://www.redhat.com/en/technologies/management/cloudforms)。

总结

本章我们了解了响应式微服务和Vert.x。你还创建了你的第一个Vert.x应用。本章绝不是一个全面的指南,而仅仅对主要概念提供了一个快速介绍。如果你想要深入这些主题,看看以下资源:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值