← Kursa Dön
📄 Text · 35 min

Resilience4j & Circuit Breaker

Dağıtık sistemlerde servisler arası çağrılar ağ üzerinden gerçekleşir ve ağ güvenilir değildir. Bir servis yavaşlayabilir, cevap vermeyebilir veya tamamen çökebilir. Bu durumda onu çağıran servisin de yavaşlaması — hatta çökmesi — tehlikesi vardır. Bu "cascading failure" (zincirleme hata) problemidir. Resilience4j, bu tür hatalara karşı uygulamanızı dayanıklı kılan hafif bir Java kütüphanesidir.

Circuit Breaker Pattern

Elektrik devrelerindeki sigorta (circuit breaker) mantığıyla çalışır. Üç durumu vardır:

     Başarılı çağrılar
    ┌──────────────┐
    │              ▼
┌───────┐    ┌──────────┐    ┌───────┐
│ CLOSED│───►│   OPEN   │───►│ HALF- │
│       │    │          │    │ OPEN  │
└───────┘    └──────────┘    └───┬───┘
    ▲                            │
    └────────────────────────────┘
         Başarılı test çağrıları

CLOSED (Kapalı — Normal Durum): İstekler normal şekilde geçer. Hata oranı izlenir.

OPEN (Açık — Devre Kesildi): Hata oranı eşiği aştığında devre açılır. İstekler hedefe gönderilmez, doğrudan fallback yanıtı döner. Bu, çökmüş servisi gereksiz yere bombardımana tutmayı engeller.

HALF-OPEN (Yarı Açık — Test): Belirli bir süre sonra sınırlı sayıda test isteği geçirilir. Başarılıysa CLOSED'a döner, başarısızsa tekrar OPEN olur.

Resilience4j Kurulumu

<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-circuitbreaker-resilience4j</artifactId>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

@CircuitBreaker Annotation

@Service
public class OrderService {

    private final RestTemplate restTemplate;

    public OrderService(RestTemplate restTemplate) {
        this.restTemplate = restTemplate;
    }

    @CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
    public UserDto getUser(Long userId) {
        return restTemplate.getForObject(
            "http://user-service/api/users/{id}",
            UserDto.class, userId);
    }

    // Fallback metodu — aynı parametre + Exception parametresi
    private UserDto getUserFallback(Long userId, Exception ex) {
        return new UserDto(userId, "Unknown User", "N/A");
    }
}

YAML Konfigürasyonu

resilience4j:
  circuitbreaker:
    instances:
      userService:
        register-health-indicator: true
        sliding-window-type: COUNT_BASED
        sliding-window-size: 10          # son 10 çağrıyı izle
        failure-rate-threshold: 50       # %50 hata → OPEN
        wait-duration-in-open-state: 10s # 10 sn bekle → HALF-OPEN
        permitted-number-of-calls-in-half-open-state: 3
        minimum-number-of-calls: 5       # en az 5 çağrıdan sonra değerlendir
        automatic-transition-from-open-to-half-open-enabled: true

      orderService:
        sliding-window-type: TIME_BASED
        sliding-window-size: 60          # son 60 saniyeyi izle
        failure-rate-threshold: 60       # %60 hata → OPEN
        wait-duration-in-open-state: 30s
        slow-call-duration-threshold: 2s # 2s'den yavaş = slow call
        slow-call-rate-threshold: 80     # %80 slow call → OPEN

@Retry — Yeniden Deneme

Geçici hatalarda otomatik yeniden deneme:

@Retry(name = "userService", fallbackMethod = "getUserFallback")
@CircuitBreaker(name = "userService", fallbackMethod = "getUserFallback")
public UserDto getUser(Long userId) {
    return restTemplate.getForObject(
        "http://user-service/api/users/{id}",
        UserDto.class, userId);
}
resilience4j:
  retry:
    instances:
      userService:
        max-attempts: 3
        wait-duration: 1s
        exponential-backoff-multiplier: 2  # 1s, 2s, 4s
        retry-exceptions:
          - java.io.IOException
          - java.util.concurrent.TimeoutException
        ignore-exceptions:
          - com.example.BusinessException

@RateLimiter — İstek Sınırlama

Belirli bir servise yapılan çağrı sayısını sınırlar:

@RateLimiter(name = "externalApi")
public ExternalData callExternalApi(String query) {
    return externalClient.search(query);
}
resilience4j:
  ratelimiter:
    instances:
      externalApi:
        limit-for-period: 10          # periyot başına 10 istek
        limit-refresh-period: 1s       # her 1 saniyede yenile
        timeout-duration: 500ms        # limit aşılınca max bekleme

@Bulkhead — İzolasyon

Bir servisin tüm thread'leri tüketmesini engellemek için eş zamanlı çağrı sayısını sınırlar:

@Bulkhead(name = "userService", type = Bulkhead.Type.SEMAPHORE)
public UserDto getUser(Long userId) {
    return restTemplate.getForObject(
        "http://user-service/api/users/{id}",
        UserDto.class, userId);
}
resilience4j:
  bulkhead:
    instances:
      userService:
        max-concurrent-calls: 25       # max 25 eş zamanlı çağrı
        max-wait-duration: 500ms       # doluysa max bekleme

@TimeLimiter — Zaman Sınırı

Çağrının belirli sürede tamamlanmasını zorlar:

@TimeLimiter(name = "userService")
public CompletableFuture<UserDto> getUser(Long userId) {
    return CompletableFuture.supplyAsync(() ->
        restTemplate.getForObject(
            "http://user-service/api/users/{id}",
            UserDto.class, userId));
}
resilience4j:
  timelimiter:
    instances:
      userService:
        timeout-duration: 3s
        cancel-running-future: true

Annotation Sırası

Birden fazla annotation kullanıldığında çalışma sırası önemlidir (dıştan içe):

Retry → CircuitBreaker → RateLimiter → TimeLimiter → Bulkhead → Method

Resilience4j, mikroservis mimarisinde hata yönetiminin temel yapı taşıdır. Servisler arası iletişimde oluşabilecek hataları zarif bir şekilde ele alarak uygulamanızın dayanıklılığını artırır.