Komplet guide til Spring Boot 4 · Lektion

Circuit breakers og bulkhead-isolering

Beskyt kald til downstream-tjenester med Resilience4j circuit breakers, bulkheads og fallback-metoder.

Lektion 2 af 413 trin

Circuit breakers og bulkhead-isolering er en gratis Komplet guide til Spring Boot 4-lektion på CoddyKit. Dette er lektion 2 af 4. Du kan læse alle 3 lektioner i dette læringsspor gratis i deres fulde længde — derefter låser CoddyKit PRO alle lektioner op samt praktiske øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Den er en del af læringsforløbet i Komplet guide til Spring Boot 4, og dine fremskridt synkroniseres på tværs af nettet og CoddyKit-appen. Komplet guide til Spring Boot 4-kurset indeholder 4 lektioner i alt.

Hvorfor robusthed er vigtig

I et distribueret system kan en enkelt langsom eller fejlende downstream-tjeneste udløse et kaskaderende totalt nedbrud. Hvis din tjeneste bliver ved med at kalde en utilgængelig afhængighed, hober trådene sig op, mens de venter på timeouts, indtil hele applikationen bliver ufølsom.

Robusthedsteknik handler om at begrænse fejl. To grundlæggende mønstre er:

  • Effektafbryder — stop med at kalde en fejlende afhængighed i en periode, og giv hurtigt op i stedet for at vente.
  • Skotteseparation — isolér ressourcer, så én overbelastet afhængighed ikke kan opbruge de tråde eller forbindelser, som resten har brug for.

I Spring Boot 4 implementerer vi dette med Resilience4j, et letvægtsbibliotek til funktionel fejltolerance, som har erstattet det nu udfasede Hystrix.

Tilføjelse af Resilience4j

Spring Boot 4 integrerer Resilience4j gennem starteren resilience4j-spring-boot3 (kompatibel med Boot 3.x/4.x) samt Spring AOP. Annotationerne aktiveres af et aspekt, der pakker dine metodekald ind.

Du skal også tilføje spring-boot-starter-aop og, til metrikker, spring-boot-starter-actuator med 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>

Sådan fungerer en effektafbryder

En effektafbryder er en tilstandsmaskine, der overvåger fejlraten for kald:

  • CLOSED — kald passerer normalt igennem. Fejl registreres i et glidende vindue.
  • OPEN — når fejlraten overskrider en tærskel, udløses afbryderen. Efterfølgende kald fejler med det samme (uden et downstream-kald) i en venteperiode.
  • HALF_OPEN — efter ventetiden tillades et begrænset antal prøvekald. Hvis de lykkes, vender den tilbage til CLOSED; hvis de fejler, vender den tilbage til OPEN.

Den vigtigste fordel er, at du kan give hurtigt op, når en afhængighed er nede, i stedet for at blokere tråde på dødsdømte kald, og at afhængigheden får mulighed for at komme sig.

Annotering af et beskyttet kald

Annotationen @CircuitBreaker pakker en metode ind. name knytter den til en konfigurationsinstans, og fallbackMethod angiver navnet på den metode, der skal kaldes, når kaldet fejler, eller afbryderen er åben.

Fallback-metoden skal have samme signatur plus en afsluttende parameter af typen Throwable (eller en mere specifik undtagelse).

@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");
    }
}

Konfiguration af afbryderen

Effektafbryderens adfærd justeres i application.yml. Du definerer default-indstillinger og tilsidesættelser pr. instances, identificeret med det name, du brugte i annotationen.

  • sliding-window-type — COUNT_BASED eller TIME_BASED.
  • failure-rate-threshold — procentdelen af fejl, der åbner afbryderen.
  • wait-duration-in-open-state — hvor længe den skal forblive OPEN, før den går til HALF_OPEN.
  • permitted-number-of-calls-in-half-open-state — antal tilladte prøvekald.
  • slow-call-duration-threshold / slow-call-rate-threshold — behandl langsomme kald som fejl.
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

Modellering af tilstandsmaskinen i ren Java

For at få styr på logikken får du her en forenklet, framework-uafhængig model af overgangen fra CLOSED til OPEN ved hjælp af et tællebaseret vindue. Det svarer begrebsmæssigt til det, Resilience4j gør for dig bag annotationen.

Kør den for at se afbryderen udløse, når fejlraten overskrider tærsklen.

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);
        }
    }
}

Optælling af poster kontra ignorering af undtagelser

Det er ikke enhver undtagelse, der bør udløse afbryderen. En 400 Bad Request betyder, at dit input var forkert, ikke at afhængigheden er usund — hvis du tæller den som en fejl, ville afbryderen åbne for gyldig trafik.

Brug record-exceptions til at angive de throwables, der tæller som fejl, og ignore-exceptions til dem, der skal passere igennem uden at påvirke afbryderens tilstand.

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

Isolation med skotteseparation

Skotmønsteret (opkaldt efter et skibs vandtætte skotter) begrænser, hvor mange samtidige kald en afhængighed kan bruge, så én langsom tjeneste ikke kan tømme hele trådpuljen.

Resilience4j tilbyder to varianter:

  • SemaphoreBulkhead — begrænser samtidige kald på den kaldende tråd. Letvægtsløsning uden ekstra tråde.
  • ThreadPoolBulkhead — kører kald i en dedikeret, begrænset trådpulje med en kø. Giver reel isolation og fungerer kun med asynkrone returværdier (CompletableFuture).

Hvis skottet er fyldt, afvises kaldet med BulkheadFullException, som din reservefunktion kan håndtere.

