Haladó Olvasási idő: ~8 perc

Resilience

circuit breaker, Resilience4j, retry, rate limiting, bulkhead

Resilience

A resilience lényege nem az, hogy minden hiba eltűnjön, hanem hogy a hibák ne tudják magukkal rántani az egész rendszert.

1. Definíció / Definition

Mi ez? / What is it?

A resilience a distributed rendszerek önvédelme részleges hibák, lassulások és kapacitásproblémák ellen. Spring világban ezt ma leginkább a Resilience4j és a Spring Cloud Circuit Breaker absztrakció segítségével valósítjuk meg.

Miért létezik? / Why does it exist?

Mert a hálózat nem megbízható, a downstream service néha lassú, néha hibás, néha részben elérhető. Ha minden hívást vakon újra meg újra megpróbálsz, threadeket fogsz elfogyasztani, queue-kat töltesz fel és végül a még egészséges részeket is lerántod. A resilience patternök célja a hibák körbezárása és a rendszer túlélőképességének növelése.

Hol helyezkedik el? / Where does it fit?

A resilience a service-to-service kommunikációs rétegben, az integration boundary körül helyezkedik el. Nem domain logika, nem is infrastruktúra monitoring helyettesítője, hanem védőréteg a remote callok körül.

2. Alapfogalmak / Core Concepts

2.1 Resilience4j mint Hystrix utód

A Hystrix ma már legacy irány. A Resilience4j lightweight, moduláris és jobban illeszkedik a modern Java/Spring stackhez. A legfontosabb moduljai:

  • CircuitBreaker
  • Retry
  • RateLimiter
  • Bulkhead
  • TimeLimiter

A Spring Cloud Circuit Breaker egy absztrakció, amely fölé tud ülni különböző implementációknak, de Spring ökoszisztémában a Resilience4j az alapértelmezett gyakorlati választás.

2.2 Circuit Breaker állapotok

A circuit breaker figyeli a hívások kimenetelét, és állapotot vált.

Tipikus állapotváltások:

  1. CLOSEDOPEN, ha a hibák vagy lassú hívások száma átlépi a küszöböt.
  2. OPENHALF_OPEN, amikor letelik a várakozási idő.
  3. HALF_OPENCLOSED, ha a próbahívások sikeresek.
  4. HALF_OPENOPEN, ha a próbahívások újra hibáznak.
Állapot Jelentés Viselkedés
CLOSED Normál működés Hívások átmennek és metrikázódnak
OPEN Túl sok hiba vagy lassú hívás Hívások azonnal elutasítva
HALF_OPEN Tesztüzem Néhány próbahívás eldönti a további állapotot

2.3 Sliding window konfiguráció

A breaker döntése sliding window alapján történik.

Típus Mit néz? Mikor hasznos?
Count-based Utolsó N hívás Egyenletes forgalomnál
Time-based Utolsó X másodperc Változó forgalomnál

Fontos paraméterek:

  • failure rate threshold
  • slow call rate threshold
  • minimum number of calls
  • wait duration in open state
  • permitted number of calls in half-open state

2.4 Retry

A retry nem ugyanaz, mint a resilience maga. Csak akkor hasznos, ha a hiba várhatóan átmeneti, és a művelet idempotens vagy biztonságosan újrapróbálható. Retry nélkül elveszíthetsz átmeneti sikeres lehetőségeket, túl sok retry-val viszont DDoS-olhatod a saját downstream rendszeredet.

2.5 RateLimiter és TimeLimiter

A RateLimiter korlátozza, adott idő alatt hány hívást engedsz át. Ez lehet külső partner védelme vagy saját erőforráskontroll.

A TimeLimiter azt mondja meg, mennyi ideig vársz egy async eredményre. Ha nincs timeout stratégiád, a rendszered hajlamos lesz szálakat és erőforrásokat bent tartani túl sokáig.

2.6 Bulkhead: semaphore vs thread-pool isolation

A bulkhead célja, hogy egy problémás dependency ne foglalja el az összes erőforrást.

Minta Hogyan izolál? Mikor jó?
Semaphore bulkhead Párhuzamos hívások számát limitálja Egyszerű, alacsony overhead
Thread pool bulkhead Külön végrehajtó poolt használ Erősebb izoláció, több overhead

