Resilience Pattern: Circuit Breaker

Gokhan Konuk
Akbank Teknoloji
Published in
4 min readDec 23, 2022

--

Railway tracks in the sunset. Taken at Frankfurt Central Station. Image on Wiki by Arne Hückelheim.
Railway tracks in the sunset. Taken at Frankfurt Central Station. Image on Wiki by Arne Hückelheim.

Bir uçak seyahati yaptığınızı düşünün, oksijen maskesi ve can yeleği nadiren dikkatinizi çeker, nedeni oldukça nettir, çünkü bunlar sadece bir aksilik durumunda kullanılacaktır. Temel varsayımınız, uçuşunuzun başarısız olmayacağı, kullanmanıza gerek kalmayacağı şeklindedir. Bu nedenle de dikkat etmezsiniz. Fakat bu durum Bilişim Teknolojileri için doğru değildir.

“Everything fails all the time” — Werner Vogels, AWS CTO

Eminim, bir çok ünlü web sitesinin çeşitli nedenlerle erişilimez duruma geldiğini duymuşsunuzdur. Dağıtık bir ortamda her an her şey başarız olabilir.

Bir mimar olarak, başarısızlıkları tamamiyle engelleyemezsiniz. Ancak bu başarısızlıkları da kapsayacağınız tasarımlar yapabilirsiniz.

Uygulamaların kesintisiz hizmet verebilmesi için; mimarinin doğru tasarlanması, geliştiricilerin de bu mimariye uygun ve hataya sebebiyet vermeyecek uygulamalar geliştirmesi gerekir. Mikroservis mimaride servislerin dayanıklılığını sağlamak, monolitik bir mimariye göre nispeten daha zordur. Mikroservis mimaride servisler arası iletişim dağıtık olarak gerçekleşmekte ve bir transaction için bir çok internal veya external network trafiği oluşmaktadır. Servisler arasındaki iletişim ihtiyacı ve dış servislere bağımlılık arttıkça hata oluşması ihtimali de bu oranda artacaktır. Neyse ki kesintisiz hizmet verebilmek için uygulayabileceğimiz yaklaşımlar mevcut, bu yaklaşımların literatürdeki adı “Resilience Pattern” ’dır. Bu yazı serisinde Akbank Teknoloji olarak alt yapımızı daha dayanaklı inşa edebilmek için araştırmalarını yapıp, POC’lerini yaptığımız tasarım desenlerine bakacağız.

Circuit Breaker, bir uygulamanın başarısız olması neredeyse kesin olan bir işlemi gerçekleştirmesini engeller. Bunu biraz daha açalım. Sisteminize aniden yüklenme oluyor ve kaldırabileceğinden daha fazla API isteği var. Örneğin, servisin genel yanıt süresi eşiği 5 sn ve artık istekler bundan daha fazla sürüyor ve servis 500 hata kodu dönmeye başlıyor. Kullanıcıya yaşatacağı kötü deneyimi bir kenara bırakırsak, belli bir noktanın üzerinde daha fazla yük olursa sisteminiz tamamen kapanabilir. Şimdi daha fazla isteği durdurma zamanı.

Bunu bir elektrik devre kesicinin nasıl çalıştığıyla karşılaştıralım. Bununla hiç karşılaştınız mı? Bir cihaza ait fişi prize taktınız ve elektriğiniz aniden gitti. Şimdi elektrik panosuna gitmeniz ve kapalı duruma gelen anahtarı açmanız gerekebilir. Devre kesicinin gerçek dünyada çalıştığı yer burasıdır. Ne yaptı? Yükün fazla olması nedeniyle daha fazla zarar vermemek için elektriği kesti.

Şimdi bizim dünyamıza geri dönelim. Circuit Breaker modelinin üç durumu vardır. Kapalı, Açık ve Yarı Açık. Kapalı durumda her şey normaldir ve sisteminiz olması gerektiği gibi çalışır. Bir sorun olduğunda Circuit Breaker açılır ve veri akışı kesilir. Bu, amaçlanan taleplerin kullanıcılara sunulmadığı anlamına gelir. Yarı Açık, sorunun devam edip etmediğini görmek için bir süre bekledikten sonra test etmeye çalışır. Başarılı olursa, sistem normale dönecek şekilde durum Kapalı olarak değiştirilir. Başarılı olmazsa, Açık duruma geri dönecektir.

Bu modelin avantajı, başarısızlıklar veya yavaş yanıtlar olması durumunda sistemin tamamiyle kapanmamasıdır. Açık durumda bekleme süresi ile iyileşme süresi sağlar. Devreyi açarak, bekleme ve zaman aşımı yerine kullanıcılara anlamlı bir yanıt gönderebiliriz veya alternatif yollar sunabilirz.

Spring Boot ile resilience4j

