İçeriğe geç

Circuit Breaker

İleri 10 dk Çok sık karşılaşılır

Önce şunu oku: Monolit mi, mikroservis mi?

30 saniyede özet

Circuit breaker, evdeki sigorta gibidir: bozuk servisi değil, çağıranı korur. Yavaşça ölen bir servis senin bütün thread'lerini bekletip seni de düşürmesin diye, bir süre ona hiç gitmeden hemen hata döner.

Evde bir cihaz kısa devre yapınca sigorta atar ve ev yanmaz. Yazılımda sigortası olmayan bir servis ise komşusunun yangınında yanabilir.

Sigorta prizi değil, evi korur.
Adım adım oku
  1. Ödeme servisi çöktü; ona giden her çağrı zaman aşımına kadar uzun uzun bekler.
  2. Sipariş servisinin bütün işçileri ödeme kapısında beklemeye başlar; ödemeyle ilgisi olmayan istekler de cevapsız kalır.
  3. Hata oranı eşiği geçince circuit breaker açılır: ödemeye giden çağrılar beklemeden hemen hata döner.
  4. İşçiler serbest kalır ve öteki istekler yeniden karşılanır. Bir süre sonra yarı açık durumda birkaç deneme çağrısı yapılır.
  1. Bayt: Ödeme servisi çöktü. Birkaç dakika sonra sipariş servisi de cevap vermez oldu!

  2. Sen: Ama sipariş servisinde hiçbir sorun yoktu ki?

  3. Bayt: Yoktu! Bütün thread'leri ödemenin cevabını bekliyordu.

  4. Bayt: Bir servis düştü, iki servis gitti. Evdeki sigorta kutusu bunu nasıl önlüyor?

Ödeme servisi çöktü. Sipariş servisin her isteği ona gidiyor, 30 saniye boyunca cevap bekliyor ve hata dönüyor. Birkaç dakika içinde sipariş servisinin thread havuzu tamamen dolu: artık ödemeyle ilgisi olmayan istekler de cevapsız kalıyor.

Bir servis düştü, iki servis gitti. circuit breakerSağlıksız bir bağımlılığa giden çağrıları bir süre tamamen kesen durum makinesi. Çağrılan servisi değil, çağıranın kaynaklarını korur.Sözlükte gör → tam olarak bunu engeller.

Sigorta kimi korur?

Ödeme servisi yanıt vermiyor ve her çağrı 30 saniye bekleyip düşüyor. Sipariş servisindeki breaker açıldı. En çok kim rahatlar? Cevabı göster

Sipariş servisi, yani çağıran. İstek hiç gönderilmez, thread beklemeden geri döner.

En sık duyulan açıklama: “Circuit breaker, çökmüş servisi korumak için ona istek göndermeyi durdurur.”

Bu ikincil bir fayda; asıl amaç çağıranın kaynaklarını serbest bırakmak. Breaker açıkken istek hiç gönderilmez, 30 saniye beklenmez, thread anında geri döner. Bağımlılığın nefes alması güzel bir yan etki, ama sebebi bu değil.

Kafam karıştı, daha basit anlat

Evdeki sigorta gibi: bir cihaz kısa devre yapınca sigorta atar, ev yanmaz. Circuit breaker da bozuk servise gitmeyi keser, senin thread’lerin boşuna beklemez.

Hızlı kontrolOrta

Breaker OPEN durumundayken bir çağrı gelirse ne olur?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kapalı, açık, yarı açık

DurumÇağrılara ne olurÇıkış koşulu
CLOSEDHepsi geçer, sonuçlar kayan pencereye yazılırPencere dolu ve hata oranı ≥ eşik → OPEN
OPENHiçbiri gitmez, anında hata (fail fast)Bekleme süresi doldu → HALF_OPEN
HALF_OPENSınırlı sayıda deneme çağrısı geçerHepsi başarılı → CLOSED · biri bile hatalı → OPEN

Kritik ayrıntı: HALF_OPEN durumunda tek bir başarısızlık yeter. Henüz toparlanmamış bir servisi tüm trafikle tekrar ezmemek için tasarım böyle.

Kafam karıştı, daha basit anlat

Kapalıyken akım geçer, açıkken sigorta atmıştır ve istek gönderilmez. Yarı açıkken “düzeldi mi” diye birkaç deneme yapılır. Bir deneme bile başarısızsa sigorta yeniden atar.

Hızlı kontrolOrta

Circuit breaker'ın üç durumu nedir ve aralarında nasıl geçilir?

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Kendin dene

Aşağıdaki simülatörde slider’ları oynat ve breaker’ın ne zaman açıldığını izle. “Servisi düşür” düğmesiyle bağımlılığı öldür, sonra OPEN durumundayken ayağa kaldır ve HALF_OPEN denemelerini seyret.

