0Pricing
Spring Boot 4 Complete Guide · 课时

断路器与舱壁隔离

使用 Resilience4j 断路器、舱壁和回退方法保护下游调用。

断路器与舱壁隔离 是 CoddyKit 上的免费 Spring Boot 4 Complete Guide 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 Spring Boot 4 Complete Guide 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 Spring Boot 4 Complete Guide 课程共包含 4 节课。

本课时的部分内容尚未翻译,以英文显示。

Why Resilience Matters

In a distributed system, a single slow or failing downstream service can cascade into a total outage. If your service keeps calling a dead dependency, threads pile up waiting on timeouts until the whole application becomes unresponsive.

Resilience engineering is about containing failure. Two foundational patterns:

  • Circuit Breaker — stop calling a failing dependency for a while, fail fast instead of waiting.
  • Bulkhead — isolate resources so one saturated dependency cannot exhaust the threads or connections needed by the rest.

In Spring Boot 4 we implement these with Resilience4j, a lightweight, functional fault-tolerance library that replaced the now end-of-life Hystrix.

Adding Resilience4j

Spring Boot 4 integrates Resilience4j through the resilience4j-spring-boot3 starter (compatible with Boot 3.x/4.x) plus Spring AOP. The annotations are activated by an aspect that wraps your method calls.

You also pull in spring-boot-starter-aop and, for metrics, spring-boot-starter-actuator with Micrometer.

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot3</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

How a Circuit Breaker Works

A circuit breaker is a state machine that monitors the failure rate of calls:

  • CLOSED — calls flow through normally. Failures are recorded in a sliding window.
  • OPEN — once the failure rate crosses a threshold, the breaker trips. Further calls fail immediately (no downstream call) for a wait duration.
  • HALF_OPEN — after the wait, a limited number of trial calls are permitted. If they succeed, it returns to CLOSED; if they fail, it returns to OPEN.

The key benefit: when a dependency is down, you fail fast instead of blocking threads on doomed calls, and you give the dependency room to recover.

Annotating a Protected Call

The @CircuitBreaker annotation wraps a method. The name ties it to a configuration instance, and fallbackMethod names a method to invoke when the call fails or the breaker is open.

The fallback must have the same signature plus a trailing Throwable (or a more specific exception) parameter.

@Service
public class PaymentClient {

    private final RestClient restClient;

    public PaymentClient(RestClient restClient) {
        this.restClient = restClient;
    }

    @CircuitBreaker(name = "paymentService", fallbackMethod = "fallbackCharge")
    public ChargeResult charge(ChargeRequest request) {
        return restClient.post()
                .uri("/charges")
                .body(request)
                .retrieve()
                .body(ChargeResult.class);
    }

    private ChargeResult fallbackCharge(ChargeRequest request, Throwable t) {
        return ChargeResult.deferred(request.id(), "payment temporarily unavailable");
    }
}

Configuring the Breaker

Circuit breaker behavior is tuned in application.yml. You define default settings and per-instances overrides keyed by the name you used in the annotation.

  • sliding-window-type — COUNT_BASED or TIME_BASED.
  • failure-rate-threshold — percent of failures to open the breaker.
  • wait-duration-in-open-state — how long to stay OPEN before HALF_OPEN.
  • permitted-number-of-calls-in-half-open-state — trial calls allowed.
  • slow-call-duration-threshold / slow-call-rate-threshold — treat slow calls as failures.
resilience4j:
  circuitbreaker:
    configs:
      default:
        sliding-window-type: COUNT_BASED
        sliding-window-size: 20
        failure-rate-threshold: 50
        slow-call-duration-threshold: 2s
        slow-call-rate-threshold: 80
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 5
        automatic-transition-from-open-to-half-open-enabled: true
    instances:
      paymentService:
        base-config: default
        failure-rate-threshold: 40

Modeling the State Machine in Plain Java

To internalize the logic, here is a stripped-down, framework-free model of the CLOSED to OPEN transition using a count-based window. This is conceptually what Resilience4j does for you behind the annotation.

Run it to see the breaker trip once the failure rate crosses the threshold.

public class MiniBreaker {
    enum State { CLOSED, OPEN }
    static State state = State.CLOSED;
    static int window = 10, threshold = 50;
    static boolean[] results = new boolean[window];
    static int idx = 0, count = 0;

    static void record(boolean failure) {
        results[idx] = failure;
        idx = (idx + 1) % window;
        if (count < window) count++;
        int fails = 0;
        for (int i = 0; i < count; i++) if (results[i]) fails++;
        int rate = count == 0 ? 0 : (fails * 100 / count);
        if (count == window && rate >= threshold) state = State.OPEN;
    }

    public static void main(String[] args) {
        boolean[] calls = {false,true,false,true,true,false,true,true,false,true};
        for (boolean fail : calls) {
            record(fail);
            System.out.println((fail ? "FAIL" : "OK  ") + " -> state=" + state);
        }
    }
}

Counting Records vs Ignoring Exceptions

Not every exception should trip the breaker. A 400 Bad Request means your input was wrong, not that the dependency is unhealthy — counting it as a failure would open the breaker for valid traffic.

Use record-exceptions to list throwables that count as failures, and ignore-exceptions for those that should pass through without affecting breaker state.

resilience4j:
  circuitbreaker:
    instances:
      paymentService:
        base-config: default
        record-exceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
          - org.springframework.web.client.HttpServerErrorException
        ignore-exceptions:
          - com.example.payments.InvalidCardException
          - org.springframework.web.client.HttpClientErrorException$BadRequest

Bulkhead Isolation

The bulkhead pattern (named after a ship's watertight compartments) limits how many concurrent calls a dependency can consume, so one slow service cannot drain the entire thread pool.

Resilience4j offers two flavors:

  • SemaphoreBulkhead — caps concurrent calls on the caller's thread. Lightweight, no extra threads.
  • ThreadPoolBulkhead — runs calls on a dedicated, bounded thread pool with a queue. Provides true isolation and only works with asynchronous returns (CompletableFuture).

If the bulkhead is full, the call is rejected with BulkheadFullException, which your fallback can handle.

Applying a Bulkhead

The @Bulkhead annotation limits concurrency. With type = THREADPOOL the method must return a CompletableFuture and runs on the bulkhead's own pool. You can stack it with @CircuitBreaker — the annotations compose.

@Service
public class InventoryClient {

    private final RestClient restClient;

    public InventoryClient(RestClient restClient) {
        this.restClient = restClient;
    }

    @CircuitBreaker(name = "inventory", fallbackMethod = "fallbackStock")
    @Bulkhead(name = "inventory", type = Bulkhead.Type.THREADPOOL)
    public CompletableFuture<StockLevel> getStock(String sku) {
        return CompletableFuture.completedFuture(
            restClient.get().uri("/stock/{sku}", sku)
                      .retrieve().body(StockLevel.class));
    }

    private CompletableFuture<StockLevel> fallbackStock(String sku, Throwable t) {
        return CompletableFuture.completedFuture(StockLevel.unknown(sku));
    }
}

Configuring Bulkheads

Semaphore and thread-pool bulkheads have separate config sections.

  • bulkhead (semaphore): max-concurrent-calls and max-wait-duration (how long a call waits for a permit before being rejected).
  • thread-pool-bulkhead: max-thread-pool-size, core-thread-pool-size, and queue-capacity.

Right-size these to the dependency's real capacity: a bulkhead larger than the downstream can handle defeats the purpose.

resilience4j:
  bulkhead:
    instances:
      paymentService:
        max-concurrent-calls: 25
        max-wait-duration: 50ms
  thread-pool-bulkhead:
    instances:
      inventory:
        core-thread-pool-size: 8
        max-thread-pool-size: 16
        queue-capacity: 20

Ordering, Fallbacks, and Observability

When multiple Resilience4j annotations decorate one method, they apply in a fixed aspect order (highest precedence first): Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry. So Retry wraps the circuit breaker — a retried call that still fails counts toward the breaker.

Fallback design rules:

  • Keep fallbacks fast and side-effect-free; never call the failing dependency again.
  • Return degraded-but-valid data (cached value, default, queued-for-later).
  • Inspect the Throwable to distinguish CallNotPermittedException (breaker open) from BulkheadFullException (overloaded).

Actuator exposes state and metrics at /actuator/circuitbreakers and via Micrometer (resilience4j_circuitbreaker_state), so you can alert on OPEN breakers.

private ChargeResult fallbackCharge(ChargeRequest request, CallNotPermittedException ex) {
    return ChargeResult.deferred(request.id(), "breaker open");
}

private ChargeResult fallbackCharge(ChargeRequest request, BulkheadFullException ex) {
    return ChargeResult.rejected(request.id(), "system busy");
}

private ChargeResult fallbackCharge(ChargeRequest request, Throwable t) {
    return ChargeResult.deferred(request.id(), "payment unavailable");
}

Quick Check

Test your understanding of bulkhead isolation.

Recap

You learned how to protect downstream calls in Spring Boot 4 with Resilience4j:

  • Circuit breakers fail fast via the CLOSED → OPEN → HALF_OPEN state machine, tuned with sliding-window size, failure-rate and slow-call thresholds, and wait duration.
  • Use record-exceptions / ignore-exceptions so client errors (4xx) don't trip the breaker.
  • Bulkheads cap concurrency — SemaphoreBulkhead on the caller thread, ThreadPoolBulkhead for true isolation with CompletableFuture.
  • Fallback methods share the signature plus a trailing Throwable; return degraded-but-valid data and never re-call the failing dependency.
  • Annotation order is Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry; observe state through Actuator and Micrometer.

Together these patterns keep one failing dependency from taking down your whole service.

常见问题解答

「断路器与舱壁隔离」课时是免费的吗?

是的 — 「断路器与舱壁隔离」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 Spring Boot 4 Complete Guide 课程的其余内容,请升级到 CoddyKit PRO。 Spring Boot 4 Complete Guide 课程共包含 4 节课。

「断路器与舱壁隔离」这节课中我会学到什么?

使用 Resilience4j 断路器、舱壁和回退方法保护下游调用。 你通过在浏览器中直接运行的动手代码来练习 Spring Boot 4 Complete Guide,全天候 AI 导师会在你学习这节课的过程中回答你的问题。

学习 Spring Boot 4 Complete Guide 需要有经验吗?

无需任何先前经验。CoddyKit 上的 Spring Boot 4 Complete Guide 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。

「断路器与舱壁隔离」课时需要多长时间?

大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。

我能在这节 Spring Boot 4 Complete Guide 课中编写并运行代码吗?

能。每节 Spring Boot 4 Complete Guide 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。

此课程中的所有课时

  1. 上下文传播与跨度插桩
  2. 断路器与舱壁隔离
  3. 速率限制、重试与时间限制器
  4. 关联日志、指标与跟踪
← 返回 Spring Boot 4 Complete Guide