Resilience4j, Netflix Hystrix’den esinlenilmiş ve Mikroservis mimarisi için dayanıklılık/hata toleransı modeli sunan bir kütüphanedir. Dökümanlarında belirttikleri gibi light weight ve kullanımı kolaydır. Spring Boot ile bu tanımları yapmak ve annotasyon’lar kullanarak uygulamalarımıza dahil etmek oldukça kolaydır.

Resilience4j kütüphanesi hata toleransı kapsamında bir çok farklı tasarım sunmaktadır; Circuit Breaker, Bulkhead, Timeouts, Rate Limiters ve Retries. Biz bu yazımızda Circuit Breaker kullanımını ele alacağız.

Spring Boot ile Circuit Breaker:

  • Projenin veya modülün pom.xml dosyasına aşağıdaki bağımlılıkları ekle.
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>1.7.0</version>
</dependency>

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

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
  • Circuit Breaker için application.yml içerisine tanımlar ekle.
resilience4j.circuitbreaker:
instances:
ratingService:
slidingWindowType: COUNT_BASED #request sayısına göre yapılandır
slidingWindowSize: 100 #Kapalı durumdayken son kaç adet request için kayıt tutacağını belirtir
permittedNumberOfCallsInHalfOpenState: 10 #Yarı Açık durumdayken kaç adet çağrıya izin vereceğini belirtir
waitDurationInOpenState: 10 #Açık durumunda ne kadar süre beklemesi gerektiğini belirtir
failureRateThreshold: 60 #Açık duruma geçmek için yüzde kaç hata alınması gerektiğini belirtir
recordExceptions: #Hata olarak hesaba kattığımız exception tipini bu şekilde belirtebiliriz
- org.springframework.web.client.HttpServerErrorException

Yukarıdaki tanımlara ve detaylarına resilience4j docs linkinden erişebilirsiniz.

  • Ardından, external servisi çağıran methoda ilgili annotasyonu ekle.
@CircuitBreaker(name = "ratingService", fallbackMethod = "getDefault")
public ProductRatingDto getProductRatingDto(int productId){
return this.restTemplate.getForEntity(this.ratingService + productId, ProductRatingDto.class)
.getBody();
}

public ProductRatingDto getDefault(int productId, Throwable throwable){
return ProductRatingDto.of(0, Collections.emptyList());
}

En temelde, Circuit Breaker’ı configure etmek için ihtiyacımız olan şeyler bunlar.

Spring Boot Actuator’da Circuit Breaker detayları ve durumları:

  • Spring Boot Actuator, bizlere sunduğu endpoint’ler aracılığıyla uygulamamız hakkında çeşitli bilgiler (health check, disk usage, heap dump vs.) almamızı sağlar.

Endpoint: /actuator/metrics

  • Metrics endpoint’i default olarak gösterilmediğinden, tüm actuator endpoint’lerini ortaya çıkarmak için aşağıdaki yapılandırmayı application.yaml dosyasına ekleyin.
management:
endpoints:
web:
exposure:
include: '*'
  • Ardından, localhost:8080/actuator/metrics endpoint’inin altında tüm mevcut metrikleri görebiliriz.

Bu metriklerle ilgili daha fazla ayrıntı, metrik adını aşağıda gösterildiği gibi /actuator/metrics endpoint’ine eklenerek görüntülenebilir.

/actuator/metrics/{requiredMetricName}
/actuator/metrics/resilience4j.circuitbreaker.calls
/actuator/metrics/resilience4j.circuitbreaker.failure.rate
/actuator/metrics/resilience4j.circuitbreaker.slow.call.rate
/actuator/metrics/resilience4j.circuitbreaker.state

Endpoint: /actuator/circuitbreakerevents

  • Circuit Breaker event’leri, dairesel bir consumer arabelleğinde depolanır. eventConsumerBufferSize arabelleğinin boyutu application.yml’de configure edilebilir.
resilience4j.circuitbreaker:
instances:
ratingService:
eventConsumerBufferSize: 10
  • Ardından, localhost:8080/actuator/circuitbreakerevents endpoint’inin altında tüm mevcut event’leri görebiliriz.

Bu event’lerle ilgili daha fazla ayrıntı, /actuator/circuitbreakerevents endpoint ve türevlerine gidilerek görüntülenebilir.

/actuator/circuitbreakerevents
/actuator/circuitbreakerevents/{name}/{eventType}
/actuator/circuitbreakerevents/{name}
/actuator/circuitbreakers
/actuator/circuitbreakers/{name}

Akbank Teknoloji çatısı altında çalışmalarına devam ettiğimiz Resilience Pattern serisinin devamı için bizleri takip edin.

Ve son olarak takım arkadaşım Ziya Orujaliyev’e katkılarından dolayı teşekkür ediyorum.

Referanslar:
https://resilience4j.readme.io/docs/circuitbreaker#introduction
https://www.baeldung.com/spring-boot-resilience4j

--

--