๐Ÿ›ก️ Resilience Patterns in Distributed Systems

Welcome back to The Code Hut Distributed Systems series! In this post, we’ll explore patterns that help distributed systems remain resilient under failure conditions. ๐Ÿ’ช⚡

๐ŸŒ Why Resilience Patterns Matter

In distributed systems, failures are inevitable. Resilience patterns help:

  • ❌ Prevent cascading failures
  • ⚡ Maintain availability
  • ๐Ÿ˜Š Improve user experience during partial outages

1. ๐Ÿ›‘ Circuit Breaker

A circuit breaker stops requests to a failing service to prevent overwhelming it and allows the system to recover gracefully. Circuit breaker states:

  • Closed: Requests flow normally.
  • Open: Requests fail immediately; fallback triggers.
  • Half-Open: A few trial requests check if the service has recovered.

Example with Resilience4j:


// Using Resilience4j circuit breaker
CircuitBreaker circuitBreaker = CircuitBreaker.ofDefaults("service");
Supplier decorated = CircuitBreaker
    .decorateSupplier(circuitBreaker, () -> remoteService.call());
String result = Try.ofSupplier(decorated)
    .recover(throwable -> "fallback response").get();

2. ๐Ÿ’ก Fallback Strategies

When a service fails, fallback strategies ensure users still get meaningful responses:

  • Return a default value or cached response.
  • Call an alternative service.
  • Return a friendly error message.

3. ๐Ÿ”„ Retry with Exponential Backoff

Retries can help recover from transient errors but must be combined with backoff to avoid overwhelming the service:


// Retry with Resilience4j and exponential backoff
RetryConfig config = RetryConfig.custom()
    .maxAttempts(5)
    .waitDuration(Duration.ofSeconds(2))
    .build();
Retry retry = Retry.of("serviceRetry", config);
Supplier decorated = Retry.decorateSupplier(retry, () -> remoteService.call());
String result = Try.ofSupplier(decorated)
    .recover(throwable -> "fallback response").get();

4. ⏲ Timeouts

Prevent a slow service from blocking requests indefinitely:


// Timeout using Resilience4j
TimeLimiter timeLimiter = TimeLimiter.of(Duration.ofSeconds(3));
Supplier> futureSupplier = 
    () -> CompletableFuture.supplyAsync(() -> remoteService.call());
Supplier> decorated = TimeLimiter.decorateFutureSupplier(timeLimiter, futureSupplier);
String result = Try.ofSupplier(() -> decorated.get().get())
    .recover(throwable -> "timeout fallback").get();

5. ๐Ÿงฑ Bulkhead

Bulkheads isolate resources to prevent one failing part from affecting others. Think of it as compartmentalizing ship sections to avoid sinking completely.


// Using Resilience4j bulkhead
Bulkhead bulkhead = Bulkhead.ofDefaults("serviceBulkhead");
Supplier decorated = Bulkhead
    .decorateSupplier(bulkhead, () -> remoteService.call());
String result = Try.ofSupplier(decorated)
    .recover(throwable -> "fallback response").get();

Next in the Series

In the next post, we’ll explore Service Discovery & Load Balancing ๐Ÿ”⚖️, including registry-based and client-side strategies.

Label for this post: Distributed Systems, Resilience Patterns

Comments

Popular posts from this blog

๐Ÿ› ️ The Code Hut - Index

๐Ÿ›ก️ Thread-Safe Programming in Java: Locks, Atomic Variables & LongAdder