0Pricing
Spring Boot 4 Complete Guide · บทเรียน

ตัวตัดวงจรและการแยกส่วนแบบกำแพงกั้น

ปกป้องการเรียกบริการปลายทางด้วยตัวตัดวงจร กำแพงกั้น และเมธอดสำรองของ Resilience4j

ตัวตัดวงจรและการแยกส่วนแบบกำแพงกั้น เป็นบทเรียน Spring Boot 4 Complete Guide ฟรีบน CoddyKit นี่คือบทเรียนที่ 2 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน 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 ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส Spring Boot 4 Complete Guide ให้อัปเกรดเป็น CoddyKit PRO คอร์ส Spring Boot 4 Complete Guide มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “ตัวตัดวงจรและการแยกส่วนแบบกำแพงกั้น”

ปกป้องการเรียกบริการปลายทางด้วยตัวตัดวงจร กำแพงกั้น และเมธอดสำรองของ Resilience4j คุณปฏิบัติ Spring Boot 4 Complete Guide ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน Spring Boot 4 Complete Guide หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน Spring Boot 4 Complete Guide บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 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