第三章 构建响应式微服务

在本章中,我们将使用Vert.x构建我们的第一个微服务。由于大多数微服务系统使用HTTP进行交互,因此我们将以HTTP微服务作为开始。但是由于系统包含多个相互通讯的微服务,因此我们会构建另一个微服务消费第一个(作为第一个的消费者)。然后,我们将说明为什么这样一个设计不是完全的拥抱响应式微服务。最后,我们将实现基于消息的微服务,以领会消息如何提升响应性。

第一个微服务

在这一章中,我们将实现同样一组微服务两次。第一个微服务公开了一个hello服务,我们称为hello微服务。另一个消费这个服务两次(并发的)。消费者被称为hello消费者微服务。这个小系统不仅说明了如何提供服务,还说明了如何消费服务。图3-1的左侧,微服务使用HTTP交互。hello消费者微服务使用一个HTTP客户端执行hello微服务。在右侧,hello消费者微服务使用消息与hello微服务交互。这种差异会影响系统的响应性。

图3-1 本章中使用HTTP和基于消息交互的微服务实现

在前一章中,我们看到使用Vert.x API的两种不同方法:回调和RxJava。为了说明它们之间差异并帮助你找到首选方法,hello微服务使用基于回调的开发模型实现,而消费者使用RxJava实现。

HTTP微服务实现

微服务总是通过HTTP公开API并且使用HTTP请求消费。让我们看一下这些HTTP交互如何使用Vert.x实现。本节中开发的代码在代码仓库的microservices/hello-microservice-http目录下。

开始

创建一个目录名为hello-microservice-http,然后生成项目结构:

mkdir hello-microservice-http
cd hello-microservice-http
mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \-DprojectGroupId=io.vertx.microservice \-DprojectArtifactId=hello-microservice-http \-Dverticle=io.vertx.book.http.HelloMicroservice \-Ddependencies=web

这个命令会生成Maven项目并且配置Vert.x Maven插件。除此之外,它添加了vertx-web依赖。Vert.x Web是一个模块,提供了你在Vert.x之上构建现代Web应用所需要的任何东西。

Verticle

打开src/main/java/io/vertx/book/http/HelloMicroservice.java。生成的Verticle代码并没做任何有趣的事情,它只是一个起点:

package io.vertx.book.http;
import io.vertx.core.AbstractVerticle;
public class HelloMicroservice extends AbstractVerticle {@Overridepublic void start() {}
}

现在,启动以下Maven命令:

mvn compile vertx:run

你现在可以编辑这个Verticle。你每次保存文件,应用程序都会重新编译并且自动重启。

HTTP微服务

是时候让我们的MyVerticle类做些事情了。让我们启动一个HTTP服务器。如前章看到的,使用Vert.x创建HTTP服务器,你只需要使用:

@Override
public void start() {vertx.createHttpServer().requestHandler(req -> req.response().end("hello")).listen(8080);
}

一旦添加并保存,你可以通过浏览器在http://localhost:8080看到hello。这段代码在8080端口创建了一个HTTP服务器,注册了一个requestHandler,它在每次接收到HTTP请求时调用。现在,我们只是在响应中写入hello。

使用路由和参数

许多服务是通过Web URL调用的,因此,路径检查对于知道请求是什么至关重要。然而,在requestHandler中执行路径检查以实现不同的操作可能会变得复杂。幸运的是,Vert.x Web提供了Router,我们可以在它的上面注册路由(Route)。路由是一种机制,Vert.x Web通过它检测路径并执行相关的动作。让我们重写start方法,使用两个路由:

@Override
public void start() {Router router = Router.router(vertx);router.get("/").handler(rc -> rc.response().end("hello"));router.get("/:name").handler(rc -> rc.response().end("hello " + rc.pathParam("name")));vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}

