微服务架构

Vert.x 断路器(Circuit Breaker)

Vert.x Circuit Breaker 是面向 Vert.x 响应式体系的断路器模式(Circuit Breaker Pattern)实现。它持续跟踪最近的失败次数,当失败达到指定阈值时触发熔断,阻止后续调用。此外,它支持在熔断或失败时可选地执行降级回退(Fallback)。

支持判定的失败类型包括:

  • 你的业务代码在 Future 中上报的失败

  • 你的业务代码抛出的未捕获异常

  • 未在超时时间内完成的 Future(超时 Timeout)

断路器所保护的操作应当是非阻塞且异步的,以充分发挥 Vert.x 响应式执行模型的优势。

使用 Vert.x Circuit Breaker

在构建描述文件中添加以下依赖:

  • Maven(在你的 pom.xml 中配置):

<dependency>
  <groupId>io.vertx</groupId>
  <artifactId>vertx-circuit-breaker</artifactId>
  <version>5.2.1</version>
</dependency>
  • Gradle(在你的 build.gradle 文件中配置):

compile 'io.vertx:vertx-circuit-breaker:5.2.1'

使用断路器

使用断路器通常分为两步:

  1. 创建断路器实例,并配置所需参数(超时时间、失败阈值、重试间隔等);

  2. 使用该断路器包裹并执行业务代码。

重要提示:切勿在每次调用时重复创建断路器!断路器是一个有状态的单例实体,强烈建议将其保存在类成员变量(Field)中复用。

基本使用示例如下:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions()
    .setMaxFailures(5) // 触发断路器开启(熔断)前的失败次数阈值
    .setTimeout(2000) // 若操作未能在此时间内完成(单位毫秒),则视为超时失败
    .setFallbackOnFailure(true) // 当发生失败时是否自动调用降级回调
    .setResetTimeout(10000) // 保持熔断状态的时长(毫秒),超时后进入半开状态尝试探测恢复
);

// ---
// 将断路器保存在变量中,并通过以下方式调用:
// ---

breaker.execute(promise -> {
  // 在断路器保护下执行的代码块
  // 代码通过传入的 promise 上报成功或失败
  // 若 promise 标记为失败,断路器会累加失败计数
}).onComplete(ar -> {
  // 获取操作的最终结果
});

被执行的代码块接收一个 Promise 参数,用于声明该操作的成功或失败以及输出结果。在下面的示例中,受保护的操作是调用 REST 端点:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions().setMaxFailures(5).setTimeout(2000)
);

// ---
// 将断路器保存在类属性中并使用:
// ---

breaker.<String>execute(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 状态码错误");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
}).onComplete(ar -> {
  // 处理最终结果
});

操作的最终结果可通过以下方式获取:

  • 调用 execute 方法时返回的 Future

  • 调用 executeAndReport 方法时传入的 Promise

你可以可选地提供降级回退函数(Fallback),当断路器处于熔断开启(Open)状态时自动执行降级逻辑:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions().setMaxFailures(5).setTimeout(2000)
);

// ---
// 将断路器保存在变量中:
// ---

breaker.executeWithFallback(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 错误");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
}, v -> {
  // 当断路器处于开启熔断状态时执行此降级逻辑
  return "降级默认响应内容";
}).onComplete(ar -> {
  // 处理最终结果(正常结果或降级结果)
});

降级函数会在断路器处于开启状态时被调用,或者当开启了 isFallbackOnFailure 且操作发生失败时被调用。配置降级后,整体结果将由降级函数的返回值确定。降级函数接收一个 Throwable 异常对象作为参数,并返回预期类型的数据。

你也可以直接在 CircuitBreaker 实例上全局绑定降级函数:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions().setMaxFailures(5).setTimeout(2000)
).fallback(v -> {
  // 当断路器开启时执行
  return "hello";
});

breaker.<String>execute(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 状态错误");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
});

捕获的异常类型

降级函数接收到的异常主要包括:

自动重试机制(Retries)

你还可以通过 setMaxRetries 指定在彻底宣告失败前断路器自动重试执行的次数。 若将其设置为大于 0 的数值,受保护的代码块在最终失败前会多次尝试执行。 一旦某次重试执行成功,处理器将立即收到成功通知,且不再发起后续重试。 重试机制仅在断路器处于关闭(Closed)正常工作状态下生效。

如果将 maxRetries 设置为 2,受保护操作最多可能被调用 3 次(1 次初始尝试 + 2 次重试)。

默认情况下,重试之间的等待延迟为 0,即立即紧接着执行下一次重试。然而这会加剧下游故障服务的瞬时并发压力,可能延缓其自愈恢复。因此强烈建议为重试配置非零的间隔延迟。

通过 retryPolicy 方法可以定制重试策略。 重试策略是一个接收当前操作失败原因与当前重试轮次作为参数的函数,并返回下一次重试执行前的等待延迟毫秒数。

它允许你实现精细的重试逻辑,例如根据故障服务返回的 Retry-After 标头计算等待时间。 Vert.x 提供了常用的开箱即用策略:RetryPolicy.constantDelay(固定间隔)、RetryPolicy.linearDelay(线性递增)以及 RetryPolicy.exponentialDelayWithJitter(带随机抖动的指数退避)。

以下是带抖动指数退避的示例:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions().setMaxFailures(5).setMaxRetries(5).setTimeout(2000)
).retryPolicy(RetryPolicy.exponentialDelayWithJitter(50, 500));

