Daha Dayanaklı Mikroservisler için Resilience Pattern’lar

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. Ne yazık ki her zaman bunu başaramayabiliriz ve neyse ki kesintisiz hizmet verebilmek için uygulayabileceğimiz yaklaşımlar mevcut, bu yaklaşımların literatürdeki adı “Resilience Pattern” ’dır. Resilience Pattern’ları çözdüğü problem alanına göre farklı kategorilere ayırarak, ayrı başlık altında incelemek mümkün, fakat bu yazıda en çok ihtiyaç duyulan ve kullanılması elzem olan tasarım desenlerine bakacağız.

Kategorize Edilmiş Resilience Pattern’lar

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 bu oranda hata oluşması ihtimalide artacaktır.

1. Circuit Breakers Tasarım Deseni

Circuit Breakers deseni, adından anlaşılacağı üzere elektronik devrelerdeki, devre kesici şalt cihazlar gibi kurgulanan bir yöntemdir. Devre kesiciler, elektronik devreyi korumak için sistemde meydana gelen bir aksaklık durumunda (yük akımını veya kısa devre akımları) yük geçişini durdururlar. Circuit Breakers deseni uygulandığında, servisler arasında haberleşmeyi kapsayacak şekilde inşaa edilir. Servisler arasındaki iletişimi (Event, Message, Http, vb.) izler ve haberleşmedeki meydana gelen hataları takip eder. Request yapılan bir API ucunun, http 500 hata kodu dönmesi veya fırlatılan bir event’in handle edilememesi bu hata duruma örnek olarak gösterilebilir. Sistemde meydana gelen hata durumu belirli bir eşik değerini geçtiğinde ise Circuit Breakers açık duruma geçer ve haberleşmeyi keser, daha önce belirlenen hata mesajlarını döndürür. Circuit Breakers açık durumdayken haberleşme trafiğini izlemeye devam eder ve istek yapılan servis veya fırlatılan bir event başarılı sonuçlar dönmeye başlamışsa kapalı duruma geçer, yani bu durumda teyakkuz halindedir.

Circuit Breakers’ın üç durumu vardır. Bu durumlar: Açık (Open), Kapalı (Closed) ve Yarı-Açık (Half-Open)

Kapalı (Closed) Durum:

Kapalı durum, devre anahtarının kapalı olması ve akım geçişine izin vermesi gibidir. Kapalı durumda olduğunda, her şeyin başarılı olarak gerçekleştiğini ve Circuit Breaker’ın sadece monitor ederek izleyici olarak kaldığı durumdur.

Circuit Breaker Kapalı Durumda

Yukarıdaki görselde, istemciden gelen bir kullanıcı isteği (turuncu bağlantılar) ve servisler arası gerçekleşen bir haberleşme trafiği (mavi bağlantılar) örneklenmiştir. Kullanıcıdan gelen istekler tıpkı bir elektrik akımı gibi kapalı devre üzerinden akmaktadır. Bir ve iki numaralı bağlantılarda servise giden istek hata almadığı için Circuit Breaker durumunu korumaya devam etmektedir.

Açık (Open) Durum:

Açık durum, devre anahtarının açık olması ve akım geçişine izin vermemesi gibidir. Aşağıdaki görselde, istemci tarafından yapılan isteklere (turuncu bağlantı) Mikroservis A tarafından hata döndürmesi görülmektedir. Servisin hata döndürmeye devam etmesi durumunda belirlenen eşik değeri aşılmış ve Circuit Breaker açık duruma geçmiştir. Bu servise yapılacak aynı istekler artık Circuit Breaker da kalacak ve daha önceden belirlenen cevaplar döndürülecektir.

Circuit Breaker Açık Durumda

Yarı-Açık (Half-Open) Durum:

Yarı açık durumda, circuit breaker trafiğin bir kısmına izin verir, bazılarına ise daha önceden belirlenen hata mesajlarını döndürmeye devam eder. İzin verdiği istekleri izlemeye etmeye devam eder ve servislerin durumunu gözlemler. İzin verilen isteklerin hepsi başarı ile işleniyorsa ve servislerden bir hata dönmüyorsa kapalı duruma geçer, eğer ki hata almaya devam ediyor ise circuit breaker açık duruma geçmek için tetiklenir. Bu süreç tanımlanan policy’ler ile yönetilebilir.

İmplementasyonlar

Circuit Breaker için farklı implementasyonlar mevcuttur, proxy gibi konumlandırılabilirken, bir client paketi gibi tüm request’lerde kullanılabilecek bir helper olarak da tasarlanabilir ihtiyacınıza göre kurgulayabilirsiniz. Popüler implementasyonlardan bazıları aşağıdaki gibidir.

2. Bulkhead Tasarım Deseni

Bulkhead tasarım deseni, servisleri izole ederek bir yerde meydana gelen hatanın değer servisleri de etkilememesi için meydana gelen hatanın izolasyonunu amaçlar. Bulunduğu kategori itibari ile de izolasyon tasarım desenleri içerisinde yer alır.

