部署与容错

在 Vert.x 中使用 Resilience4j

本文档将向您展示如何在 Vert.x 应用程序中使用 Resilience4j。

Resilience4j 是一个流行的库,它实现了常见的容错策略

  • 舱壁(并发限制器)

  • 断路器

  • 速率限制器

  • 重试

  • 时间限制器(超时)

在本操作指南中,我们只演示了断路器的用法,但本操作指南的仓库中包含了上述所有策略的 Vert.x 适配器。

我们在此将使用的 Resilience4j 2.0 需要 Java 17。

您将构建什么

您将把断路器与 HTTP 客户端一起使用,以防止对持续返回错误的服务器执行请求。这主要有两个目的:

  • 避免对可能已过载的服务器产生更多负载,

  • 当失败很可能发生时快速失败。

该应用程序由几个类组成:

  1. CircuitBreakerVerticle

  2. VertxCircuitBreaker 适配器,包含在本操作指南的仓库中

您需要什么

  • 文本编辑器或 IDE

  • Java 17 或更高版本

  • Maven 或 Gradle

创建项目

此项目的代码包含功能等效的 Maven 和 Gradle 构建文件。

使用 Maven

首先,您的 pom.xml 文件应声明版本属性:

Maven pom.xml:版本属性
<resilience4j.version>2.1.0</resilience4j.version>
<vertx.version>5.0.0.CR2</vertx.version>

接下来,导入 Resilience4j 和 Vert.x 的 BOM 以管理依赖版本:

