Resilience — Cheat Sheet
Microservices · 5 topics. Download the PDF or the Instagram carousel and share it.
Circuit Breaker Pattern
When failure rate exceeds a threshold the circuit "opens", short-circuiting calls and returning a fallback; after a wait it goes half-open to probe recovery.
- ✓Circuit breaker has three states: CLOSED (normal), OPEN (fast-fail), HALF-OPEN (recovery probe).
- ✓The circuit opens when the failure rate in a sliding window (count or time-based) exceeds the configured threshold.
- ✓Always provide a fallback method — a cached response, a default value, or a graceful error — so the caller never waits for a timeout.
- ✓waitDurationInOpenState prevents hammering a recovering service; set it to at least the expected recovery time of the downstream service.
- ✓Circuit breakers prevent cascading failures: an open circuit stops thread exhaustion in the upstream service.
- ✓Combine with Retry (for transient failures) and Bulkhead (for resource isolation) to build a comprehensive resilience strategy.
// Resilience4j Circuit Breaker — configuration
resilience4j:
circuitbreaker:
instances:
paymentService:
slidingWindowType: COUNT_BASED
slidingWindowSize: 10 # evaluate last 10 calls
failureRateThreshold: 50 # open if >=50% fail
waitDurationInOpenState: 30s # stay open for 30 seconds
permittedNumberOfCallsInHalfOpenState: 3
recordExceptions:
- java.io.IOException
- java.util.concurrent.TimeoutExceptionResilience4j Circuit Breaker
Resilience4j is a lightweight fault-tolerance library providing CircuitBreaker, RateLimiter, Retry, Bulkhead, and TimeLimiter decorators for functional or reactive code.
- ✓CircuitBreaker has three states: CLOSED (normal), OPEN (blocked), HALF_OPEN (probing)
- ✓failureRateThreshold triggers OPEN; waitDurationInOpenState controls recovery pause
- ✓Fallback method must have same signature as the decorated method plus a Throwable parameter
- ✓Decorator order: Retry → CircuitBreaker → RateLimiter → TimeLimiter → Bulkhead
- ✓@Retry uses exponential backoff + jitter to avoid thundering herd on recovery
- ✓Expose circuit breaker state via Spring Actuator /actuator/circuitbreakers endpoint
# application.yml
resilience4j:
circuitbreaker:
instances:
payment-service:
slidingWindowType: COUNT_BASED # or TIME_BASED
slidingWindowSize: 10 # last 10 calls
failureRateThreshold: 50 # open if ≥50% fail
slowCallRateThreshold: 80 # open if ≥80% are slow
slowCallDurationThreshold: 2000ms
waitDurationInOpenState: 10s # stay open 10s before HALF_OPEN
permittedNumberOfCallsInHalfOpenState: 3
minimumNumberOfCalls: 5 # need at least 5 calls before evaluating
# Spring Boot Actuator exposes circuit breaker state:
# GET /actuator/circuitbreakers
# GET /actuator/circuitbreakereventsRetry Pattern
Transient failures are retried with exponential back-off and jitter; combine with idempotency on the server side to avoid duplicate effects.
- ✓Only retry idempotent operations — retrying POST /payments without an idempotency key causes double-charging.
- ✓Exponential backoff + jitter prevents thundering-herd: all clients retrying at exactly the same interval.
- ✓Never retry 4xx client errors — they indicate a problem with your request, not a transient server issue.
- ✓Combine @Retry with @CircuitBreaker: the circuit opens after exhausted retries, preventing retry storms.
- ✓Set max-attempts conservatively (2–3) — aggressive retries amplify load on already-degraded services.
- ✓Monitor retry rate as a metric — a sudden spike indicates upstream instability, not normal operation.
# application.properties
resilience4j.retry.instances.inventoryService.max-attempts=3
resilience4j.retry.instances.inventoryService.wait-duration=500ms
resilience4j.retry.instances.inventoryService.enable-exponential-backoff=true
resilience4j.retry.instances.inventoryService.exponential-backoff-multiplier=2
resilience4j.retry.instances.inventoryService.randomized-wait-factor=0.5
# wait times: ~500ms, ~1s, ~2s (randomised by ±50%)
resilience4j.retry.instances.inventoryService.retry-exceptions= java.net.SocketTimeoutException, org.springframework.web.client.HttpServerErrorException$ServiceUnavailable
@Service
public class OrderService {
@Retry(name = "inventoryService", fallbackMethod = "inventoryFallback")
@CircuitBreaker(name = "inventoryService") // combine with CB
public InventoryResponse checkInventory(String sku) {
return inventoryClient.checkStock(sku); // retried on transient errors
}
// Fallback only called after all retries are exhausted
private InventoryResponse inventoryFallback(String sku, Exception ex) {
log.warn("Inventory check failed after retries for {}: {}", sku, ex.getMessage());
return InventoryResponse.unavailable(sku);
}
}Bulkhead Pattern
Isolates failure by limiting concurrent calls to a downstream dependency (semaphore or thread-pool bulkhead), preventing one slow service from exhausting shared resources.
- ✓Bulkhead prevents thread pool exhaustion: one slow service cannot starve all other endpoints.
- ✓Semaphore bulkhead is lightweight (counts permits); thread-pool bulkhead provides true pool isolation.
- ✓Thread-pool bulkhead requires CompletableFuture return type — it offloads the call to its own pool.
- ✓Combine: Bulkhead (concurrency limit) + TimeLimiter (timeout) + CircuitBreaker (failure threshold).
- ✓Set max-wait-duration=0ms to fail fast when the bulkhead is full — blocking is worse than fast failure.
- ✓Size bulkheads based on expected concurrency and downstream SLAs — too small = false positives; too large = no protection.
# application.properties — semaphore bulkhead
resilience4j.bulkhead.instances.paymentService.max-concurrent-calls=10
resilience4j.bulkhead.instances.paymentService.max-wait-duration=0ms
# max-wait-duration=0: reject immediately when full (fail-fast)
@Service
public class CheckoutService {
@Bulkhead(name = "paymentService",
type = Bulkhead.Type.SEMAPHORE,
fallbackMethod = "paymentFallback")
public PaymentResult processPayment(PaymentRequest req) {
return paymentClient.charge(req); // max 10 concurrent calls
}
// Called when bulkhead is full (BulkheadFullException)
private PaymentResult paymentFallback(PaymentRequest req,
BulkheadFullException ex) {
log.warn("Payment bulkhead full — circuit protecting main thread pool");
return PaymentResult.rejected("Payment service temporarily unavailable");
}
}
// Monitor: Resilience4j metrics exposed via Micrometer
// resilience4j.bulkhead.available.concurrent.calls{name="paymentService"}
// Alert when available calls drops to 0 consistently → bulkhead too smallTimeouts & Deadline Propagation
Always set read/connect timeouts on HTTP clients; propagate deadlines through call chains so that downstream work is abandoned when the upstream caller has given up.
- ✓Always set both connect timeout (TCP establishment) and read timeout (response wait) on every HTTP client
- ✓Base read timeouts on actual p99 latency + safety margin — not arbitrary large values like 30 s
- ✓Deadlines are absolute timestamps that shrink across call chains; timeouts reset at each hop
- ✓gRPC propagates deadlines automatically; HTTP services need explicit header forwarding (e.g. X-Request-Deadline)
- ✓Combine timeouts with circuit breakers — timeouts prevent individual call hangs, circuit breakers prevent repeated waits
- ✓Timeout budget each leg proportionally: in A→B→C with a 5 s SLA, each leg timeout must fit within 5 s total
// Spring Boot 3 — RestClient with Apache HttpClient 5 timeouts
@Bean
public RestClient inventoryClient() {
CloseableHttpClient httpClient = HttpClients.custom()
.setConnectionRequestTimeout(Timeout.ofSeconds(2)) // connect timeout
.setResponseTimeout(Timeout.ofSeconds(5)) // read timeout
.build();
return RestClient.builder()
.baseUrl("http://inventory-service")
.requestFactory(new HttpComponentsClientHttpRequestFactory(httpClient))
.build();
}
// Feign client — application.yml
// spring:
// cloud:
// openfeign:
// client:
// config:
// inventory-service:
// connect-timeout: 2000 # ms
// read-timeout: 5000 # ms
// WebClient with Netty reactor timeout
@Bean
public WebClient webClient() {
HttpClient nettyClient = HttpClient.create()
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 2_000)
.responseTimeout(Duration.ofSeconds(5));
return WebClient.builder()
.clientConnector(new ReactorClientHttpConnector(nettyClient))
.build();
}