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:
CLOSED→OPEN, ha a hibák vagy lassú hívások száma átlépi a küszöböt.OPEN→HALF_OPEN, amikor letelik a várakozási idő.HALF_OPEN→CLOSED, ha a próbahívások sikeresek.HALF_OPEN→OPEN, 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