DevOps and Infrastructure

Resilience4j ve Devre Kesici Desenleri ile Dayanıklı Mikroservisler İnşa Etmek

Modern dağıtık sistemler dünyasında arıza, "eğer" değil, "ne zaman" meselesidir. Mikroservis mimarisi kuran geliştiriciler olarak, ağ bölünmeleri, servis zaman aşımı ve üçüncü taraf API hatalarının kaçınılmaz olduğunu kabul etmeliyiz. Zincirinizdeki bir bileşen başarısız olursa, bu durum sistematik bir çöküşe yol açarak tüm uygulamanızı çökertebilir. İşte burada dayanıklılık desenleri devreye girer. Java ekosistemi için mevcut çeşitli kütüphaneler arasında, Resilience4j hafif ve fonksiyonel bir yaklaşım sunarak hataları yönetmede altın standart haline gelmiştir.

Mikroservislerde Dayanıklılık Neden Önemlidir?

Monolitik uygulamaların tek bir başarısızlık noktası vardır ancak bunlar yerel olarak yönetilmesi daha kolaydır. Mikroservisler ise tam tersine ağ gecikmesi ve karmaşıklığı getirir. Service A, Service B'yi çağırdığında ve Service B yavaş veya yanıt vermez hale geldiğinde, Service A'nın iş parçacıkları bloke olabilir, iş parçacığı havuzunu tüketerek sonunda çağrı yapan servisi çökertebilir. Buna "zincirleme arıza" denir.

Dayanıklılık mühendisliği, arızaları izole ederek, geçici hataları yeniden deneyerek ve zarif bir şekilde yedek plana geçerek bu zincirlemeyi önlemeyi amaçlar. Resilience4j, bunu başarmak için çeşitli modüller sağlar ve Devre Kesici (Circuit Breaker), zincirleme arızalara karşı koruma sağlama konusunda en kritik olanıdır.

Devre Kesici Desenini Anlamak

Devre Kesici deseni, evinizdeki elektrik sigortasına benzer şekilde çalışır. Çok fazla hata oluştuğunda, sigorta "atar" ve devreyi açarak arızalanan servise yönelik tüm istekleri durdurur. Belirtilen bir kurtarma süresinden sonra, sınırlı sayıda "test" isteğinin geçmesine izin verir. Bu istekler başarılı olursa, devre kesici kapanır; başarısız olursa tekrar açılır.

Resilience4j, bu deseni yüksek yapılandırılabilirlikle uygular; hata oranı eşiklerini, minimum çağrı sayısını ve kayan pencere boyutlarını tanımlamanıza olanak tanır.

Spring Boot'ta Resilience4j Uygulama

Başlamak için, projenize Resilience4j Devre Kesici bağımlılığını eklemeniz gerekir. Maven kullanıyorsanız, pom.xml dosyanıza aşağıdakileri ekleyin:

<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-spring-boot2</artifactId>
    <version>1.7.1</version>
</dependency>
<dependency>
    <groupId>io.github.resilience4j</groupId>
    <artifactId>resilience4j-circuitbreaker</artifactId>
    <version>1.7.1</version>
</dependency>

Ekledikten sonra, devre kesiciyi application.yml dosyanız üzerinden yapılandırabilirsiniz. Bu, yapılandırmayı dışa aktararak, kodu yeniden dağıtmadan üretim ortamında davranışı kolayca ayarlamanızı sağlar.

resilience4j:
  circuitbreaker:
    instances:
      backendA:
        sliding-window-size: 10
        failure-rate-threshold: 50
        wait-duration-in-open-state: 10s
        permitted-number-of-calls-in-half-open-state: 3

Hizmet katmanında, korumak istediğiniz yöntemlere basitçe anotasyon eklemeniz yeterlidir. Resilience4j, Spring'in AOP (Yöneldiği Programlama) ve Java'nın fonksiyonel arayüzleriyle sorunsuz bir şekilde entegre olur.

Pratik Kod Örneği: Yeniden Deneme ve Yedek (Fallback) Mekanizması

Sadece devreyi kesmekle kalmayıp, genellikle başarısız istekleri yeniden denemek (geçici ağ sorunları için) ve devre açık olduğunda bir yedek yanıt sağlamak istersiniz. İşte Spring Boot hizmetinde Resilience4j anotasyonlarını kullanarak bunu nasıl uygulayabileceğiniz:

@Service
public class PaymentService {

    private final PaymentClient paymentClient;

    // Yapıcı metod enjeksiyonu
    public PaymentService(PaymentClient paymentClient) {
        this.paymentClient = paymentClient;
    }

    @CircuitBreaker(name = "paymentService", fallbackMethod = "paymentFallback")
    @Retry(name = "paymentService")
    public String processPayment(String orderId) {
        // Harici ödeme kapısını çağır
        return paymentClient.charge(orderId);
    }

    // Yedek yöntem imzası, orijinal yöntemle eşleşmelidir
    public String paymentFallback(String orderId, Exception e) {
        log.error("Sipariş {} için ödeme başarısız oldu, nedeni: {}", orderId, e.getMessage());
        return "Ödeme servisi şu anda kullanılamıyor. Lütfen daha sonra tekrar deneyin.";
    }
}

Bu örnekte, @Retry anotasyonu, yapılandırılmış politika doğrultusunda çağrıyı tekrarlayarak geçici arızaları yönetir. Arızalar devam ederse, @CircuitBreaker devreye girer ve paymentFallback yöntemi çağrılır; bu da kullanıcının kriptografik bir hata veya zaman aşımı yerine zarif bir yanıt almasını sağlar.

Gözlemlenebilirlik ve İzleme

Devre kesicinin durumunu göremiyorsanız, işe yaramaz. Resilience4j, Prometheus ve Grafana gibi araçlarla entegre olan Micrometer aracılığıyla metrikleri dışa aktarır. resilience4j.circuitbreaker.call.fails veya resilience4j.circuitbreaker.state gibi metrikleri izleyebilirsiniz. Bu görünürlük, servisleriniz sık sık başarısız olmaya başladığında uyarı kurmanıza olanak tanır ve devre tamamen kırılmadan önce ekibinizin sorunu araştırması için zaman tanır.

Sonuç

Dayanıklı mikroservisler oluşturmak, tüm arızaları önlemekle ilgili değil, bunları etkili bir şekilde yönetmekle ilgilidir. Resilience4j'den yararlanarak, Java geliştiricileri minimum iskelet kodla Devre Kesiciler ve Yeniden Denemeler gibi endüstri standardı dayanıklılık desenlerini uygulayabilir. Bu desenler, sisteminizi zincirleme arızalardan korur, zarif bozulma yoluyla kullanıcı deneyimini iyileştirir ve üretim ortamında sistem sağlığını korumak için gerekli olan gözlemlenebilirliği sağlar. Mikroservis mimarinizi ölçeklendirdikçe, bu dayanıklılık stratejilerini entegre etmek en önemli önceliklerden biri olmalıdır.

Share: