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.