Async Messaging Pattern

Semih Şahan
9 min readApr 2, 2023

--

Merhabalar, bugün sizlere asenkron ve senkron mesajlaşmanın avantaj ve dezavantajlarından, Outbox pattern, circuit breaker pattern ve idempotency’den bahsederken aynı zamanda neden, nasıl ve niçin sorularına yanıt arayacağız, bazen net bir cevabı olmadığını, ihtiyaçlar, durum ve şartlara göre seçimlerin değiştiğini göreceğiz, hadi başlayalım, iyi okumalar dilerim…

  • Asenkron mesajlaşma yönteminde client tarafını, mesajın hemen gelmeyeceğini düşünerek yazmalıyız ve senkron mesajlaşmada olduğu gibi client servera request’de bulunduktan sonra belli bir süre response bekleyip response gelmezse timeout’a düşme gibi bir durum gözlenmez, yani server, client’i blocklamaz

Biraz da terminoloji :)

  • Command
  • Bir RPC(Remote Procedure Call) isteğinin eşdeğeri olan bir mesaj. Çağrılacak işlemi ve parametrelerini belirtiriz.
  • Event
  • Sender/Publisher da dikkate değer bir şeyin meydana geldiğini belirten bir mesaj. Bir event genellikle, Order veya Customer gibi bir domain nesnesinin state değişikliğini temsil eden bir domain eventidir.

2 tür channel vardır, bunlar şu şekilde;

  • point-to-point
  • point-to-point chanell, kanaldan okuyan consumer’lardan tam olarak birine bir mesaj iletilir, yani bir mesaj birden çok consumera iletilmez. Örneğin, bir command message genellikle point-to-point bir chanell üzerinden gönderilir.
  • Publish-subscribe
  • Bir ileti ilgili chanella subscribe olan birden çok consumera gönderilir. Örneğin, bir event mesajı genellikle bir publish-subscribe kanalı üzerinden gönderilir.

One-Way-Notifications

  • Server, Client’in yolladığı command’leri işler ancak Client’e response dönmez

Pub/Sub

  • Client, birden çok serverın consume ettiği bir chanell’a event send eder ve birden çok server bu event’i handle eder

Publish/Asynch

  • Pub/Sub ile Request/Response(Senkron messaging) interaction style’ın birleşimi olan ileri seviye bir modeldir.
  • Yukarıdaki görselde gözüktüğü üzere Client Requst Channel’a Command yollar Command içinde CorrelationId’de yollar Service Request Channel’dan Command’ı okur ve işler, sonucunda Reply Channel’a response döner ama response içinde corellationId’de verilir böylece client Reply Channel’dan değerleri okurken ilgili item hangi requestin response’u olduğunu anlayabilir
  • Ayrıca Client Request Channel’a command yollarken command içinde Reply Channel’da belirtebilir, yani topic ismini belirtir, Server’da ilgili reply channel(topic)’e response döner(bu iki maddeyi okurken devamlı yukarıdaki görsele bakarsanız daha anlaşılır olacaktır)
  • Service’lerimizi brokersız şekilde de haberleştirebiliriz ancak brokerlı haberleştirme yöntemi tavsiye ediliyor

Brokerless Avantajlar

  • Mesajlar önce brokera sonra servera gitmesi yerine direkt servera gider böylece daha az mesaj gecikmesi olur, mesajlar daha hızlı gider
  • Kurulumu ve bakımı gereken bir broker olmadığından daha az operasyonel karmaşıklık oluşur
  • Broker’ın fazla yük almasından dolayı darboğaza girme ihtimali ortadan kalkar

Brokerless Dezavantajlar

  • Service’lerin birbirlerini bilmesi gerekir bu sebeple discovery service’e ihtiyaç duyulur
  • Mesajlaşma işleminin tamamlanması için service’lerin sürekli available olması gerekir ancak broker-based haberleşmede mesaj client’den brokera atılır server ne zaman available olursa o zaman mesajı alır işler
  • Mesaj iletiminin garanti edilmesi daha zordur

Broker-based avantajlar

  • Loose Coupling: Clientın mesajını göndermek için serverın network location bilgisine ihtiyacı yoktur, böylece service discovery’de ihtiyaç yoktur
  • Message buffering: Clientın gönderdiği mesajları broker ara belleğine alır ve server onları işleyebilecek duruma gelene kadar tutar
  • Flexible communication: Farklı interaction style’larını destekler

