Entegrasyon Noktalarında Hata Yönetimi

Mikro servis mimarisinin kullanımı arttıkça, uygulamalar daha fazla servis tüketmeye başladı ve uygulamanızın daha fazla servis tüketmesi demek, aynı zamanda uygulamanızın da bu bağlantı noktalarında daha fazla hatayla karşı karşıya olması demektir. Bense bu yazıda entegre olduğunuz servislerle ilgili hataları yönetirken kullanabileceğiniz bir kaç şablondan bahsedeceğim.

Entegrasyon noktası bir uygulamanın en büyük düşmanıdır, özellikle de ara servisleri geliştiren bizler için. Kullandığınız servisteki bir hata yüzünden sizin servisin kabahatli bulunması çok tanıdık bir senaryoysa ne demek istediğimi daha iyi anlıyorsunuzdur. İnsanı kötü hissettiren bu durum aslında dışarıdan bir gözle baktığınızda gayet normaldir. Çünkü, uygulamanızın bir servisle entegre olması demek, uygulamanızın artık o servis olmadan çalışamaması demektir. Bağımlı olduğunuz servisteki en küçük hata bile sizin servisinizi etkileyebilir. Fakat yazılımcılar olarak bu etkiyi azaltmanın yolları mevcut. Şimdi bir kaç şablon üzerinden bunu nasıl yapabileceğimize bakalım.

Olabildiğince Verinizi Önbellekte Saklayın

Her servisle farklı bir nedenden entegre oluruz; kimine hesapladığımız veriyi kaydetmek için, kimine ihtiyaç duyduğumuz veriyi çekmek için kimine de bizim adımıza belli hesaplamaları yapması için entegre oluruz. Bunların hepsinde ama özellikle de ihtiyaç duyduğumuz veriyi çektiğimizde, önbellekte saklamayı (eng. Caching, argo. Keşlemek) kullanabiliriz.

Örneğin verilen herhangi bir dövizi Türk lirasına çeviren bir servis geliştirdiğimizi varsayalım.  Bu servisi geliştirmek için ise döviz kurlarının tutulduğu bir servisi tüketecek olalım. Servisimiz gayet basit, bize gönderilen döviz miktarı için kuru okuyup dört işlem yapacağız. Döviz bilgisi hızlı değişen bir bilgi olduğundan her seferinde bu bilgiyi yeniden okumayı tercih edebiliriz. Fakat bağımlı olduğunuz servis erişilemez olduğunda ne yapacaksınız? Hiç veri dönmeyecek misiniz? Sizi tüketen diğer servisler buna ne şekilde yaklaşacaktır? Yoksa, bir hata anında 15 dakika önceki bilgi ile de cevap vermeniz sistem açısından kabul edilebilir bir hata mıdır? Eğer kabul edilebilirse bu bilgiyi ön bellekte tutup, hata anında bu bilgi üzerinden hareket etmek bu durumda en mantıklı yol gibi görünüyor.

Burada ek bir parantez daha açıp önbellekte tutmanın uygulamanızın performansını da arttıracağını unutmayın. Fakat performans üzerinde etkileri görmek için daha sağlam ve dağıtık bir yapı (Mesela: hazelcast, ehcache, elasticache) kullanmanız gerekebilir.  Öte yandan hata anları için basit bir kütüphane de işinizi görecektir. (Mesela: Mapdb)

Toparlayacak olursak, veri çektiğiniz her yerde veriyi önbellekte tutup tutamayacağınıza bakın. Bunu yaparken verinizin tutarlılığından ödün vermeniz gerekeceğini de göz önünde bulundurmayı unutmayın.

Tekrar Deneyiniz