Anvendelse af et skot

Annoteringen @Bulkhead begrænser samtidigheden. Med type = THREADPOOL skal metoden returnere en CompletableFuture og køre i skottets egen pulje. Du kan kombinere den med @CircuitBreaker — annoteringerne sammensættes.

@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));
    }
}

Konfiguration af skotter

Skotter baseret på semaforer og trådpuljer har separate konfigurationssektioner.

  • bulkhead (semafor): max-concurrent-calls og max-wait-duration (hvor længe et kald venter på en tilladelse, før det afvises).
  • thread-pool-bulkhead: max-thread-pool-size, core-thread-pool-size og queue-capacity.

Tilpas disse værdier til afhængighedens reelle kapacitet: Et skot, der er større end det, den underliggende tjeneste kan håndtere, modarbejder formålet.

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

Rækkefølge, reservefunktioner og overvågning

Når flere Resilience4j-annoteringer udsmykker den samme metode, anvendes de i en fast aspekt-rækkefølge (højeste prioritet først): Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry. Derfor omslutter Retry kredsløbsafbryderen — et gentaget kald, der stadig mislykkes, tæller med for afbryderen.

Regler for design af reservefunktioner:

  • Hold reservefunktioner hurtige og uden bivirkninger; kald aldrig den fejlende afhængighed igen.
  • Returnér forringede, men gyldige data (cachet værdi, standardværdi eller værdi lagt i kø til senere).
  • Undersøg Throwable for at skelne mellem CallNotPermittedException (afbryderen er åben) og BulkheadFullException (overbelastet).

Actuator viser tilstand og målepunkter på /actuator/circuitbreakers og via Micrometer (resilience4j_circuitbreaker_state), så du kan oprette alarmer for åbne afbrydere.

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");
}

Hurtigt tjek

Test din forståelse af isolation med skotter.

Opsummering

Du har lært, hvordan du beskytter kald til underliggende tjenester i Spring Boot 4 med Resilience4j:

  • Kredsløbsafbrydere fejler hurtigt via tilstandsmaskinen CLOSED → OPEN → HALF_OPEN, som justeres med størrelsen på skydevinduet, tærskler for fejlfrekvens og langsomme kald samt ventetid.
  • Brug record-exceptions / ignore-exceptions, så klientfejl (4xx) ikke udløser afbryderen.
  • Skotter begrænser samtidighed — SemaphoreBulkhead på den kaldende tråd og ThreadPoolBulkhead for reel isolation med CompletableFuture.
  • Reservefunktioner deler signaturen plus en afsluttende Throwable; returnér forringede, men gyldige data, og kald aldrig den fejlende afhængighed igen.
  • Annoteringernes rækkefølge er Bulkhead → TimeLimiter → RateLimiter → CircuitBreaker → Retry; overvåg tilstanden via Actuator og Micrometer.

Tilsammen forhindrer disse mønstre, at én fejlende afhængighed får hele din tjeneste til at gå ned.

Gratis at komme i gang

Lær Java med en AI-underviser — gratis

Skriv og kør rigtig kode i din browser, få øjeblikkelig hjælp fra en AI-underviser døgnet rundt, og fortsæt, hvor du slap, på web eller i appen.

Kurser
21
Lektioner
84

Ofte stillede spørgsmål

Er lektionen “Circuit breakers og bulkhead-isolering” gratis?

Ja — alle 3 lektioner i læringssporet Komplet guide til Spring Boot 4, inklusive “Circuit breakers og bulkhead-isolering”, kan læses gratis i deres fulde længde her på webstedet. Derefter låser CoddyKit PRO alle lektioner op samt interaktive øvelser med en indbygget kodeeditor og en AI-underviser døgnet rundt. Komplet guide til Spring Boot 4-kurset indeholder 4 lektioner i alt.

Hvad lærer jeg i “Circuit breakers og bulkhead-isolering”?

Beskyt kald til downstream-tjenester med Resilience4j circuit breakers, bulkheads og fallback-metoder. Du øver dig i Komplet guide til Spring Boot 4 med praktisk kode, som du kører direkte i browseren, og en AI-vejleder døgnet rundt besvarer dine spørgsmål, mens du arbejder dig gennem lektionen.

Skal jeg have erfaring for at begynde på Komplet guide til Spring Boot 4?

Der kræves ingen tidligere erfaring. Komplet guide til Spring Boot 4 på CoddyKit er tilrettelagt for både begyndere og øvede, så du kan starte her eller fra begyndelsen og lære i dit eget tempo. Dette er lektion 2 af 4.

Hvor lang tid tager lektionen “Circuit breakers og bulkhead-isolering”?

De fleste CoddyKit-lektioner tager cirka 5–10 minutter. Hver lektion er kort og interaktiv, så du gør løbende fremskridt og kan fortsætte, hvor du slap – på både web og app.

Kan jeg skrive og køre kode i denne Komplet guide til Spring Boot 4-lektion?

Ja. Alle Komplet guide til Spring Boot 4-lektioner har en indbygget kodeeditor, så du kan skrive og køre rigtig kode direkte i din browser og få øjeblikkelig feedback fra AI – uden lokal opsætning.

Alle lektioner i dette kursus

  1. Kontekstvideregivelse og span-instrumentering
  2. Circuit breakers og bulkhead-isolering
  3. Ratebegrænsning, retries og tidsbegrænsere
  4. Sammenkædning af logs, metrikker og traces
← Tilbage til Komplet guide til Spring Boot 4