Broker-based dezavantajlar

  • Potential performance bottleneck: Message Broker’ın performans darboğazı olma riski vardır. Neyse ki, birçok modern message broker yüksek düzeyde ölçeklenebilir olacak şekilde tasarlanmıştır.
  • Additional operational complexity: Mesajlaşma sistemi, kurulması, yapılandırılması ve çalıştırılması gereken başka bir sistem bileşenidir yani birden çok broker ve onların konfigurasyonları, performans takibi vs.

Message Broker Teknolojisi Seçerken Bunlara Dikkat Edilmeli

  • Supported programming languages
  • Muhtemelen çeşitli programlama dillerini destekleyen birini seçmelisiniz.
  • Supported messaging standards
  • Message Broker, AMQP ve STOMP gibi herhangi bir standardı destekliyor mu, yoksa tescilli mi?
  • Messaging ordering
  • Message Broker mesajların sırasını koruyor mu?
  • Delivery guarantees
  • Broker ne tür teslimat garantileri veriyor?
  • Persistence
  • İletiler diskte kalıcı mı ve broker çökmelerinden kurtulabiliyor mu?
  • Durability
  • Bir consumer message broker’a yeniden bağlanırsa, bağlantısı kesilirken gönderilen tema mesajlarını alacak mı?
  • Scalability
  • Message Broker ne kadar ölçeklenebilir?
  • Latency
  • End-to-end latency değeri nedir ?
  • Competing consumers
  • Birden çok consumer eş zamanlı olarak birden çok iletiyi consume edebiliyor mu ?
  • Aynı anda birden çok işlem(Async) yapmak için Consumer sayısını artırırken mesajların işlenme sırasını bozmamamız gerekir
  • Mesela, aynı point-to-point kanaldan okuyan bir service’in üç instance’ının olduğunu ve producer’ın sırasıyla OrderCreated, OrderUpdated ve OrderCancelled event mesajlarını publish ettiğini hayal edin. Uygulama, her mesajı aynı anda farklı bir consumera iletebilir. Network sorunları veya Garbage Collector’lerden kaynaklanan gecikmeler nedeniyle, işlemler sıra dışı olarak işlenebilir ve bu da garip davranışlara neden olabilir. Mesela, bir service instance, başka bir service OrderCreated mesajını işlemeden önce OrderCancelled mesajını işleyebilir! Yani henüz order create edilmeden cancel edilmeye çalışılabilir.
  • Apache Kafka ve AWS Kinesis gibi modern Message Broker’lar tarafından kullanılan yaygın bir çözüm, shared(partitioned) kanalları kullanmaktır.

Shared Channel

  • Sharded Channel, her biri bir kanal gibi davranan iki veya daha fazla partition’dan oluşur.
  • Producer her bir mesajı birbirinden ayırmak için shard-key kullanır.
  • Message Broker, bir consumer’un birden çok instance’ını bir araya toplar ve onlara aynı logical reciever gibi davranır.
  • Mesela Apache Kafka, consumer group terimini kullanır. Message Broker, her partition’ı tek bir reciever’a atar. Consumer’lar açılıp kapandığında partition’ları yeniden send eder.
  • Yukarıdaki örnekte, her Order event message, shard-key olarak orderId’ye sahiptir.
  • Böylece orderId değeri aynı olan event partition’ları aynı instance’a yollanır ve o instance tarafından işlenmesi sağlanır böylece işlemlerin, işlenme sırasında bir yanlışlık oluşmaz.

Handling duplicate messages

  • Mesajlaşmayı kullanırken üstesinden gelmeniz gereken bir diğer zorluk da yinelenen mesajlarla uğraşmaktır. Bir message broker ideal olarak her mesajı yalnızca bir kez iletmelidir, ancak tam olarak bir kez mesajlaşmayı garanti etmek genellikle çok maliyetlidir. Bunun yerine, çoğu message broker, bir mesajı en az bir kez iletme sözü verir.
  • Clinet’in, network’ün veya message broker’ın başarısız olması, bir iletinin birden çok kez teslim edilmesine neden olabilir
  • Bu konuda birkaç farklı çözüm vardır

Idempotent çalışan message handler’lar yazmak

Message’ların izini sürüp duplicate olmuş message’ları silebiliriz, yani parallel’de sürekli bir senkronizasyon işlemi yapabiliriz

Writing Idempotent Message Handlers

  • Mesela bir sipariş oluşturma OrderCreate işleminiz olsun, bu işlemde bir clientId alırsanız ve uygulama logic’inizde db’de ilgili clientId’nin siparişi varsa tekrar oluşturma şeklinde bir kural koyarsanız POST işleminiz idempotent olur