Circuit Breaker durum makinesi

Tohum 7
eşik aşıldısüre doldudenemeler geçtiCLOSEDOPENHALF_OPEN
CLOSED

Kayan pencere · hata oranı %0 / eşik %50

Başarılı
0
Hatalı
0
Reddedilen
0
Açılma sayısı
0
Hız
Adım 0

Şu an ne oldu?

CLOSED — pencere doluyor (0/10)

Pencere dolmadan hata oranı hesaplanmaz. Aksi halde ilk çağrı başarısız olunca oran %100 görünür ve breaker gereksiz yere açılırdı.

Aklında kalsın: Minimum çağrı sayısı olmadan bir breaker, düşük trafikte tek bir hatayla açılır. Bu ayarı bilmek ayırt edicidir.

Görevler0/3

  • Breaker’ı OPEN durumuna düşüraçık

    İpucu

    Varsayılan ayarlarla oynat ya da "Servisi düşür" düğmesine bas. Pencere dolmadan açılmadığına dikkat et.

  • Açılan breaker’ı yeniden CLOSED’a getiraçık

    İpucu

    Önce breaker’ı aç, sonra hata oranını düşür veya servisi ayağa kaldır. HALF_OPEN denemelerinin hepsi geçmeli.

  • %30 hata oranında breaker’ı hiç açtırmadan 40 çağrı geçiraçık

    İpucu

    Hata oranını %30’a çek. Sonra eşiği yükselt veya pencereyi büyüt — ama gerçek bir kesintiye ne kadar geç tepki vereceğini de düşün.

Olay günlüğü (0)

Henüz olay yok. Oynat veya adımla.

Denemeye değer üç şey:

  • Eşiği %90 yap. Breaker neredeyse hiç açılmıyor — servis batarken sen de batıyorsun.
  • Pencereyi 4 yap. Breaker çok hassaslaşıyor; iki talihsiz hata onu açmaya yetiyor.
  • Servisi düşür, OPEN olmasını bekle, sonra ayağa kaldır. Breaker’ın toparlanması bekleme süresi + deneme sayısı kadar sürüyor. Bu gecikme tasarımın parçası.

Simülatörün altındaki Görevler listesi bunları hedefe çeviriyor. Üçüncü görev en öğretici olanı: %30 hata oranında breaker’ı açtırmamanın bir bedeli var.

Hızlı kontrolİleri

Her hatayı, breaker'ın hata sayacına girmesi gerekip gerekmediğine göre ayır.

Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

Sınıflandırılmamış

Sayılmalı