Kulağa kola reklamı gibi gelse de, pratikte çok sıkça kullanılan bir yöntem. Bir servisle entegre olursunuz, hata almışsınızdır, hatanın neden kaynaklandığını öğrenmek istersiniz. Doğal olarak tükettiğiniz servise gidip, “Arkadaşım, ben böyle böyle bir hata aldım, bunun kaynağı ne, neden böyle oluyor sorularını açıklar mısın?” dersiniz. Alacağınız cevap basit ve nettir, “Oluyor ya öyle arada, tekrar dene, çalışır!”. Kulağa komik gibi gelse de, evet tekrar denersiniz ve çalışır. Hatta belki böyle bir cevabı siz de vermiş olabilirsiniz. Amacım bu cevabı veren bir servis kalitesizdir gibi bir çıkarım yapmak değil. DynamoDB bile olsan böyle bir cevap verebiliyorsunuz. (bkz. DynamoDB Forumundaki bu soru ve cevabı) Kabul edelim tekrar deneme bir ihtiyaç ve bu sebepten bunu otomatik bir şekilde, belli kurallar çerçevesinde yapmalısınız.

Tükettiğiniz servisin hata vermesi dünyanın sonu değil ya da bu sizin servisinizin hata vermesi için bir sebep değil. Tükettiğiniz servisin dokümantasyonunda neyin geçici bir hata olduğu neyin istemci hatası olduğu bilgileri açıkça yer alır (almıyorsa da almalı). Bu bilgilere göre hangi hatayı tekrar deneyeceğinize karar verebilirsiniz. Hangi hatada tekrar deneyeceğinize karar verdikten sonra sıra nasıl tekrar deneyeceğinize geliyor.

  • Öncelikle hata aldığınızda anında tekrar denememelisiniz. Tükettiğiniz sistemi boğmamak ve toparlanmasına fırsat vermek için bir süre bekleyip tekrar denemelisiniz. (Mesela 20 ms)
  • Sadece belirli bir sayıda tekrar edip sonrasında vazgeçmelisiniz. (Mesela 5 denemeden sonra) Vazgeçtiğinizde kendi sisteminizden döndüğünüz cevabın tekrar edilebilir olduğunu belirtmelisiniz. Böylelikle sizin servisinizi tüketen diğer servislerde gönül rahatlığıyla tekrar deneyebilir.
  • Tekrar ederken beklediğiniz sürenin katlanarak arttığına emin olunuz. (Mesela 20 ile başladıysanız bir sonraki 40 sonraki 80 ms beklemelidir.) Ek olarak rastgele bir gecikme (jitter) eklemeniz, özellikle birden fazla thread ile aynı servisi çağırıyorsanız, yerinde olacaktır.
  • Ne kadar tekrar denerseniz deneyin yine de hata alabileceğinizi unutmayın. Tekrar deneme sadece aldığınız hata sayısını azaltacaktır. Hataları kökünden çözmeyecektir. Bu sebepten sisteminizi olabildiğince takip edin.

Örneğin bir AWS servisini tüketiyorsanız, dokümantasyonunu açtığınızda hangi hatayı tekrar etmeniz gerektiğiyle ilgili bir çok bilgiye ulaşabilirsiniz. Hatta bu bilgiler o kadar detaylıdır ki hangi hata geldiğinde ne kadar süre bekleyip tekrar denemeniz gerektiği, katlanarak ne kadar arttığı bilgilerine bile ulaşabilirsiniz.

Uygulamanıza tekrar denemeyi eklemenin ise farklı yolları var:

  • Eğer istemci kütüphanesi, tekrar deneme mantığı ile birlikte paketlenmişse en rahatı bu yapıyı kullanmaktır
  • Eğer RxJava kullanıyorsanız, RxJava ile bütünleşik olarak tekrar deneme mantığı zaten geliyor
  • Spring kullanıyorsanız SpringRetry ile kolaylıkla tekrar denemeleri aktif edebilirsiniz. Aktif ettikten sonra tek yapmanız gereken bir kaç annotasyon eklemek olacaktır.
  • Kendi tekrar deneme mantığınızı yazabilirsiniz. Basit bir tekrar deneme kütüphanesi yazmak kolay olacaktır. Özellikle Java 8 kullanıyorsanız method referansları ile daha genel bir sınıf bile yazabilirsiniz.