A választás attól függ, hogy csak concurrency limit kell, vagy külön execution isolation is.

3. Gyakorlati használat / Practical Usage

Egy payment service tipikus példa. Meghív egy külső PSP API-t. Ha a PSP lassul, nem akarod, hogy a teljes checkout thread pool megálljon. Circuit breakerrel megakadályozod a végtelen hibás hívásokat, timeouttal limitálod a várakozást, retry-jal csak a tényleg átmeneti hibákat próbálod újra, bulkheaddel pedig megóvod a többi funkciót attól, hogy a fizetési integráció elszívja az összes erőforrást.

Másik jó példa az inventory vagy pricing service. Ezeket sok helyről hívják, ezért egy lassulásuk láncreakciót indíthat. RateLimiterrel vagy bulkheaddel biztosíthatod, hogy a túlterhelés kontroll alatt maradjon. Ha van értelmes fallback, például cache-elt read-only pricing snapshot, az bizonyos use case-ekben megmentheti a felhasználói élményt.

Nagyon fontos: fallback nem jelent „hazudjunk valamit”. Egy product recommendation fallback lehet üres lista, de egy banki egyenleg fallback nem lehet random utolsó ismert érték üzleti egyeztetés nélkül.

4. Kód példák / Code Examples

4.1 Annotáció alapú Circuit Breaker és fallback

@Service
class PaymentGatewayClient {

    @CircuitBreaker(name = "paymentProvider", fallbackMethod = "fallbackAuthorize")
    @Retry(name = "paymentProvider")
    public PaymentResponse authorize(PaymentRequest request) {
        return callRemoteProvider(request);
    }

    private PaymentResponse callRemoteProvider(PaymentRequest request) {
        throw new IllegalStateException("Provider unavailable");
    }

    public PaymentResponse fallbackAuthorize(PaymentRequest request, Throwable throwable) {
        return new PaymentResponse("PENDING_MANUAL_REVIEW", "fallback due to: " + throwable.getMessage());
    }
}

record PaymentRequest(String orderId, BigDecimal amount) {}
record PaymentResponse(String status, String message) {}

4.2 TimeLimiter async művelettel

@Service
class PricingClient {

    @TimeLimiter(name = "pricingService")
    public CompletableFuture<PricingResponse> getPricing(String sku) {
        return CompletableFuture.supplyAsync(() -> fetchPricing(sku));
    }

    private PricingResponse fetchPricing(String sku) {
        return new PricingResponse(sku, new BigDecimal("19.99"));
    }
}

record PricingResponse(String sku, BigDecimal price) {}

4.3 Bulkhead annotáció

@Service
class RecommendationClient {

    @Bulkhead(name = "recommendationService", type = Bulkhead.Type.SEMAPHORE, fallbackMethod = "fallback")
    public List<String> getRecommendations(String customerId) {
        return List.of("sku-1", "sku-2");
    }

    public List<String> fallback(String customerId, Throwable throwable) {
        return List.of();
    }
}

4.4 YAML konfiguráció sliding windowval

resilience4j:
  circuitbreaker:
    instances:
      paymentProvider:
        slidingWindowType: COUNT_BASED
        slidingWindowSize: 20
        minimumNumberOfCalls: 10
        failureRateThreshold: 50
        slowCallRateThreshold: 60
        slowCallDurationThreshold: 2s
        waitDurationInOpenState: 30s
        permittedNumberOfCallsInHalfOpenState: 5
  retry:
    instances:
      paymentProvider:
        maxRetryAttempts: 3
        waitDuration: 200ms
  ratelimiter:
    instances:
      pricingService:
        limitForPeriod: 50
        limitRefreshPeriod: 1s
        timeoutDuration: 0

5. Trade-offok / Trade-offs

Előnyök

  • megakadályozza, hogy egy hibás downstream elszívja az összes erőforrást;
  • gyorsabban és kontrolláltabban hibázik;
  • jobban véd a láncreakciós incidensek ellen;
  • mérhető és hangolható védelmi policy-t ad.

Hátrányok

  • rosszul hangolva vagy túl agresszíven szükségtelen hibákat okozhat;
  • fallback könnyen adat- vagy üzleti következetlenséget vihet be;
  • retry + timeout + breaker kombinációja nehezen átlátható lehet;
  • nem helyettesíti a jó API dizájnt, capacity planninget és observabilityt.