Bağımlılığın sağlıksız olduğunu gösterir

    Sayılmamalı

    Sorun karşı tarafta değil

      Minimum çağrı sayısı: küçük ama önemli

      Pencere dolmadan hata oranı hesaplanmamalı. Neden:

      Neden minimum çağrı sayısı gerekir
      // Minimum sayı yoksa:
      // 1. çağrı -> hata -> oran %100 -> eşik %50 aşıldı -> OPEN
      //
      // Tek bir geçici hata, düşük trafikli bir endpoint'i tamamen kapattı.

      Resilience4j’de bu minimumNumberOfCalls, ve varsayılanı 100’dür:

      ResilienceConfig.java
      CircuitBreakerConfig.custom()
      .slidingWindowType(SlidingWindowType.COUNT_BASED)
      .slidingWindowSize(20)
      .minimumNumberOfCalls(10) // bu satır olmadan düşük trafikte erken açılır
      .failureRateThreshold(50)
      .waitDurationInOpenState(Duration.ofSeconds(10))
      .permittedNumberOfCallsInHalfOpenState(3)
      .slowCallDurationThreshold(Duration.ofSeconds(2)) // yavaş çağrı da "hata" sayılır
      .slowCallRateThreshold(50)
      .build();

      slowCallDurationThreshold satırı da en az bunun kadar önemli: yukarıda anlattığımız “ayakta ama yavaş” senaryosunu yakalayan ayar budur.

      Kafam karıştı, daha basit anlat

      İki istekten biri hata verdi diye “yarısı bozuk” deme. Karar vermeden önce yeterince istek görmen gerekir.

      Hızlı kontrolİleri

      Pencere 10, eşik %50, `minimumNumberOfCalls` 10. İlk iki çağrı da başarısız oluyor. Breaker açılır mı?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.
      BreakerAyari.java
      1CircuitBreakerConfig.custom()
      2 .slidingWindowSize(10)
      3 .failureRateThreshold(50)
      4 .minimumNumberOfCalls(10)
      5 .build();
      6
      7// cagri 1 -> hata
      8// cagri 2 -> hata
      Java 21UTF-8LF

      Breaker açıldığında çağıran ne yapmalı?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Tuzaklar: tek başına yetmez

      Circuit breaker bir katmandır, çözümün tamamı değil. Yanında duranlar:

      • Timeout. Breaker’sız timeout işe yarar; timeout’suz breaker yaramaz. Çağrının ne zaman “başarısız” sayılacağını timeout belirler.
      • Retry + exponential backoff + jitter. Retry’ı breaker’ın içine koyarsan her mantıksal çağrı üç fiziksel çağrı olur ve pencere yanlış dolar. Jitter olmadan tüm istemciler aynı anda tekrar dener — thundering herd.
      • bulkheadHer bağımlılığa ayrı ve sınırlı bir kaynak havuzu vermek. Yavaşlayan bağımlılık kendi havuzunu doldurur, servisin geri kalanı çalışmaya devam eder.Sözlükte gör →. Her bağımlılığa ayrı thread havuzu. Breaker açılana kadar geçen sürede bile ödeme çağrıları katalog çağrılarının havuzunu yiyemez.
      • Fallback. Breaker açıkken ne döneceksin? Önbellekten eski veri, kısıtlı bir cevap veya net bir hata — ama bilinçli bir karar.

      Aşağıdaki örnek bir bankanın kredi başvuru servisinden ve kredi bürosu çağrısını Resilience4j ile koruyor: zaman aşımı, retry’ın dışında duran bir circuit breaker, anlamlı bir fallback ve durum değişikliklerini gösteren log’lar.

      Derinleş · Kredi bürosu çağrısı için circuit breaker: uçtan uca 6 dosya · ~125 satır · ilk okumada atlayabilirsin
      Proje dosyaları

      src/main/resources/ application.yml Breaker ve retry ayarları. minimumNumberOfCalls, birkaç hatayla erken açılmayı önler; yavaş çağrılar da hata sayılır.

      src/main/resources/application.yml
      resilience4j:
      circuitbreaker:
      instances:
      creditBureau:
      sliding-window-type: COUNT_BASED
      sliding-window-size: 20
      minimum-number-of-calls: 10 # no verdict from 2 unlucky calls
      failure-rate-threshold: 50
      slow-call-duration-threshold: 2s # slow counts as failing
      slow-call-rate-threshold: 50
      wait-duration-in-open-state: 30s
      permitted-number-of-calls-in-half-open-state: 3
      record-exceptions:
      - org.springframework.web.client.ResourceAccessException
      - org.springframework.web.client.HttpServerErrorException
      ignore-exceptions:
      - com.bank.loan.NoCreditHistoryException # a business answer is not an outage
      retry:
      instances:
      creditBureau:
      max-attempts: 2
      wait-duration: 200ms
      retry-exceptions:
      - org.springframework.web.client.ResourceAccessException

      src/main/java/com/bank/loan/ BureauHttpConfig.java Timeout olmadan breaker işe yaramaz: yavaş servis thread tutar ama hiç 'hata' olmaz.

      src/main/java/com/bank/loan/BureauHttpConfig.java
      @Configuration
      class BureauHttpConfig {
      @Bean
      RestClient bureauRestClient(RestClient.Builder builder) {
      var factory = new SimpleClientHttpRequestFactory();
      factory.setConnectTimeout(Duration.ofMillis(500));
      factory.setReadTimeout(Duration.ofSeconds(2)); // matches slow-call-duration-threshold
      return builder.baseUrl("https://bureau.example").requestFactory(factory).build();
      }
      }

      src/main/java/com/bank/loan/ CreditBureauGateway.java Sıra açık: retry içeride, breaker dışarıda. Bir mantıksal çağrı pencereye bir kayıt düşer.

      src/main/java/com/bank/loan/CreditBureauGateway.java
      @Component
      class CreditBureauGateway {
      private final RestClient http;
      private final CircuitBreaker breaker;
      private final Retry retry;
      CreditBureauGateway(RestClient bureauRestClient, CircuitBreakerRegistry breakers, RetryRegistry retries) {
      this.http = bureauRestClient;
      this.breaker = breakers.circuitBreaker("creditBureau");
      this.retry = retries.retry("creditBureau");
      }
      BureauReport report(InquiryRequest request) {
      Supplier<BureauReport> call = () -> http.post().uri("/inquiries")
      // The same inquiry id on a retry lets the bureau recognise a repeated inquiry.
      .header("X-Inquiry-Id", request.inquiryId())
      .body(request)
      .retrieve()
      .body(BureauReport.class);
      return Decorators.ofSupplier(call)
      .withRetry(retry) // inner: up to 2 physical attempts
      .withCircuitBreaker(breaker) // outer: records ONE logical call
      .withFallback(List.of(CallNotPermittedException.class, ResourceAccessException.class),
      ex -> BureauReport.unavailable(request.applicationId()))
      .get();
      }
      }

      src/main/java/com/bank/loan/ LoanApplicationService.java Fallback bir iş kararıdır: başvuru reddedilmez, skor gelince değerlendirilmek üzere bekleyen olarak kaydedilir.

      src/main/java/com/bank/loan/LoanApplicationService.java
      @Service
      class LoanApplicationService {
      private final CreditBureauGateway bureau;
      private final LoanApplicationRepository applications;
      private final CreditPolicy policy;
      LoanApplicationService(CreditBureauGateway bureau, LoanApplicationRepository applications, CreditPolicy policy) {
      this.bureau = bureau;
      this.applications = applications;
      this.policy = policy;
      }
      @Transactional
      public ApplicationResult apply(LoanRequest request) {
      LoanApplication application = applications.save(LoanApplication.received(request));
      BureauReport report = bureau.report(InquiryRequest.of(application));
      // The fallback is a product decision: never reject a customer because a
      // dependency is down. Keep the application and decide when the score arrives.
      return switch (report.status()) {
      case AVAILABLE -> ApplicationResult.decided(application.decide(policy.evaluate(report)));
      case UNAVAILABLE -> ApplicationResult.pending(application.markAwaitingScore());
      };
      }
      }

      src/main/java/com/bank/loan/ BreakerLogging.java Durum geçişlerini log'a yazar: CLOSED → OPEN → HALF_OPEN.

      src/main/java/com/bank/loan/BreakerLogging.java
      @Component
      class BreakerLogging {
      private static final Logger log = LoggerFactory.getLogger(BreakerLogging.class);
      BreakerLogging(CircuitBreakerRegistry breakers) {
      breakers.circuitBreaker("creditBureau").getEventPublisher()
      .onStateTransition(event -> log.warn("credit bureau breaker {}", event.getStateTransition()))
      .onCallNotPermitted(event -> log.debug("credit bureau call rejected while OPEN"));
      }
      }

      src/test/java/com/bank/loan/ BureauBreakerTest.java Test: yeterli sayıda hata sonrası breaker açılır ve çağrı hiç yapılmaz.

      src/test/java/com/bank/loan/BureauBreakerTest.java
      class BureauBreakerTest {
      @Test
      void opensAfterEnoughFailuresAndThenRejectsWithoutCalling() {
      var breaker = CircuitBreaker.of("test", CircuitBreakerConfig.custom()
      .slidingWindowSize(4)
      .minimumNumberOfCalls(4)
      .failureRateThreshold(50)
      .build());
      var calls = new AtomicInteger();
      Supplier<String> failing = CircuitBreaker.decorateSupplier(breaker, () -> {
      calls.incrementAndGet();
      throw new ResourceAccessException("down");
      });
      for (int i = 0; i < 4; i++) {
      assertThatThrownBy(failing::get).isInstanceOf(ResourceAccessException.class);
      }
      assertThat(breaker.getState()).isEqualTo(CircuitBreaker.State.OPEN);
      assertThatThrownBy(failing::get).isInstanceOf(CallNotPermittedException.class);
      assertThat(calls).hasValue(4); // the fifth call never reached the service
      }
      }

      Kendini sına

      Şimşek turu1/5

      Circuit breaker çağrılan servisi değil, çağıranı korur.

      Soru 1/4İleri

      Circuit breaker'ın birincil amacı nedir?

      Cevabı biliyor musun?Önce birini seç. Tekrar zamanlaması buna göre ayarlanıyor.

      Aklında kalacak üç şey

      1. 1 Circuit breaker çağrılan servisi değil, çağıranın thread'lerini korur. Hemen hata dönmek, çağıranın kaynaklarını serbest bırakır.
      2. 2 Üç durum var: kapalı (normal), açık (hiç gitme) ve yarı açık (birkaç deneme yap). Hata oranı eşiği aşınca açılır, süre dolunca dener, denemeler geçerse kapanır.
      3. 3 Minimum çağrı sayısı önemlidir: yeterince çağrı birikmeden oran hesaplanırsa az trafikte tek bir hata breaker'ı açar.
      Sonraki kapı Ödeme alındı ama kargo oluşturulamadı. Servisler ayrıyken işi nasıl geri alırsın? Saga Pattern · 10 dk

      5 kart sonraki derste seni bekliyor

      0/5 kart bu dersten toplandı

      Bu dersin üstüne kurulanlar

      Bunlar bu dersi temel alıyor; hazır olduğunda devam edebilirsin.