Tracking Messages And Discarding Duplicates

  • Mesela kredi kartlarına yetkilendirme işlemi yapan bir message handlerımız olsun, aynı kartla birden çok kez istek atıldığında aynı karta birden fazla kez yetkilendirme işlemi yapmaması gerekir, yani bu message handlerı idempotent yapmamız gerekir bunun için her kartın uniq bir numarası olur, ayrı bir tablo oluşturup yetkilendirme işlemi yapılan kartları burada kaydedebiliriz böylece message handlerımızın başında eğer yetkilendirilmiş kartlar tablosunda bu kartın kaydı yoksa yetkilendirme yap filtresinden geçirebiliriz requesti böylece idempotent olur

Transactional Messaging

  • Service’ler veritabanı güncellemesi yaptıktan sonra domain event fırlatmalıdır, ona göre diğer service’ler tablolarını güncellemelidir
  • Veritabanı güncellemesi ve event fırlatma aynı transaction içerisinde olmalıdır, aksi takdirde service veritabanını güncelledikten sonra çöker ve event fırlatamaz böyle olunca da veritabanları arasında veri uyumsuzlukları olur

Outbox Pattern

  • Outbox patternin dezavantajlarından biri, message broker OUTBOX table’da göndermiş olduğu bir mesajı tekrar gönderebilir, mesela mesajı gönderir sonra bi anda kapanır ve göndermediğini sanarak tekrar gönderir, bu durumun önüne geçmek için consumer’ların idempotent çalışması gerekir

Publishing Events By Using The Pooling Publisher Pattern

  • Message Relay OUTBOX tablosunu belli aralıklarla kontrol eder, eğer message brokera publish edilmemiş bir message varsa publish eder ve publish ettikten sonra OUTBOX tablosundaki ilgili kaydı siler

Dezavantaj

  • Bu yöntem çok maliyetlidir, yani performansı düşürür, çünkü sürekli select ve delete sorgusu atmak sistem performansı açısından iyi değil ama bir NoSql veritabanı kullanıyorsak NoSql çözümünün select performansına göre bu işlem yapılabilir

Publishing Events By Applying The Transaction Log Tailing Pattern

  • Bu yöntemde ise message brokera message’ları publish edecek olan uygulama ilgili database’den OUTBOX tablosuna yapılan insert işlemlerinin transaction log’larına bakar ve böylece commit olmuş işlemleri alır ve onları message brokera publish eder böylece sürekli OUTBOX tablosuna select ve delete işlemleri yapmak zorunda kalmaz

Bu patterni uygulayan 3. party software’ler vardır;

  • Debezium: Database’deki değişiklikleri kafkaya publish eder
  • Linkedin Databus: Oracle Transaction Log’larına bakarak OUTBOX tablosundaki değişiklikleri event olarak message brokera publish eden bir uygulama
  • DynamoDB: Veritabanında yapılan create, update, delete işlemlerinin kaydını sıralı(sequential) şekilde DynamoDB tablosunda tutar böylece uygulamamızdan bu tabloyu okuyup message brokera event send edebiliriz
  • Eventuate Tram: MySql, Postgres veritabanlarında OUTBOX tablosunda yapılan değişiklikleri message brokerlara event olarak send etmemizi sağlayan bir yazılım

Synchronous Communication Reduces Availability

  • Servisler arası Synchronous iletişim sağlamak sistemin availability’sini düşürür
  • Servisler arası REST ile haberleşirsek synchronous iletişim kurmuş oluruz
  • Synchronous iletişimde request’den sonra response’u bekleriz, iş yapmadan boş boş beklenen bir süreden bahsediyoruz, hiçbir saniyesinde hiçbir şey yapmadan boş boş beklememesi lazım bilgisayarın, çünkü bilgisayarlar yorulan varlıklar değil bu sebeple kesintisiz çalışabilirler, onlara bu imkanı sağlamalıyız :)
  • Yukarıdaki diagramda CreateOrder isteğini işlemesi için tüm servislerin aynı anda kullanılabilir olması gerekir.
  • Synchronous çalışan bu sistemde 3 servisten herhangi biri çalışmıyorsa order oluşturulamaz
  • Bu sorun, REST tabanlı iletişime özgü değildir. Availability, bir service’in müşterisine yalnızca başka bir service’den yanıt aldıktan sonra yanıt verebildiği durumlarda azalır. Bu sorun sistem asyncronous çalışsa bile mevcuttur
  • Örneğin, bir message broker aracılığıyla ConsumerService’e bir mesaj gönderip ardından bir yanıt beklediyse, OrderService’in availability’si azalacaktır.
  • Availabilty’i en üst düzeye çıkarmak istiyorsanız, synchronous iletişim miktarını en aza indirmelisiniz.
  • Yukarıdaki diagramda hiçbir service response beklemez, response gelirse işler yoksa başka işlere bakar
  • Böyle bir mimari son derece esnek olacaktır, çünkü message broker mesajları tüketilinceye kadar arabelleğe alır.