一旦我们创建了Router对象,我们注册两个路由。第一个处理“/”路径的请求,只是输出hello。第二个路由有一个路径参数(:name)。处理器追加参数值到祝贺消息。最后,我们变更HTTP服务器的requestHandler,使用路由器的accept方法。

如果你没有停止vertx:run的执行,你应该可以打开浏览器到:

  • http://localhost:8080 ——你将看到hello
  • http://localhost:8080/vert.x ——你将看到hello vert.x

生成JSON

在微服务中经常使用JSON。让我们修改前面的类,以生成JSON载荷:

@Override
public void start() {Router router = Router.router(vertx);router.get("/").handler(this::hello);router.get("/:name").handler(this::hello);vertx.createHttpServer().requestHandler(router::accept).listen(8080);
}private void hello(RoutingContext rc) {String message = "hello";if (rc.pathParam("name") != null) {message += " " + rc.pathParam("name");}JsonObject json = new JsonObject().put("message", message);rc.response().putHeader(HttpHeaders.CONTENT_TYPE, "application/json").end(json.encode());
}

Vert.x提供了一个JsonObject类来创建和操作JSON结构。这段代码就绪之后,你应该可以打开浏览器指向:

  • http://localhost:8080 ——你将看到{“message”: “hello”}
  • http://localhost:8080/vert.x ——你将看到{“message”:”hello vert.x”}

打包和运行

使用Ctrl+C停止vertx:run的执行,从相同目录执行以下命令:

mvn package

这会在target目录中生成一个FatJar:hello-hellomicroservice-http-1.0-SNAPSHOT.jar。虽然FatJar往往偏胖,但这里的JAR具有合理的尺寸(~6.3MB),包含运行应用程序的所有内容:

java -jar target/hello-microservice-http-1.0-SNAPSHOT.jar

你可以通过打开:http://localhost:8080以检查确保它是运行的。保持进程运行,因为接下来的微服务将调用它。

消费HTTP微服务

一个微服务不能组成一个应用;你需要一个微服务系统。现在我们已有第一个运行的微服务,让我们编写第二个微服务来消费它。第二个微服务还提供了一个HTTP外观来执行它,每次执行会调用我们刚实现的微服务。本节中展示的代码位于代码仓库的microservices/helloconsumer-microservice-http目录。

项目创建

照旧,我们创建一个新的工程

mkdir hello-consumer-microservice-http
cd hello-consumer-microservice-http
mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \-DprojectGroupId=io.vertx.microservice \-DprojectArtifactId=hello-consumer-microservice-http \-Dverticle=io.vertx.book.http.HelloConsumerMicroservice \-Ddependencies=web,web-client,rx

最后的命令行添加了另一个依赖:Vert.x Web客户端,一个异步的HTTP客户端。我们将使用这个客户端调用第一个微服务。该命令还添加了Vert.x RxJava绑定,我们随后会使用。

现在编辑src/main/java/io/vertx/book/http/HelloConsumerMicroservice.java文件并且更新它以便包含:

package io.vertx.book.http;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.*;
import io.vertx.ext.web.client.*;
import io.vertx.ext.web.codec.BodyCodec;
public class HelloConsumerMicroservice extends AbstractVerticle {private WebClient client;@Overridepublic void start() {client = WebClient.create(vertx);Router router = Router.router(vertx);router.get("/").handler(this::invokeMyFirstMicroservice);vertx.createHttpServer().requestHandler(router::accept).listen(8081);}private void invokeMyFirstMicroservice(RoutingContext rc) {HttpRequest<JsonObject> request = client.get(8080, "localhost","/vert.x").as(BodyCodec.jsonObject());request.send(ar -> {if (ar.failed()) {rc.fail(ar.cause());} else {rc.response().end(ar.result().body().encode());}});}
}

在start方法中,我们创建了一个WebClient和一个Router。在创建的Router上,我们注册了一个到“/”的路由并且启动HTTP服务器,传递Router的accept方法作为requestHandler。路由处理器是一个方法引用(hello)。这个方法使用WebClient通过一个具体的路径(/vert.x)调用第一个微服务,并且将结果写到HTTP响应。