Kısa Devre Yapın

Kısa devre (eng. Circuit Breaker), benim çok sonra aşina olduğum fakat uygulamanızın hayatını kurtarabilecek bir tasarım şablonudur. Basit olarak yaptığı şey, elektirik devrelerindeki kısa devre mantığını entegre olduğunuz servislere taşımaktır. Eğer tükettiğiniz servislerden biri beklediğiniz sürede cevap vermiyorsa ya da hiç ulaşılamıyorsa, yani tükettiğiniz servis sağlıklı bir durumda değilse, bu servis ile olan bağlantınız kısa devre yapılır. Kısa devre olduktan sonra o servise çağrılarınız iletilmemeye başlar. Burada kritik olan ‘sağlıksız’ servis tanımını doğru yapmak. Bunun için tükettiğiniz servislerin vaatlerini ya da sözleşmelerinizi (SLA) kullanabilirsiniz. Örneğin, tükettiğiniz servis peş peşe 5 çağrıya da hata (http 500) döndüyse ya da ard arda gelen 5 çağrı ortalama 10sn üzerinde sürdüyse gibi ‘sağlıksız’ servis tanımları yapabilirsiniz.

Genelde akla ilk gelen soru, “Peki servis kısa devre yapıldıktan sonra ne olacak?” oluyor. Burada yürüteceğiniz strateji size bağlı. Anında hata cevabı dönebilirsiniz ya da önbellekte sakladığınız bilgilerden faydalanabilirsiniz.

Akla gelen ikinci soru ise, genelde “Peki servisin sağlıklı hale geldiğini nerden bileceğiz?” oluyor. Algoritmanızı geliştirirken ya da bir kütüphaneyi kullanırken “sağlıksız” tanımınızı yaptığınız gibi bir de “sağlıklı” servis tanımı yapmalısınız. Örneğin, uygulamanız, kısa devre olduktan sonra gelen her 5 istekten birini deneyip tüketilen servisin sağlığını kontrol edebilir. Eğer denediği istek başarılı şekilde sonuçlanırsa, uygulamanız kaldığı yerden devam eder.

Burada altını çizmek istediğim bir durum var. Bu tarz bir şablonu uygulayabilmeniz için, tükettiğiniz servisin düzgün hata kodları dönmesi gerekiyor. Aksi halde sizin “sağlıksız” ya da “sağlıklı” servis tanımı yapmanız çok zor olacaktır.

Eğer bir mikro servis mimarisindeyseniz, zamanında cevap vermeyen bir servis katlanarak büyüyen bir soruna neden olabilir. Uygulamanız zamanında cevap alamadığına, zaman aşımı süresi kadar bekleyecektir. Bir de bunun üzerine tekrardan deneme yapıyorsanız bekleme süreniz çok daha fazla artacaktır ve bu da makinenizin kaynaklarını tüketmeniz demektir. Sadece istemci tarafında değil, geç cevap veren servis tarafında da soruna yol açabilir. Örneğin, tükettiğiniz servis hiç bir talep yokken rahatlıkla kendini toparlayabilecekken, durmadan gelen talepler tamamen çökmesine yol açabilir. Bu sebepten kısa devre şablonu daha sağlıklı bir servis ağı geliştirmenize yardımcı olacaktır.

Eğer kendiniz bu şekildeki bir şablonu geliştirmekle uğraşmak istemiyorsanız, Netflix’in Hystrix kütüphanesine göz atmanızı öneririm.

Son

Özetleyecek olursak, entegrasyon noktalarınız hataya en açık noktalardır. Tükettiğiniz servisi kontrol edemeyebilirsiniz fakat sizin servisiniz üzerinde yarattığı etkiyi en aza indirebilirsiniz. Bunu yaparken önbellekte veri saklamak, tekrar denemek ve kısa devreler yapmak işinizi kolaylaştıracaktır.