Mikor használd? Ha remote dependencyid vannak, különösen külső API-k, instabil szolgáltatások vagy sokat hívott belső service-ek esetén.

Mikor ne? Ha egyszerű, lokális műveletet próbálsz vele „felokosítani”, vagy ha a csapat nem érti a fallback üzleti következményeit.

6. Gyakori hibák / Common Mistakes

6.1 Retry nem idempotens műveleten

Ha egy POST művelet pénzt von le vagy rendelést hoz létre, a vak retry duplikált üzleti eseményt okozhat. Idempotency key vagy üzleti garancia nélkül veszélyes.

6.2 Mindenre fallback

Nem minden hiba kezelhető értelmes fallbackkel. Sokszor a korrekt viselkedés a gyors, egyértelmű hiba és a feljebb lévő réteg megfelelő kommunikációja.

6.3 Rossz sliding window méret

Túl kicsi window esetén a breaker idegesen kapcsolgat, túl nagy esetén túl lassan reagál. A traffic karakterisztikához kell illeszteni.

6.4 Timeout és circuit breaker összhang hiánya

Ha a HTTP kliens timeoutja 10 másodperc, de a TimeLimiter 2 másodpercre van állítva, vagy fordítva, könnyen zavaros hibaképet kapsz. A teljes stack időzítéseit együtt kell kezelni.

6.5 Bulkhead nélkül csak retry-zni

Ez klasszikus önsorsrontás. Ha a downstream lassú, a retry több terhelést ad rá, miközben saját threadjeidet is foglalod. Isolation nélkül a retry önmagában kevés.

7. Senior szintű meglátások / Senior-level Insights

Resilience policy-t nem annotációk alapján kell tervezni, hanem üzleti kritikalitás alapján. Más védelmi stratégia kell recommendationre, más paymentre, más audit log írásra. A „mindenre ugyanaz a retry és circuit breaker config” tipikus anti-pattern.

A fallback minősége fontosabb, mint a fallback létezése. Olyan fallback jó, ami domain-szinten is védhető. Üres ajánlólista oké lehet, becsült bankszámlaegyenleg sokkal veszélyesebb. Senior szinten itt mindig az üzleti következményt nézed, nem csak a technikai eleganciát.

A metrikák nélkülözhetetlenek. Circuit breaker open rate, retry count, rate limiter rejection, bulkhead saturation, timeout ratio nélkül nem tuningolsz, hanem vakon lövöldözöl. A resilience nem egyszeri kódminta, hanem folyamatos operációs finomhangolás.

És végül: a resilience patternök nem gyógyítják meg a rossz architektúrát. Ha túl chatty a service-hálózat, túl sok a synchron hívás, vagy nincs világos SLA/SLO modell, akkor a breaker csak később és kontrolláltabban mutatja meg ugyanazt a strukturális problémát.

8. Szószedet / Glossary

  • Resilience4j: Java library resilience patternökhöz.
  • Circuit Breaker: hívásokat megszakító védelmi mechanizmus túl sok hiba esetén.
  • Retry: átmeneti hiba utáni újrapróbálás.
  • RateLimiter: áteresztési sebességet korlátozó komponens.
  • Bulkhead: erőforrásizolációs minta.
  • TimeLimiter: async műveletek timeout kontrollja.
  • HALF_OPEN: tesztállapot a circuit breaker újranyitása előtt.
  • Sliding window: a breaker döntési horizontja count vagy time alapon.

9. Gyorsreferencia / Cheatsheet

Minta Mire jó? Fő konfiguráció Tipikus hiba
CircuitBreaker Hibás downstream leválasztása failure threshold, open wait Túl agresszív nyitás
Retry Átmeneti hiba kezelése attempts, wait Nem idempotens hívás retry-ja
RateLimiter Forgalom szabályozása limit per period Rossz kapacitásbecslés
Bulkhead Erőforrás izoláció concurrency/pool méret Isolation hiánya
TimeLimiter Várakozási idő korlátozása timeout Kliens timeouttal nincs összehangolva
Fallback Degradált válasz fallback method Üzletileg hamis válasz

🎮 Játékok

8 kérdés