一旦HTTP请求创建,我们调用send发送请求。当响应到达或者发生错误时,我们传递的处理器被调用。if-else块检测执行是否成功。不要忘记它是一个远程交互,有许多原因会失败。例如第一个微服务可能未运行。当它成功后,我们将收到的载荷写入响应;否则,我们回复一个500响应。

调用服务多于一次

现在我们修改当前的行为,调用hello微服务两次,使用两个不同的(路径)参数:

HttpRequest<JsonObject> request1 = client.get(8080, "localhost", "/Luke").as(BodyCodec.jsonObject());
HttpRequest<JsonObject> request2 = client.get(8080, "localhost", "/Leia").as(BodyCodec.jsonObject());

这两个请求是独立的并且可以并发执行。但是此处我们想要写一个组合了这两个结果的响应。调用服务两次并且装配两个结果的代码可能会变得复杂。当我们收到其中一个响应时,我们需要检查另一个请求是否已完成。虽然这部分代码对于两个请求仍然是可管理的,但是当我们需要处理更多时,它会变得极度复杂。幸运的是,如前一章所述,我们可以使用响应式编程和RxJava来简化代码。

我们通过vertx-maven-plugin导入Vert.x RxJava API。在HelloConsumerMicroservice中,我们替换import声明:

import io.vertx.core.json.JsonObject;
import io.vertx.rxjava.core.AbstractVerticle;
import io.vertx.rxjava.ext.web.*;
import io.vertx.rxjava.ext.web.client.*;
import io.vertx.rxjava.ext.web.codec.BodyCodec;
import rx.Single;

使用RX,我们编写的调用两次请求并且使用它们构建响应的复杂代码变得更简单:

