Neden Microservis ?
Belirli Bir Teknoloji / Dil Ekosisteminde Sıkışmak
İçerisinde çeşitli modüller barındıran monolith yapıda bir uygulama düşünelim.
Monolith yapıdan dolayı tüm kodumuz tek bir proje yani tek bir repository içerisindedir.
Talep doğrultusunda projenize yeni eklenecek olan bir özellik için uygulamayı geliştirdiğiniz
programlama dili ve teknolojilerle bu özelliği geliştirmeniz gerekmektedir.
Eğer diliniz bu iş için pek elverişli bir dil değilse, hem geliştirme eforumuz artacak hem de belki ciddi performans sorunları yaşayacağız.
Bu işlemi yapan bağımsız bir servis geliştirelim, ve bu servisi istediğimiz farklı bir dille (go, python vs..) geliştirerek hem performans kazanımı elde edelim hem de teknoloji stack’ imizi genişletelim diye düşünmeye başladıysak, Mikroservis Mimari bizim için uygun olabilir.
Yüksek Ölçeklenebilirlik
Uygulamamızda bir t anında özellikle bir modül için yapılan işlem arttıkça kaynak yetersizliği ortaya çıkar.
Bu durumda sunucumuzda kaynak arttırımı yapıp, uygulamanızı dikey ölçekleyebilir (bir yere kadar) veya bir kaç sunucu daha devreye alıp yatay ölçeklendirmeye gidebiliriz.
Peki bu çok iş yapılan modül dışındaki diğer modüllerde de bu ölçeklenme ihtiyacı söz konusu mu?
Cevabınız hayır ise, tek bir servisi dilediğinizce yatay/dikey ölçekleyebilme imkanını elde edeceğiniz Mikroservis Mimari’yi düşünebiliriz.
Kolay ve Hızlı Release Çıkabilme
“En ufak bir değişiklikte koca uygulamayı olduğu gibi deploy ediyoruz” tarzı cümleler kurmaya başladıysanız,
SERVİCE ESSENTIALS
Platform içerisinde bir çok servis çalıştırıyor olacağız. Her bir servis bir business capabilityi encapsule eder. Servislerimiz tek bir amaca hizmet edecek kadar küçük olmalıdır aynı şekilde diğer servislerle etkileşimi en aza indirecek kadar da büyük olacak şekilde oluşturmalıyız.
Independently Developed & Deployed( Bağımsız olarak Develop ve Deploy)
Her servis bağımsız olarak geliştirilmeli ve deploy edilebilir olmalıdır.
- Servisleri birlikte deploy etmeye ihtiyaç duyuyorsak birşeyleri yanlış yapıyoruz demektir.
- Tüm servisler için tek bir codebaseye sahipsek birşeyleri yanlış yapıyoruz demektir.
- Shared librarylere dikkat etmemiz gerekir. Shared libraryde yapılan değişiklikler, tüm servislerin aynı anda güncellenmesini gerektiriyorsa, servisler arasında sıkı bir bağ oluşturmuş oluruz.
Private Data Ownership
Birden fazla servis aynı veritabanındaki tabloyu doğrudan read/write yaptığında, bu tablolarda yapılacak herhangi bir değişiklik tüm bu veritabanını kullanan servislerin deployment edilmesini gerektirir. Bu durum servislerin bağımsızlığı ilkesine aykırıdır. Data storageyi servisler arasında paylaşmak servisleri bir birine bağımlı hale getirir. Her servisin kendine özel veritabanı olmalıdır.
Identifying Service Boundaries(Servis sınırlarını belirleme)
- Her servis, bir business capabilityi uygulayan autonomus(özerk) bir birim olmalıdır.
- Bir servis diğer servislerle gevşek bir şekilde birleştirilmelidir. Diğer servislere minimum seviyede bağımlı olmalıdır. Diğer servislerle olan iletişim API yada Eventler aracılığıyla olmalıdır.
- Servisler high cohesiona sahip olmalıdır. Yakından ilişkili functionalityler bir arada kalmalıdır. Böylece servisler arası iletişimi en aza indirebiliriz.
Bir modülün/sınıfın sorumluluklarının netliğini ifade eder. Dolayısıyla Cohesion modülün nasıl tasarlandığına odaklanır.
Modülümüz sadece bir görevi yerine getiriyorsa ve başka hiç birşey yapmıyorsa ve net bir amacı varsa modülün cohesionu yüksektir.
Single Responsibility Prensibi son derece yüksek Cohesionlu bir sınıf oluşturmayı amaçlar.,
Cohesionun Faydaları
- Azaltılmış modül karmaşıklığı(daha basit, daha az operasyona sahip)
- Domaindeki mantıksal değişiklikler daha az modülü etkilediği için ve bir modüldeki değişiklikler
diğer modullerde daha az değişiklik gerektirdiği için artan sistem sürdürülebilirliği sağlar.
- Arttırılmış Modül Yeniden Kullanılabilirliği
Stateless Service Instances(Durum bilgisi olmayan servisler)
Stateless bir servisin intancesi, previous requestle ilgili hiç bir bilgiyi saklamaz. Gelen request, servisin herhangi bir instancesine gönderilebilir. Bunun en büyük faydası servisi çok kolay bir şekilde bir load balancer arkasına koyabiliriz ve request hacmi arttıkça sevisin yeni bir instancesini oluşturarak çok kolay bir şekilde ölçeklendirme yapabiliriz.
Eventual Consistency(Nihai Tutarlılık)
Dağıtık sistemlerde tutarlılığı sağlamak çok zordur. Bununla savaşmak yerine, dağıtılmış sistemler için daha iyi bir yaklaşım olan nihai olarak tutarlılığı sağlamaktır. Nihai olarak tutarlı sistemlerde, servisler herhangi bir zamanda farklı bir görünüşe sahip olsalar bile, en sonunda tutarlı olacaktırlar.
Servislerimizi loose coupling & high cohesion olarak modellediğimiz zaman doğal olarak kendiliğinden asenkron iletişim kurma eğiliminde olacaktırlar. Böyle bir durumda bir çok kullanım durumu için nihai tutarlık bizim için uygun bir çözüm olacaktır.
Örn. Biletleme servisi ve rapor servisi olmak üzere iki adet servisimiz olduğunu düşünelim. Rapolama servisi biletleme servisi tarafından asenkron olarak bilgileri alır. Biletleme servisinde bir güncelleme olduğunda, raporlama servisi bu güncellemeyi bir kaç saniye sonra alırsa bu bir kaç saniye boyunca iki servis farklı datalara sahip olmuş olur. Raporlama durumları için bu birkaç saniyelik gecikme kabul edilebilir.
Documentation
Bir servis dökümanı ne kadar iyiyse kendisi de o kadar iyidir. Her servisin net ve kolay anlaşılır kullanım dökümanı olmalıdır. İdeal olarak tüm servislerin dökümanları ortak bir yerde olmalıdır. Servis ekipleri, kullandıkları servislerin dökümanlarına kolayca erişebilmelidir.
Bir API endpointi değiştiğinde bu servise bağımlı APIlerin ownerına bildirim gitmesi gerekir.
Load Balancer
Stateless servislerin avantajlarından biriside servislerin rahatça birden fazla örneğini oluşturabilmemizdir. Herhangi bir instance herhangi bir requesti handle edebilir. Bu durum servisleri ölçeklendirmeye yardımcı olmak için kullanılan load balancerlar için iyi bir uyum sağlar. Geleneksel load balancerlarda Client sadece targeti bilir. Target arkasında bulunan birden fazla instance arasında uygun algoritmalara göre requestleri paylaştırır.
SERVİCE INTERACTION
Micro servis mimarisi, küçük ve tek bir işe focus olmuş birden çok servisin birbiriyle iletişim kurduğu bir mimariye teşvik eder. Burada bazı sorular ortaya çıkıyor.
- Servisler bir birini nasıl bulmalı ?
- Tüm servisler ortak bir protokolde mi konuşuyor ?
- Bir servis diğeriyle iletişim kuramadığında ne olur ?
Communication Protokols
Mimari içerisindeki servis sayısı arttıkça bu servisler arasında standartlaştırılmış iletişim yöntemlerine sahip olmak kritik hale gelir. Servislerin hepsi aynı programlama dili ile yazılması gerekmediğinden, seçilen protokol programlama dili ve platformdan bağımsız olmalıdır. Ayrıca hem senkron hemde asenkron iletişimi hesaba katmamız gerekecektir.
1- Senkron İletişim
Request/Response-based Communication Mechanisms (HTTP-Based REST, gRPC)
Client servisten yanıt dönene kadar bekler ve requeste cevap dönene kadar Clientın yaptığı işlem bloklanır.
- REST, İnternet üzerinden bilgisayarlar arasında en yaygın iletişim standardıdır. Her HTTP request’inde yapılması istenilen işlemin HTTP Method’larıyla (Verb) ifade edilmelidir.(get, post, put, patch, delete). RESTful servisler’in birçok farklı response tipi olabilir. Bugünlerde popüler olarak JSON kullanılıyor fakat XML, CSV veya amaca bağlı olarak HMTL bile kullanılabilir. REST standardını izleyen bir API, RESTful API olarak adlandırılır.
- Bir resource almak için bir request göndeririz ve bu requeste karşılık istek yapılan yerden bize resource döner.
- HTTP senkron iletişim için iyi bir seçimdir. HTTP clients tüm programlama dillerinde mevcuttur. Built-in olarak caching, persistent connection, compression, authentication ve encryption bulunmaktadır. Http'nin olumsuz yanı, plaint text headerları tekrar tekrar gönderilmesi ve connectionların tekrar tekrar kurulup bozulmasıdır. http/2 sıkıştırılmış headerlar kullanarak ve kalıcı connectionlar üzerinden requestleri çoklayarak bu sorunu çözer.
- Birden fazla resourceye ihtiyacımız olursa ne olacak ? Bu durum genelde microsevisler arası iletişimde REST mimariyi kullandığımızda dezavantaj olarak ortaya çıkar.REST ile http isteği attığımızda resource api çalışmadığı zaman o request boşa gider. Kaybolur. Clientlar resourceye ait URLleri bilmek zorundadır.
gRPC
- gRPC, RPC(Remote Procedure Call) nin bir alt kolu gibi düşünebiliriz. A noktası ile B noktası arasında bir iletişim varsa bir request gönderiyoruz demektir. Bu iki nokta arasında haberleşme olabilmesi için aralarında ortak bir dil olmak zorundadır ve aynı zamanda bir mesajllaşma formatı olmalıdır. Default olarak gRPC'de HTTP/2 kullanılır. Message formatı olarakda Protol Bufferı kullanılır. Buda bir binary formattır. Ayrıca gRPC de tagleme formatı vardır. Bu tagleme formatıda şu anlama geliyor. Proto dosyasının içine baktığımızda fieldın adını, numarasını ve veri tipini falan anında görebiliyoruz. Buradaki fieldları bir, iki vs. diyerek tagliyoruz. Bu tagleme işleminin de mantığı veri kablo üzerinde giderken bize yardımcı oluyor
Yukarıdaki REST ve gRPC gibi http requesti atılan bir resourse api servisi down olduğu durumda bu request kaybolur. Bu tarz durumlarla başa çıkabilmek için microservis mimarimizin resiliency(Dayanıklılık, Esneklik) yüksek olmalıdır.
Retry Pattern: Bir request başarısız olduğunda bu requestin kaybolup gitmesi yerine aynı işlemi ilgili birime tekrar göndermektir. Bu yöntemi requesti alacak servisin kısa süreli down olacağını düşündüğümüz senaryolarda kullanmalıyız
Circuit Breaker Pattern:Başarısız http request sayısı daha önceden belirlediğimiz hata sayısı değerini aştığında belirlediğimiz süre boyunca request atılan servise istek atılmasını önlemiş olacağız.
2- Asenkron İletişim
Asenkron iletişim için publish-subscribe patterni implemente etmeliyiz. Bunun için iki major yaklaşım bulunmaktadır.
- Message Broker:Tüm servisler eventleri brokera pushlar. Servisler ihtiyaç duydukları eventlere subscribe olurlar. Bu senaryoda message broker kendi trasnport protokolünü tanımlar. Merkezi bir durumda olan broker single point of failureye sebep olabileceğinden dolayı böyle bir sistemin fault tolerantı yüksek ve yatayda ölçeklenebilir olması gerekir.
- Servisler Tarafından Sağlanan Webhooklar: Webhook farklı uygulamaların birbirleri ile entegrasyon sağlayabilmeleri için uygulama içerisinde oluşan event’leri HTTP üzerinden JSON Payload’ları ile kendilerine subscribe olan diğer uygulamaları tetiklemesidir.
Definition Of Unhealthy Service
Otomatize edilmiş monitoring ve alert sistemi kurarken bir servisin unhealthy olmasının ne anlama geldiğini belirlememiz gerekmektedir.
Http protokolünü örnek alırsak, Bir servisin 200, 300 ve 400 http status kodlarını oluşturması beklenen bir durumdur. Herhangi bir 500 error kodu veya timeoutun bir servis hatası olduğu sonucu çıkartabiliriz.
API Desing
İyi tasarlanmış bir API'nin kullanımı ve anlaşılması kolaydır. Ayrıca üzerinde herhangi bir değişiklik yaptığımız zaman mevcut clientları minumum derece etkilemelidir.Rest
Service Discovery
Bir çok servisin bir biriyle etkileşimde bulunduğu bir sistemde servislerin birbirini bulması için sabit IP tanımlamak uygun bir çözüm olmayacaktır.
Servis Registery
Available servisleri ve bu servislerin network locationlarını tutar. Tüm servislere ait bilgileri tek bir yerde tutmak single point failure neden olabileceği için fault tolerantı yüksek olmalıdır.
Servisleri servis registerye kaydetmenin iki yolu vardır
- Self registration: Bir servis startup esnasında kendisi register edebilir ve belli aralıklarla ayakta olduğu belirtmek için servis registerye sinyaller gönderebilir.
- External monitored: External bir servis, servis durumlarını izler ve servis registeryi günceller.
Discovery and Load Balancing
Servisler birbirilerini dinamik olarak discovery edebilmelidirler. Bunu yapmanın iki yolu vardır.
- Smart servers: Load balancer registeryden aldığı bilgilerle tüm trafiğin giriş noktası olur. Client requesti load balancera gönderir.
- Smart clients: Client servis registery aracılığıyla servislerin listesini tutar ve hangisine bağlanacağına kendi karar verir. Bu yaklaşımd artık load balancera ihtiyaç yoktur.
Çoğu cloud platformda servis discovery setup etmenin kolay bir yolu, her servis için load balancera point eden bir DNS girişi kullanmaktır. Load balancerların listesi servis registery listemiz olur ve DNS lookup da servis discovery mekanizmamız olur. Unhealthy instanceler load balancer tarafından otomatik olarak kaldırılır ve tekrar healthy olduklarına tekrar dahil edilirler.
Decentralized Interactions
Birden fazla servisin birlikte kordine edilmesi gereken complex work flowlarını (iş akışı) implemente etmek için iki ana yaklaşım vardır.
- Centralized Interaction: Bir process daha büyük workflowları tamamlamak için birden fazla servisle koordine olur. Orchestrator, karmaşıklılarla ilgilenir. Servislerin işlerini tamamlama sıralaması veya bir request fail olursa retrying edilmesi vb. Orchestratorun herşeyden haberdar olabilmesi için iletişim genellikle senkrondur. Bir orchestrator ile ilgili en büyük zorluk bussinesin logic'in merkezi bir yerde geliştirilmesidir.
- Decentralized Interaction: Her servis work flow içinde kendine düşen sorumluğu üstlenir. Diğer servislerden gelen eventler dinlenecek, işini en kısa sürede bitirecek, bir arıza olursa retrying edecek ve kendi işi tamamlandığında event pushlayacak. Burada iletişim asenkron olma eğilimindedir ve business logic ilgili servislerde kalır. Bu yaklaşımın zorluğu bir bütün olarak work flowun ilerlemesini izlemektir.
Decentralized interactionlar gereksinimlerimizi daha iyi karşılar. Loose coupling ve high cohesiona sahip servislerimiz olur. Ayrıca her sevisin bounded contextleri belirlidir.
Versioning
Değişim kaçınılmazdır. Önemli olan değişim ne kadar iyi yönetildiğidir. API'mizi versionlamak ve aynı anda birden çok versionu desteklemek servisimizi kullanan diğer servis ekipleri üzerindeki etkiyi en aza indirmeye yardımcı olur. Böylece diğer ekipler kendilerini hazır hissetiklerinde yeni versiona geçmek için zaman kazanmış olurlar.
Eski verisonları süresiz olarak maintain etmek zordur. Onun için eski sürümleri makul süreler sonunda desteklemeyi bırakmalıyız.
Limiting Everything
Consumerların request limitleri yönetilmelidir. Clientlar Aşırı request atarak sistemlerin yavaşlamasına sebep olmamalıdırlar.
Short Timeouts
Bir servisin aşırı request aldığını ve yavaşlağını düşünelim. Aynı şekilde bu servisi çağıran tüm servisler yavaşlar. Bu yavaşlık yukarı doğru yani user interfaceye doğru ilerler ve user intercelerde yavaşlamaya başlar. Buna ek olarak user da sayfayı donduğunu gördüğü için daha çok tıklamaya başlar ve sistem iyice durur vaziyete gelir.
Yukarı doğru akış olduğu için bir çok servis bu durumdan etkilenir ve hata kaynağını bulmak iyice zorlaşır. Bunu çözmek için timeout sürelerini kısa tutabiliriz. Örneğin bir servisin 10-50 milisaniyede içinde yanıtvermesini bekliyorsak timeout süreside en fazla 500 milisaniye olabilir. 30 saniye gibi yüksek değerler vermememiz gerekir.
Circuit Breakers
Down olmuş bir API ile iletişim kurmaya çalışmanın bir maliyeti vardır. Client bir request yapmak istediğinde kendi kaynaklarını kullanır, network kaynaklarını kullanır ve target taraftaki kaynakları kullanır. Resiliency
Correlation IDs
Birden fazla servisin birbiriyle etkileşim kurduğu senaryolarda bir requestin başlangıç noktasında bir correlationId ye sahip olması ve tüm servislerin kendi aralarında bu correlationIdyi gezdirmesi requesti izlemeyi kolaylaştıracaktır.
Maintainig Distributed Consistecy
Eventually consistent sistemlerde Veritabanınız ve event akışınız muhtemelen iki farklı sistemdir, bu da her iki sisteme atomik olarak yazmayı son derece zorlaştırır ve bu nedenle nihai tutarlılığı garanti etmeyi zorlaştırır.
Tutarsızlığa izin verip daha sonra düzeltmek
Dağıtılmış bir sistemde tutarlılık çok zordur. Dağıtılmış tutarlılığın temel bir özellik olduğu veritabanı sistemleri bile bunu doğru yapmakta zorlanıyor. Zorlu bir mücadele vermek yerine, tutarsızlıkları olaydan sonra tespit eden ve düzelten bir süreçle birleştirilmiş en iyi eforlu senkronizasyon çözümüne sahip olabilirsiniz.
Her veri parçasının tek bir doğruluk kaynağı olmalıdır
Bazı verileri birden çok serviste çoğaltmanız gerekse bile, bir servis her zaman herhangi bir veri parçası için tek gerçek kaynak olmalıdır. Tüm güncellemeler gerçeğin kaynağından geçmelidir. Bu aynı zamanda gelecekte tutarlılık doğrulamasının yapılabileceği kaynak haline gelir.
Bazı servislerin güçlü bir şekilde tutarlı olmasına ihtiyacımız olursa ne yapmalıyız?
Eğerki iki servis için tutarlılık çok önemliyse öncelikle, servislerin bounded contextlerini tekrar gözden geçirmeliyiz. Servislerin son derece tutarlı olması gerekiyorsa bu servisleri tek bir servis olarak bir araya getirmek mantıklı bir çözüm olacaktır.
Communicate Only Via Exposed APIs
Doğrudan başka bir servisin veritabanıyla konuşan bir servisiniz olduğunu fark ederseniz, çok yanlış bir şey yapıyorsunuz demektir. Hiç bir servis direkt olarak başka bir servisin veritabanına erişmemelidir. Event yada http call ile iletişime geçmelidir.
Client Libraries
Yönetilmesi gereken ortak bir çok şey vardır. Örn. Discovery, authentication, circuit breaking, connection pools ve timeouts. Her ekibin bunları sıfırdan yeniden yazması yerine, makul varsayılanlara sahip bir client libraryde package haline getirlmesi gerekir.
Client libraryler servislere özgü herhangi bir bussines logic içermemelidir.
Hiç yorum yok:
Yorum Gönder