breaker.<String>execute(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 错误");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
});

失败策略(Failure Policy)

默认情况下,断路器的失败策略是在命令未成功完成时直接将其计为失败。 你可以通过 failurePolicy 灵活覆盖该判断准则。 这允许你精准定义在什么条件下将 AsyncResult 视为断路器意义上的失败。

以下是使用自定义失败判定策略的示例:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx);
breaker.<HttpClientResponse>failurePolicy(ar -> {
  // 失败条件:操作本身抛错,或 HTTP 响应状态码不等于 200
  if (ar.failed()) {
    return true;
  }
  HttpClientResponse resp = ar.result();
  return resp.statusCode() != 200;
});

Future<HttpClientResponse> future = breaker.execute(promise -> {
  vertx.createHttpClient()
    .request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(request -> request.send()
      // 响应体完整接收后完成
      .compose(response -> response.body().map(response)))
    .onComplete(promise);
});

状态切换回调(Callbacks)

你可以注册回调函数,在断路器开启(熔断)或关闭(恢复)时获得通知:

CircuitBreaker breaker = CircuitBreaker.create("my-circuit-breaker", vertx,
  new CircuitBreakerOptions().setMaxFailures(5).setTimeout(2000)
).openHandler(v -> {
  System.out.println("断路器已开启(发生熔断)");
}).closeHandler(v -> {
  System.out.println("断路器已关闭(恢复正常)");
});

breaker.<String>execute(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 错误");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
});

你还可以在断路器进入半开状态(Half-Open,尝试恢复自愈)时接收通知,通过 halfOpenHandler 注册监听。

另外,你还可以使用构建器 API CircuitBreaker.builder 来组装断路器。通过构建器配置的处理器在实例构建完成后即被固化锁定:若在构建后再次调用 openHandler、halfOpenHandler 或 closeHandler,将抛出 IllegalStateException,从而防止预先配置的回调在运行期间被无意篡改覆盖:

CircuitBreaker breaker = CircuitBreaker.builder("my-circuit-breaker", vertx)
  .with(new CircuitBreakerOptions().setMaxFailures(5).setTimeout(2000))
  .openHandler(v -> {
    System.out.println("断路器已开启(熔断)");
  })
  .closeHandler(v -> {
    System.out.println("断路器已关闭(恢复正常)");
  })
  .build();

breaker.<String>execute(promise -> {
  vertx.createHttpClient().request(HttpMethod.GET, 8080, "localhost", "/")
    .compose(req -> req
      .send()
      .compose(resp -> {
        if (resp.statusCode() != 200) {
          return Future.failedFuture("HTTP 状态异常");
        } else {
          return resp.body().map(Buffer::toString);
        }
      }))
    .onComplete(promise);
});

事件总线广播通知(Event bus notification)

每当断路器状态发生变更时,都可以将事件广播发布到事件总线(Event Bus)上。

要启用此功能,请将 通知地址 设置为非 null 值:

options.setNotificationAddress(CircuitBreakerOptions.DEFAULT_NOTIFICATION_ADDRESS);

广播事件中包含了断路器的指标度量数据。 计算这些指标需要在项目的构建配置中添加以下依赖:

  • Maven(在你的 pom.xml 中配置):

<dependency>
  <groupId>org.hdrhistogram</groupId>
  <artifactId>HdrHistogram</artifactId>
  <version>2.1.12</version>
</dependency>
  • Gradle(在你的 build.gradle 文件中配置):

compile 'org.hdrhistogram:HdrHistogram:2.1.12'

启用后,通知默认仅投递给本地消费者。 若需要将通知广播给集群中的所有消费者,可以通过 setNotificationLocalOnly 进行调整。

每个事件消息均为一个 JSON 对象,包含:

  • state:断路器新状态(OPEN、CLOSED、HALF_OPEN)

  • name:断路器名称

  • failures:当前的失败计数

  • node:当前节点标识(若非集群模式则为 local)

  • 指标度量数据(Metrics)

半开状态(The half-open state)工作原理

当断路器处于 OPEN(开启/熔断)状态时,对断路器的调用会立即判定失败,根本不会尝试去调用真实的后端操作。在经过一段设定的时间窗口后(由 setResetTimeout 设定),断路器认为下游服务可能已恢复健康,于是自动转入 HALF_OPEN(半开)状态。在此状态下,系统会放行下一个请求去执行真实的受保护操作。若该试探性调用成功完成,断路器自动重置并恢复到 CLOSED(关闭)正常状态;而若该试探调用仍然失败,断路器将重新退回到 OPEN 熔断状态,并等待下一个超时窗口重新尝试。

结合 Resilience4j 使用

Resilience4j 是 Java 生态中广泛流行的容错框架,实现了多种主流容错设计模式:

  • Bulkhead(舱壁隔离 / 并发限制器)

  • Circuit Breaker(断路器)

  • Rate Limiter(限流器)

  • Retry(重试)

  • Time Limiter(超时时间限制器)

官方发布了一份 How-to 实践指南,详细演示了如何在 Vert.x 中整合 Resilience4j。 该指南的配套 代码仓库 提供了针对上述所有容错策略的 Vert.x 适配器。这些适配器无缝桥接了 Resilience4j API 与 Vert.x 的 Future 异步编程模型。

Resilience4j 2.0 版本要求 Java 17 或更高版本。