private void invokeMyFirstMicroservice(RoutingContext rc) {HttpRequest<JsonObject> request1 = client.get(8080, "localhost", "/Luke").as(BodyCodec.jsonObject());HttpRequest<JsonObject> request2 = client.get(8080, "localhost", "/Leia").as(BodyCodec.jsonObject());Single<JsonObject> s1 = request1.rxSend().map(HttpResponse::body);Single<JsonObject> s2 = request2.rxSend().map(HttpResponse::body);Single.zip(s1, s2, (luke, leia) -> {// We have the results of both requests in Luke and Leiareturn new JsonObject().put("Luke", luke.getString("message")).put("Leia", leia.getString("message"));}).subscribe(result -> rc.response().end(result.encodePrettily()),error -> {error.printStackTrace();rc.response().setStatusCode(500).end(error.getMessage());});
}

注意rxSend方法的调用。Vert.x的RxJava方法以rx作为前缀,很容易辨认。rxSend的结果是一个Single,即一个元素的Ovservable,表示一个操作的延期结果。single.zip方法将一组Single作为输入,一旦它们所有都接收到了值,使用这些结果调用一个函数。Single.zip产生另一个Single,包含了函数的结果。最后,我们subscribe。这个方法使用两个函数作为参数:

  1. 第一个调用使用zip函数的结果(一个JSON对象)。我们将接收到的JSON载荷写到HTTP响应。
  2. 如果一些事情失败了(超时、异常等等)将会调用第二个。这种情况,我们响应一个空的JSON对象。

代码就绪之后,如果我们打开http://localhost:8081,并且hello微服务依旧运行,我们将看到:

{"Luke" : "hello Luke","Leia" : "hello Leia"
}

这些微服务是响应式微服务?

此刻,我们有两个微服务。它们是独立的并且可以按它们自己的步调部署和更新。它们还使用一个轻量级的协议(HTTP)交互。但是它们是响应式微服务吗?不,它们不是。记住,要称为响应式,一个微服务必须是:

  • 自治的
  • 异步的
  • 可快速恢复的
  • 有弹性的

当前设计的主要问题是两个微服务之间的紧密耦合。Web客户端被配置为明确针对第一个微服务。如果第一个微服务失败,我们不能通过调用另一个来恢复。如果我们当前欠载(under load,应该是指由于hello微服务不足导致消费该服务的其它服务无法满载工作),创建一个新的hello微服务实例不能帮助我们。感谢Vert.x Web客户端,这些交互是异步的。尽管如此,由于我们没有使用一个虚拟地址(目的地)调用微服务,而是使用它的直接URL,它不能提供我们需要的快速恢复能力和弹性。

重要的是要注意,像在第二个微服务中那样使用响应式编程不能带给你响应式系统的好处。它提供了一个简洁的模型来协调异步动作,但是它没有提供我们需要的快速恢复能力和弹性。

我们可以在响应式微服务使用HTTP吗?是的。但是这需要一些基础设施来路由虚拟URL到一组服务。我们也需要实现一个负载均衡策略来提供弹性和健康检查支持以提升快速恢复能力。

不要失望,下一节我们将向响应式微服务迈出一大步。

Vert.x事件总线——一个消息支柱

Vert.x提供了一个事件总线,允许应用程序的不同组件使用消息(messages)进行交互。消息被发送到地址(addresses)并有一组消息头(headers)和一个消息体(body)。地址是一个表示目的地的不透明字符串。消息消费者注册它们自己到地址上以接收消息。事件总线也是集群的,意味着它可以通过网络在分布式的发送者和消费者之间分发消息。通过以集群的模式启动一个Vert.x应用,节点被链接以启用共享数据结构、硬停止故障检测、负载均衡组通讯。事件总线可以在集群中所有节点之间分发消息。为了创建这样一个集群配置,你可以使用Apache Ignite、Apache Zookeeper、Infinispan或者Hazelcast。在本报告中,我们将使用Infinispan,但是我们不涉及高级配置。关于这些,参考Infinispan文档(http://infinispan.org/)。在Infinispan(或者你选择的技术)管理节点发现和存储时,事件总线通讯使用直接的P2P TCP链接。

事件总线提供了三种类型的传递语义。首先,send方法允许组件发送一条消息到一个地址。一个单独的消费者将接收这条消息。如果多于一个消费者在这个地址上注册,Vert.x应用轮询策略来选择一个消费者:

// Consumer
vertx.eventBus().consumer("address", message -> {System.out.println("Received: '" + message.body() + "'");
});
// Sender
vertx.eventBus().send("address", "hello");

与send相反,你可以使用publish方法传递消息到所有在这个地址注册的消费者。最后,send方法可以与一个reply处理器配合使用。request/response机制允许实现两个组件间的基于消息的异步交互:

// Consumer
vertx.eventBus().consumer("address", message -> {message.reply("pong");
});
// Sender
vertx.eventBus().send("address", "ping", reply -> {if (reply.succeeded()) {System.out.println("Received: " + reply.result().body());} else {// No reply or failurereply.cause().printStackTrace();}
});

如果你正使用Rx-ified API,你可以使用rxSend方法,它会返回一个Single。这个Single在收到应答时接收一个值。我们马上就要看到这个方法的运作了。

基于消息的微服务

让我们重新实现hello微服务,这次使用事件总线来替代HTTP服务器接收请求。微服务应答消息以提供响应。

项目创建

让我们创建一个新项目。这次我们将添加Infinispan依赖,一个内存数据网格,用于管理集群:

mkdir hello-microservice-message
cd hello-microservice-message
mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \-DprojectGroupId=io.vertx.microservice \-DprojectArtifactId=hello-microservice-message \-Dverticle=io.vertx.book.message.HelloMicroservice \-Ddependencies=infinispan

一旦生成,我们可能需要配置Infinispan以建立集群。默认配置使用multicast发现节点。如果你的网络支持多播,它会不错。否则,检查代码库的 resource/cluster目录(包含Infinispan配置?)。

编写消息驱动Verticle

编辑src/main/java/io/vertx/book/message/HelloMicroservice.java文件,并且更新start方法:

@Override
public void start() {// Receive message from the address 'hello'vertx.eventBus().<String>consumer("hello", message -> {JsonObject json = new JsonObject().put("served-by", this.toString());// Check whether we have received a payload in the// incoming messageif (message.body().isEmpty()) {message.reply(json.put("message", "hello"));} else {message.reply(json.put("message","hello " + message.body()));}});
}

代码从vertx对象获取eventBus并且注册一个consumer到hello这个地址。当接收到消息时,它会答复它。依赖于接收到的消息是否有消息体,我们计算一个不同的响应。如前一章的示例中,我们发送回一个JSON对象。你可能会奇怪,为什么我们在JSON中添加了served-by。你将很快看到为什么。现在Verticle写好了,是时候启动它了,通过:

mvn compile vertx:run \-Dvertx.runArgs="-cluster -Djava.net.preferIPv4Stack=true"

-cluster告诉Vert.x,以cluster模式启动:

现在,让我们写一个微服务来消费这个服务。

开始基于消息的交互

在本节中,我们将创建另一个微服务,通过发送一个消息到地址hello并获得一个回复来执行hello微服务。微服务将重新实现前一章相同的逻辑并且执行该服务两次(一次使用Luke,一次使用Leia)。

照例,我们创建一个新的项目:

mkdir hello-consumer-microservice-message
cd hello-consumer-microservice-message
mvn io.fabric8:vertx-maven-plugin:1.0.5:setup \-DprojectGroupId=io.vertx.microservice \-DprojectArtifactId=hello-consumer-microservice-message \-Dverticle=io.vertx.book.message.HelloConsumerMicroservice \-Ddependencies=infinispan,rx

此处我们还添加了Vert.x RxJava支持,以便从Vert.x提供的RXified API中获益。如果你在前面章节更新了Infinispan配置,你需要拷贝它到这个新项目:

现在,编辑io.vertx.book.message.HelloConsumerMicroservice。由于我们使用RxJava,因此需要变更import部分以匹配io.vertx.rxjava.core.AbstractVerticle。然后实现start方法:

@Override
public void start() {EventBus bus = vertx.eventBus();Single<JsonObject> obs1 = bus.<JsonObject>rxSend("hello", "Luke").map(Message::body);Single<JsonObject> obs2 = bus.<JsonObject>rxSend("hello", "Leia").map(Message::body);Single.zip(obs1, obs2, (luke, leia) ->new JsonObject().put("Luke", luke.getString("message")).put("Leia", leia.getString("message"))).subscribe(x -> System.out.println(x.encode()),Throwable::printStackTrace);
}

这部分代码与前一章的代码非常相似。我们不再使用WebClient执行一个HTTP endpoint,而是使用事件总线发送消息到hello地址,并且提取答复体。我们使用zip操作获取这两个响应并构建最终结果。在subscribe方法中,我们打印最终结果到控制台或者打印堆栈(异常情况)。

让我们结合HTTP服务器。当收到HTTP请求时,我们执行hello服务两次并且返回构建结果作为响应:

@Override
public void start() {vertx.createHttpServer().requestHandler(req -> {EventBus bus = vertx.eventBus();Single<JsonObject> obs1 = bus.<JsonObject> rxSend("hello", "Luke").map(Message::body);Single<JsonObject> obs2 = bus.<JsonObject> rxSend("hello", "Leia").map(Message::body);Single.zip(obs1,obs2,(luke, leia) -> new JsonObject().put("Luke",luke.getString("message") + " from " + luke.getString("served-by")).put("Leia",leia.getString("message") + " from " + leia.getString("served-by"))).subscribe(x -> req.response().end(x.encodePrettily()),t -> {t.printStackTrace();req.response().setStatusCode(500).end(t.getMessage());});}).listen(8082);
}

最后的代码只是将事件总线交互封装到一个requestHandler并且处理HTTP响应。失败的情况下,我们返回一个包含错误消息的JSON对象。

如果你通过mvn compile vertx:run -Dvertx.runArgs=”-cluster -Djava.net.preferIPv4Stack=true”运行代码,并且打开浏览器到http://localhost:8082,你将看到像下面的内容:

{
"Luke" : "hello Luke from ...HelloMicroservice@39721ab",
"Leia" : "hello Leia from ...HelloMicroservice@39721ab"
}

我们现在是响应式吗?

代码非常接近我们前面编写的基于HTTP的微服务。唯一不同是我们使用事件总线替代了HTTP。这会改变我们的响应性(reactiveness)?是的!让我们看看原因。

弹性

弹性是微服务HTTP版本没有实施的特性之一。因为微服务针对一个特定的微服务实例(使用硬编码的URL),它没有提供我们需要的弹性。但是现在,我们使用发送到一个地址的消息,这改变了游戏。让我们看看这个微服务系统的行为如何。

记住前面执行的输出。返回的JSON对象显示了计算hello消息的Verticle。输出总是显示相同的Verticle。表明消息来自相同的实例。我们期望如此,因为我们只有一个单一的实例运行。让我们看一下两个实例的时候会发生什么。

停止Hello微服务的执行(vertx:run)并且运行:

mvn clean package

然后,在hello-microservice-message目录下打开两个不同的终端,执行以下命令(在每个终端):

java -jar target/hello-microservice-message-1.0-SNAPSHOT.jar \
--cluster -Djava.net.preferIPv4Stack=true

这启动了Hello微服务的两个实例。返回你的浏览器,刷新页面,你将看到一些东西如:

{"Luke" : "hello Luke from ...HelloMicroservice@16d0d069","Leia" : "hello Leia from ...HelloMicroservice@411fc4f"
}

这两个Hello的实例都被使用。Vert.x集群链接不同的节点,事件总线集群化。多亏事件总线轮询(round-robin),Vert.x事件总线分发消息到有效的实例,并且因此在监听相同地址的不同节点之间平衡负载。

因此,通过使用事件总线,我们拥有了我们需要的弹性属性。

快速恢复能力

快速恢复能力怎么样?在当前的代码中,如果hello微服务失败,我们将获得一个失败并执行以下代码:

t -> {t.printStackTrace();req.response().setStatusCode(500).end(t.getMessage());
}

尽管用户得到一个错误消息,但我们不会崩溃,我们没有限制我们的可伸缩性,我们仍旧可以处理请求。尽管如此,为了提升用户体验,我们应该总是及时回复用户,即便我们没有从服务收到响应。为了实现这个逻辑,我们可以通过一个超时设置来增强代码。

为了说明这个,让我们修改Hello微服务来添加失败和问题行为处理。这部分代码位于代码仓库的microservices/hello-microservice-faulty目录下。

这个新的start方法随机选择三种策略中的一种:(1)回复一个明确的失败,(2)forget回复(在消费者端导致一个超时),(3)发送正确的结果。

@Override
public void start() {vertx.eventBus().<String> consumer("hello",message -> {double chaos = Math.random();JsonObject json = new JsonObject().put("served-by",this.toString());if (chaos < 0.6) {// Normal behaviorif (message.body().isEmpty()) {message.reply(json.put("message", "hello"));} else {message.reply(json.put("message", "hello " + message.body()));}} else if (chaos < 0.9) {System.out.println("Returning a failure");// Reply with a failuremessage.fail(500, "message processing failure");} else {System.out.println("Not replying");// Just do not reply, leading to a timeout on the// consumer side.}});
}

重新打包并重启Hello微服务的两个实例。

随着故障注入工作的就绪,我们需要提升我们消费者的容错性。事实上,消费者可能会超时或接收显式故障。在hello消费者微服务中,将我们如何调用hello服务修改为:

EventBus bus = vertx.eventBus();
Single<JsonObject> obs1 = bus.<JsonObject>rxSend("hello", "Luke").subscribeOn(RxHelper.scheduler(vertx)).timeout(3, TimeUnit.SECONDS).retry().map(Message::body);
Single<JsonObject> obs2 = bus.<JsonObject>rxSend("hello", "Leia").subscribeOn(RxHelper.scheduler(vertx)).timeout(3, TimeUnit.SECONDS).retry().map(Message::body);

这部分代码位于代码仓库的microservices/hello-consumermicroservice-timeout。如果我们在给定时间内没有收到响应,timeout方法会发出一个异常。retry方法重试来获取值,如果它获得一个失败(以一个超时或者明确的失败的形式)。subscribeOn方法指示调用需要在哪个线程上完成。我们使用Vert.x的事件循环(EventLoop)调用我们的回调。如果没有指定这个,方法将由来自默认RxJava线程池的线程执行,这打破了Vert.x线程模型。RXHelper类由Vert.x提供。盲目重试服务调用不是一个非常聪明的容错策略,它甚至可能有害,下一章详细介绍了不同的方法。

现在你可以重新加载页面。你会总是得到一个结果,即便有错误或者超时。记住调用服务时,线程是非阻塞的,因此你可以总是接收新的请求并及时的响应它们。然而,这种超时重试常常弊大于利,正如我们将在下一章中看到的那样。

总结

在本章中,我们学习了如何使用Vert.x开发一个HTTP微服务并且消费它。如我们学到的,在代码中硬编码被消费方服务的URL不是个好主意,因为它破坏了响应式特性的其中之一。在第二部分,我们使用消息替换HTTP交互,展示了消息和Vert.x事件总线如何帮助构建响应式微服务。

那么,我们已经结束了吗?是也不是。是,我们知道如何构建响应式微服务,但是还有一些缺点我们需要看看。首先,要是只有HTTP服务怎么办?你如何避免硬编码地址?快速恢复能力如何?本章我们看到了超时和重试,但是断路器(circuit breakers)、故障转移和舱壁(bulkheads)怎么样?让我们继续旅程。

如果你想进一步讨论这些话题:

  1. Vert.x Web文档
  2. Vert.x Web Client文档
  3. Vert.x响应式微服务

在Java中构建响应式微服务系统——第三章 构建响应式微服务相关推荐

  1. 26:第三章:开发通行证服务:9:【注册/登录】接口:验证码校验OK后,先根据手机号去查查该用户是否已存在,如果用户不存在就创建这个用户;(tkmybatis查询构建查询条件,雪花算法,枚举类等等)

    说明: (1)本篇博客内容:继续开发[注册/登录]接口: ● 在[25:第三章:开发通行证服务:8:[注册/登录]接口:接收并校验"手机号和验证码"参数:]中,[注册/登录]接口, ...

  2. 构建超级智能未来系统的三原则

    <崛起的超级智能>作者,计算机博士刘锋 前言:科技领域看不见的手在过去50年促使互联网从网状结构进化成为大脑模型,而人类群体智慧与机器群体智能通过这个互联网大脑架构形成人类前所未有的超级智 ...

  3. JAVA中几种循环结构的表示_本文通过实例讲解给大家介绍Java中for、while、do while三种循环语句的区别,具体详情如下所示:第一种:for循环 循环结构for语句的格式...

    本文通过实例讲解给大家介绍Java中for.while.do while三种循环语句的区别,具体详情如下所示: 第一种:for循环 循环结构for语句的格式: for(初始化表达式;条件表达式;循环后 ...

  4. 33:第三章:开发通行证服务:16:使用Redis缓存用户信息;(以减轻数据库的压力)

    说明: (1)声明:这个其中的区别和相同点,要清楚: ● 在[32:第三章:开发通行证服务:15:浏览器存储介质,简介:]中,前端使用[把"用户基本信息"存到Session Sto ...

  5. 32:第三章:开发通行证服务:15:浏览器存储介质,简介;(cookie,Session Storage,Local Storage)

    说明: (1)简单介绍浏览器存储介质:cookie,Session Storage,Local Storage: (2)目前为止的.可以确定的几点: ● 这些存储介质都是浏览器的,我们要想使用这些存储 ...

  6. 优惠券系统-第三章-活动中心

    优惠券系统-第三章介绍 本文主要设计一个基于送券,送积分等的活动中心. 活动中心设计 活动中心主要是有各种活动,比如双十一活动,可能参加一个活动会送多个优惠券,某一个活动送大礼包还可能包含了积分(类似 ...

  7. Spring Boot + Spring Cloud 构建微服务系统(三):服务消费和负载(Feign)

    Spring Cloud Feign Spring Cloud Feign是一套基于Netflix Feign实现的声明式服务调用客户端.它使得编写Web服务客户端变得更加简单.我们只需要通过创建接口 ...

  8. Java中的微信支付:API V3对微信服务器响应进行签名验证3

    前言 牢记一句话:公钥加密,私钥解密:私钥加签,公钥验签. 微信支付V3版本前两篇分别讲了如何对请求做签名和如何获取并刷新微信平台公钥,本篇将继续展开如何对微信支付响应结果的验签. 2. 为什么要对响 ...

  9. java 中的网络编程(Socket、TCP三次握手四次挥手、TCP/UDP/URL)

    文章目录 前言 一.网络编程概述 二.网络通信要素概述 1.如何实现网络中的主机互相通信 2.网络通信协议 3.IP和端口号 4.InetAddress类 5.网络协议 6.TCP/IP协议簇 7.T ...

最新文章

  1. python 运算符 is 与 is not 的理解
  2. IPv6的一些特殊地址
  3. VS2010水晶报表的添加与使用
  4. python推荐系统-RecQ-Python推荐系统框架
  5. 使用Ansible批量部署SSH免密登录远程主机
  6. 数字图像处理与python实现 pdf_正版 数字图像处理与Python实现 高等院校计算机科学 人工智能 信号与信息处理 通信工程等专业的...
  7. rm linux 复制目录,linux学习(四)复制(cp)移动(mv)删除(rm)查找(find)文件、文件夹操作、软硬链接的区别...
  8. 如何用10万资金炒房到1000万
  9. 团队协作新趋势:语雀——助力更多:个人、企业、博客、协作、托管、画布设计、资源托管(立体化趋势)
  10. 现实世界的数据结构:JavaScript中的表格和图形
  11. shareplex的安装起停服务(添加新用户)
  12. linux java bin 安装_linux下安装后缀为bin的Java JDK
  13. NS方程求解-PointNet和升维思想(效果很差)
  14. autoconfig
  15. python基础(1)---python简介
  16. android搜索app下载地址,如何找出APP的URL Scheme
  17. Rasdial实现宽带自动连接
  18. 异次元发卡系统源码荔枝发卡V3.0
  19. php提取域名字符串,由字符串,提取完整子域名的方法 -php
  20. kiwix Android 地址,Kiwix 将Wikipedia下载到您的计算机或Android上以进行离线访问

热门文章

  1. 初中数学老师计算机培训反思,初中数学特级教师培训会学习心得体会
  2. 为什么要学数学、语文?还有英语!
  3. 解决:U盘无法安装到这个磁盘选中的磁盘具有MBR分区表
  4. VR全景看家装,让家装不再“纸上谈兵”
  5. 不重视需求过程的项目队伍将自食其果
  6. CentOS7 开启 BBR 加速
  7. 让笔记本触摸板默认关闭
  8. boto3使用教程用法
  9. pfv和php,【Retina China 2019】工欲善其事必先利其器——影像学在眼底病中的应用与发展...
  10. 馋猫美食记录本_隐私政策