Replica Data

  • Bazen her service asynchronous çalışamaz mesela public API’ler synchronous çalışmak zorundadır, bu durumda availability yükseltmek, service’lerin birbirlerini blocklamaması(beklememesi) için Replica Data yöntemi kullanılabilir
  • Bu yöntemde aşşağıdaki görselde de görüldüğü üzere OrderService Database’inde Restaurant Service ve Consumer Service’lerinden talep edilen Consumers ve Restaurants tablolarının kopyaları(replica) vardır böylece synchronous request geldiğinde direkt kendi database’inden gerekli verileri alabilir, replica verilerin güncel tutulması için Consumer ve Restaurant Service’lerin Order Service’e subscribe olup her değişiklikte replica dataları da güncellemeleri gerekir
  • Replica data miktarı çok fazla olursa bu yöntemi kullanmak iyi bir tercih olmayabilir
  • Bu yöntemde order oluşturulduktan sonra replica verilerin güncellenmesi gerektiğinde çözüm sunulmuyor, bunun içinde order oluşturulup client’e response dönüldükten sonra Consumer ve Restaurant Service’lerin database’lerindeki restaurants ve consumers tabloları güncellenir
  • Order oluşturma talebi geldikten sonra işlemler sırayla şu şekilde olmalıdır;
  1. Status değeri PENDING olan bir order kaydı oluşturulur ve kullanıcıya bu order’ın id değeri dönülür
  2. Order Service, Consumer Service’e ValidateConsumerInfo messajı yollar message broker aracılığıyla
  3. Order Service, Restaurant Service’e ValidateOrderDetails messajı yollar message broker aracılığıyla
  4. Consumer Service, ValidateConsumerInfo messajını aldıktan sonra ilgili Order’a ait Consumer Info bilgisini validate eder ve Order Service’e, ConsumerValidated mesajını yollar.
  5. Restaurant Service, ValidateOrderDetails messajını aldıktan sonra ilgili Order’a ait Menu Item bilgisini validate eder ve Order Service’e, OrderDetailValidated mesajını yollar.
  6. Order Service eğer önce Consumer Validated mesajını alırsa Order’ın Status kolonunu CONSUMER_VALIDATED, eğer önce OrderDetailsValidated mesajını alırsa Order’ın Status kolonunu ORDER_DETAILS_VALIDATED yapar
  • Bu yaklaşımın güzel yanı, örneğin ConsumerService çalışmıyor olsa bile, OrderService yine de Order oluşturur ve müşterilerine yanıt verir. Fault toleransı yüksektir bu yaklaşımın
  • Bu yöntemin dezavantajı ise client’ın order’ın oluştuğundan kesin olarak emin olamaması, yani ya client düzenli aralıklarla order’ın status’unun VALIDATED olup olmadığını kontrol etmeli ya da Order Service, Order’ın Status değeri VALIDATED olunca client’a bildirim yollamalı(sms, email vs.)

Özet

  • Service’ler arası iletişim mekanizması önemlidir
  • Service’lerin geriye dönük geliştirmelerini desteklemesi lazımdır. Bir service’in API’sinde önemli bir değişiklik yaparsanız, client’leri yükseltilene kadar genellikle hem eski hem de yeni sürümleri desteklemesi gerekir.
  • Service’ler arası asenkron iletişim mekanizması kullanılmalıdır
  • Circuit Breaker Pattern kesinlikle kullanılmalıdır
  • Eğer microservice sistemimiz içinde synchronous protocol kullanılıyorsa, discovery service ihtiyaç vardır, bunun için ya deploymen platform’unun sunduğu discovery service kullanılır ya da application level discovery service kullanılır. Kitabın tavsiye ettiği deployment platformun sunduğu discovery service kullanmak.
  • Service’ler arası asenkron mesajlaşmayı kullanırken önemli bir zorluk, veritabanını atomik olarak güncellemek ve bir mesaj publish etmektir. Bu zorluğun üstesinden gelmenin yolu Outbox Pattern’dir

Kaynak: https://microservices.io/book

--

--