Gemi mimarisi. Görsel kaynağı: shipdesign

Bu tasarım deseninin esin kaynağını ilginç bir şekilde gemi mimarisi oluşturur. Gemiler veya denizaltılar inşa edilirken, bir bütün olarak değil, belirli bölgelerden perdelenerek yapılırlar, böylece meydana gelecek bir su baskını veya yangın durumunda, ilgili bölme kapatılarak izolasyon sağlanır ve geminin/denizaltının görevini devam ettirebilmesi sağlanır.

Gravity filminde ISS’deki yangın sahnesi bu durumu daha iyi anlaşılır kılabilir sanırım 🙂 https://youtu.be/1AmDh6-RdlU

Geliştirdiğimiz servislerimizi de bir gemi gibi düşünürsek, bir serviste meydana gelecek hatanın diğer servisleri etkilememesi için, ilgili servisi izole ederek ve kullanıcı taleplerine cevap verebilir durumda olmalıyız.

Bulkhead Tasarım Deseni

Yukarıdaki görsel yardımıyla tasarım desenine neden ihtiyacımız olduğunu daha iyi anlayabiliriz. Bir numaralı bağlantı, kullanıcılardan gelen http isteklerini ifade etmektedir. İstek sayısı çok fazla olduğu durumda fiziksel makineler, tüm istekleri aynı anda işleyemeyecek ve doğal olarak sıraya koyacaktır. Kullanıcıların A servisine bir istek yaptığını düşünelim, A servisi yazılımsal bir bug dolayısıyla response dönemiyor ve kullanıcı istekleri iki numarada gösterildiği gibi kuyrukta birikmeye başlıyor. İstekler belirli bir zaman sonra makinelerin tüm CPU thread’lerini kullanmaya başlayacaklar ve bir süre sonra yeni gelen isteklerin hepsi kuyrukta beklemeye başlayacaktır. Çalışmayan A servisine gelen tüm istekler kaynakları meşgul ederken, başarılı olarak çalışan B ve C servisine gelen istekler ise kuyrukta beklemeye devam edecekler, bu durumda talep sahibi kullanıcıların hiçbiri memnun kalmayacaktır.

Bu soruna cevap olarak bulkhead tasarım deseni de, işlemleri bir birinden ayırarak izole edelim ve bir servis cevap vermez durumda olduğunda, o servise gelen istekleri işlemek için tüm kaynakları kullanmak yerine, kaynakların bir kısmı buraya ayrılırken diğer istekleri ise bekletmeden kalan sunucu kaynağı ile işlenmesini önermektedir.

Bu tasarım desenini uygulamak için, servislerin çalıştığı uygulama sunucusuna kaynak limitleri tanımlanabilir, örneğin servisin kullanabileceği maksimum thread sayısı ve bellek limiti sınırı konulabilir.

Eğer ki servislerinizi Kubernetes ortamında çalıştırıyorsanız, pod seviyesinde kaynak kullanımı sınırlandırılarak bu durum çözülebilir. Örnek bir Kubernetes kaynak sınırlandırma konfigürasyonu; https://gist.github.com/mesutpiskin/b01d742db83762cb9cc6099aa51a1a59

 

3. Retry Tasarım Deseni

Servisler arası haberleşme, HTTP, RPC, vb. gibi dayanaklılığı bizim tarafımızdan kontrol edilebilecek durumda olsa da , network, sunucu vb. gibi fiziksel kaynaklı kesintiler kaçınılmaz olabilmektedir. Transaction esnasında servislerden birisinin http 500 dönmesi durumunda transaction kesilebilir ve kullanıcıya hata döndürülebilir, bu bir tercihtir, fakat kullanıcı aynı transaction’ı tekrar başlattığında çalışma ihtimalide yüksek olabilir. Böylesi bir durumda 500 dönen servise yapılan isteği tekrarlamak daha mantıklı olacaktır, böylelikle gerçekleşen işlemlerin roll-back’leri çalışmak zorunda kalmayarak ekstra yük getirmeyecek ve kullanıcı geç de olsa işlemini başarı ile gerçekleştirebilecektir.

Retry Tasarım Deseni

Yukarıdaki görselde bir ve iki numaralı bağlantı okları ile ifade edilen kullanıcı isteği A servisine ulaşmaktadır. A servisi, B servisine ihtiyaç duymakta ve bir istek oluşturmaktadır eğer ki senktron işleyen bir transaction süreci ise ve B servisi http 500 döndürürse veya hata alırsa transaction sonlacaktır, tasarım deseni uygulandığında ise belirtilen adet kadar işlem denenecek ve belkide başarı ile gerçekleştirilebilecektir.

Eğer ki service mesh istio mimarisine sahipseniz, Retry ve Circuit tasarım desenlerini uygulamak zaruridir.

İmplementasyonlar