Maven pom.xml:BOM 导入
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.github.resilience4j</groupId>
      <artifactId>resilience4j-bom</artifactId>
      <version>${resilience4j.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
    <dependency>
      <groupId>io.vertx</groupId>
      <artifactId>vertx-stack-depchain</artifactId>
      <version>${vertx.version}</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

然后,添加对 Resilience4j 断路器库的依赖:

Maven pom.xml:Resilience4j 依赖
<dependency>
  <groupId>io.github.resilience4j</groupId>
  <artifactId>resilience4j-circuitbreaker</artifactId>
</dependency>

最后,添加对 Vert.x Web 和 Vert.x Web 客户端库的依赖:

Maven pom.xml:Vert.x 依赖
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web</artifactId>
</dependency>
<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-web-client</artifactId>
</dependency>

使用 Gradle

您的 build.gradle.kts 文件(假设您使用带有 Kotlin DSL 的 Gradle)中的 dependencies 块应首先导入 Resilience4j 和 Vert.x 的 BOM 以管理依赖版本:

Gradle build.gradle.kts:BOM 导入
implementation(platform("io.github.resilience4j:resilience4j-bom:2.1.0"))
implementation(platform("io.vertx:vertx-stack-depchain:5.0.0.CR2"))

然后,添加对 Resilience4j 断路器库的依赖:

Gradle build.gradle.kts:Resilience4j 依赖
implementation("io.github.resilience4j:resilience4j-circuitbreaker")

最后,添加对 Vert.x Web 和 Vert.x Web 客户端库的依赖:

Gradle build.gradle.kts:Vert.x 依赖
implementation("io.vertx:vertx-web")
implementation("io.vertx:vertx-web-client")

使用断路器保护 Vert.x Web 客户端请求

首先,让我们创建主 CircuitBreakerVerticle

CircuitBreakerVerticle
public class CircuitBreakerVerticle extends VerticleBase {
}

该类将使用 VertxCircuitBreaker 类,该类在本操作指南的仓库中提供。

创建断路器

在 verticle 的 start 方法中,让我们创建一个断路器实例,该实例将在所有请求之间共享。出于演示目的,我们将断路器配置为在仅 5 个请求后开始计算失败率:

创建断路器
CircuitBreaker cb = CircuitBreaker.of("my-circuit-breaker", CircuitBreakerConfig.custom()
  .minimumNumberOfCalls(5)
  .build());
与我们的演示配置相比,默认设置对于实际应用程序更具意义。

创建服务器

接下来,我们将使用 Vert.x Web 暴露一个简单的端点。该端点的唯一目的是执行 HTTP 请求并使用断路器保护它:

创建端点
Router router = Router.router(vertx);
WebClient client = WebClient.create(vertx);

router.get("/").handler(ctx -> {
  VertxCircuitBreaker.executeFuture(cb, () -> {
    return client.get(8080, "localhost", "/does-not-exist")
      .as(BodyCodec.string())
      .send()
      .expecting(HttpResponseExpectation.SC_SUCCESS);
  })
    .onSuccess(response -> ctx.end("Got: " + response.body() + "\n"))
    .onFailure(error -> ctx.end("Failed with: " + error.toString() + "\n"));
});

这需要一些解释。

/ 上的端点使用了 VertxCircuitBreaker 适配器,它将 Resilience4j API 和 Vert.x Future 连接起来。该适配器期望一个 Supplier<Future> 作为要保护的操作。

这里的 Future 来自 Vert.x Web 客户端的 get(8080, "localhost", "/does-not-exist") 调用。

正如您所能想象的,我们在这里发出的请求永远不会成功。因此,断路器将在 5 个请求(如上配置)后介入,并阻止进一步调用受保护的操作。相反,executeFuture 返回的 Future 将立即因 CallNotPermittedException 异常而失败。

启动服务器

最后,我们将启动服务器:

启动服务器
return vertx.createHttpServer()
  .requestHandler(router)
  .listen(8080)
  .onSuccess(server -> {
    System.out.println("HTTP server started on port " + server.actualPort());
  });

verticle 的整个 start 方法如下所示:

verticle 的 start 方法
@Override
public Future<?> start() {
  CircuitBreaker cb = CircuitBreaker.of("my-circuit-breaker", CircuitBreakerConfig.custom()
    .minimumNumberOfCalls(5)
    .build());

  Router router = Router.router(vertx);
  WebClient client = WebClient.create(vertx);

  router.get("/").handler(ctx -> {
    VertxCircuitBreaker.executeFuture(cb, () -> {
      return client.get(8080, "localhost", "/does-not-exist")
        .as(BodyCodec.string())
        .send()
        .expecting(HttpResponseExpectation.SC_SUCCESS);
    })
      .onSuccess(response -> ctx.end("Got: " + response.body() + "\n"))
      .onFailure(error -> ctx.end("Failed with: " + error.toString() + "\n"));
  });

  return vertx.createHttpServer()
    .requestHandler(router)
    .listen(8080)
    .onSuccess(server -> {
      System.out.println("HTTP server started on port " + server.actualPort());
    });
}

运行应用程序

CircuitBreakerVerticle 还需要一个 main 方法:

main 方法
public static void main(String[] args) {
  Vertx vertx = Vertx.vertx();
  vertx.deployVerticle(new CircuitBreakerVerticle());
}

然后您可以运行应用程序:

  • 直接从您的 IDE 运行,或者

  • 使用 Maven:mvn clean compile exec:java,或者

  • 使用 Gradle: ./gradlew run (Linux, macOS) 或 gradlew run (Windows)。

以下示例使用 HTTPie 命令行 HTTP 客户端。如果您的系统尚未安装它,请参阅其安装文档。

前 5 个请求

对于前 5 个请求,断路器不计算失败率,并将允许所有操作继续。正如预期,它们都将失败:

http localhost:8080
http localhost:8080
http localhost:8080
http localhost:8080
http localhost:8080

您应该看到以下输出 5 次:

Failed with: io.vertx.core.impl.NoStackTraceThrowable: Response status code 404 is not between 200 and 300

接下来,断路器介入

http localhost:8080

第 6 个请求将以不同的消息失败:

Failed with: io.github.resilience4j.circuitbreaker.CallNotPermittedException: CircuitBreaker 'my-circuit-breaker' is OPEN and does not permit further calls

我们看到断路器阻止了对服务器的请求执行。

总结

本文档涵盖了

  1. 创建 Resilience4j 断路器实例,

  2. 使用断路器保护其结果为 Vert.x Future 的操作。