Asenkron İletişimde Verimlilik: RabbitMQ ile Mikroservisler Arası Entegrasyon

Ceyda Tekin
SDTR
Published in
10 min readJun 20, 2024

Merhabalar,

Bugün sizlerle mikroservis mimarilerinin vazgeçilmez unsurlarından olan message broker’larından RabbitMQ’yu inceleyeceğiz. RabbitMQ, mesajları güvenli bir şekilde iletmek ve işlemek için kullanılan popüler bir açık kaynak mesaj broker yazılımıdır. İlk bakışta basit gibi görünen asenkron iletişim implementasyonları, yanlış mimariyle tasarlandığında büyük baş ağrılarına ve mesajların kara deliğe düşmüş gibi kaybolmasına yol açmaktadır. Gerçekte RabbitMQ, exchange, queue ve binding gibi kavramlar üzerine kuruludur ve doğru şekilde kullanıldığında güçlü bir iletişim aracıdır. Peki “RabbitMQ nasıl kullanılmalıdır?”, “temel olarak bileşenleri nedir?”, “en çok hangi hatalarla karşılaşılır?”, ”MassTransit nedir?” bunlardan bahsedeceğim bir yazı olacaktır.

RabbitMQ Nedir?

RabbitMQ, açık kaynaklı ve geniş bir kullanıcı kitlesine sahip olan bir mesajlaşma aracıdır. AMQP, MQTT, STOMP gibi çeşitli mesajlaşma protokollerini destekler. Mesajların hızlı bir şekilde alınması ve FIFO (First-In-First-Out) mantığıyla dağıtılması için özel olarak tasarlanmıştır. Ayrıca, sistemlerin ölçeklenebilir ve dağıtık bir yapıya sahip olmasını sağlayarak esneklik sağlar. Temel işlevi, iletilen mesajların bir kuyrukta depolanması ve bu kuyruktan sırayla alıcılara iletilmesidir. Ancak, benzer işlevlere sahip farklı uygulamalar da mevcuttur.

Neden RabbitMQ Kullanırız?

Günlük yaşantımızda bekleme gerektiren veya uzun süren işlemlerin sonuçlarını o anda almak istemeyebiliriz. Farklı işlerle uğraşırken o diğer iş biter ve sonucunu sonradan kullanabilmekteyiz. Her işimizde bu şekilde yaklaşırken neden kodlamada katı bir şekilde beklememesi gereken işlerde birbirini bekletiyoruz? Aşağıda bu beklemenin gerek olmadığı ve rabbitMq kullanımına bir örnek mevcuttur.

Bir haber sitesi, kullanıcıların farklı kategorilerdeki güncel haberlere erişimini sağlayan bir platformdur. Kullanıcılar, spor, teknoloji, ekonomi gibi çeşitli kategorileri takip ederek ilgilendikleri haberleri okuyabilirler.

Haber sitesi, kullanıcıların tercihlerine göre haberleri anlık olarak sunabilmek için RabbitMQ’yu kullanır. Her bir haberin yayınlanmasıyla birlikte, ilgili kategoride bir “Yeni Haber” olayı RabbitMQ’ya gönderilir.

Kullanıcılar, haber sitesinde tercih ettikleri kategorileri belirlerler. Bu tercihleri sistem, RabbitMQ’ya gönderir ve ilgili haber kategorilerini dinlemeye başlar.

Örneğin, bir kullanıcı spor haberlerini takip ederken aynı anda teknoloji haberlerini de görmek isteyebilir. Haber sitesi, RabbitMQ aracılığıyla gelen haberleri kategorilerine göre dinleyen servislere ilettikten sonra, kullanıcının isteklerine göre içerikleri birleştirebilir ve kullanıcıya tamamen özelleştirilmiş bir haber akışı sunabilir. Veya farklı kitlelere farklı haberlerin aynı zamanda ulaşmasını sağlayabilir.

Bu şekilde, haber sitesi RabbitMQ kullanarak haberleri hızlı ve etkili bir şekilde kullanıcılara ulaştırırken, kullanıcılar da tercih ettikleri kategorilerdeki haberlere anlık olarak erişebilirler.

Temel kavramları nelerdir?

  • Producer : Mesajları üreten ve RabbitMQ’ya ileten uygulama veya servis.
  • Exchange : Mesajları alıp uygun kuyruklara yönlendiren aracıdır.
  • Queue : Mesajların geçici olarak depolandığı, Consumer’ların beklediği ve işlediği kuyruk.
  • Consumer : RabbitMQ’dan gelen mesajları alarak işleyen uygulama veya servis.
  • Binding: Exchange ile Queue arasındaki bağlantıyı belirleyen ve iletişim kanalını sağlayan yapı.
  • Routing Key: Exchange tarafından kullanılarak bir mesajın hangi kuyruğa iletilmesi gerektiğini belirlemek için kullanılan değer veya alan.
  • Header Attributes: Mesajları başlık bilgilerine göre kuyruklara yönlendirmek için kullanılan ve Headers Exchange türü ile ilişkilendirilen öznitelikler.

Exchange Tipleri Nelerdir?

RabbitMQ’da kullanılan yaygın exchange tipleri şunlardır:

  1. Direct Exchange (Doğrudan Değişim): Routing key ile eşleşen kuyruklara mesajları yönlendirir. Mesajlar, direkt olarak belirli kuyruklara iletilir.
  2. Fanout Exchange (Yayın Değişimi): Mesajları bağlı tüm kuyruklara gönderir. Bu exchange tipi, mesajı alan tüm kuyruklara iletilmesi gereken durumlarda kullanılır.
  3. Topic Exchange (Konu Değişimi): Routing key’lerin pattern eşleşmesine göre kuyruklara mesajları yönlendirir. Özel routing pattern’leri kullanarak mesajları belirli kuyruklara yönlendirmek için kullanılır.
  4. Headers Exchange (Başlık Değişimi): Mesajların başlık özniteliklerine göre eşleşmesine dayalı olarak kuyruklara mesajları yönlendirir. Başlık öznitelikleriyle eşleşen kuyruklara mesajlar iletilir.
  5. Default Exchange: RabbitMQ’da bulunan ve direct exchange tipinde olan bir önceden tanımlı bir exchange’tir. Bu exchange, özel olarak adlandırılmamış ve kullanırken ismi boş geçilir. Genellikle, exchange tanımlamadan doğrudan kuyruğa mesaj göndermek istendiğinde kullanılır.

Tüm kuyruklar, kendi isimleriyle bu default exchange’e bağlıdır ve mesajların yönlendirilmesi için kuyruk adı routing key olarak kullanılır. Bu sayede, mesajların direkt olarak ilgili kuyruklara iletilmesi sağlanır. Okuma işlemi de aynı prensiple gerçekleşir; mesajları okurken, kuyruk adı routing key olarak kullanılarak exchange adı boş geçilerek mesajlar ilgili kuyruklardan alınır.

Bu exchange tipleri, farklı iletişim senaryoları için kullanılır ve mesajların nasıl işleneceğini belirler. Her bir exchange tipi, belirli bir işlevselliği temsil eder ve uygulamanın ihtiyaçlarına bağlı olarak seçilir.

Queue Tipleri Nelerdir?

  1. Classic Queue (Klasik Kuyruk): Temel kuyruk tipidir. Mesajları FIFO (First-In-First-Out) mantığıyla saklar ve Consumer’lar tarafından işlenir.
  2. Quorum: Bir kuyruktaki mesajların veri bütünlüğünü korumak için kullanılan bir kavramdır. Özellikle, bir kuyruğun birden fazla düğüm arasında dağıtıldığı durumlarda kullanılır ve mesajların birden fazla kopyası tutularak veri kaybını önler.
  3. Durable Queue (Dayanıklı Kuyruk): Bu kuyruk tipi, RabbitMQ sunucusunun yeniden başlatılması durumunda dahi mesajların kaybolmamasını sağlar. Mesajlar disk üzerinde saklanır.
  4. Transient Queue (Geçici Kuyruk): Bu kuyruk tipi, RabbitMQ sunucusu kapatıldığında kuyruk ve mesajlarının silinmesini sağlar. Yalnızca geçici bir süre için kullanılacak kuyruklar için uygundur.
  5. Priority Queue (Öncelikli Kuyruk): Mesajlara öncelik atamak için kullanılır. Öncelikli mesajlar önce işlenir.
  6. Lazy Queue (İşlenmemiş Kuyruk): Bu kuyruk tipi, mesajları disk üzerinde saklayarak RAM kullanımını azaltır ve büyük veri yüklerini daha etkili bir şekilde işler.
  7. Mirror Queue (Ayna Kuyruk): Yüksek erişilebilirlik sağlamak için kullanılır. Mesajlar, birden fazla düğümde kopyalanır ve böylece bir düğüm başarısız olsa bile mesajlar kaybolmaz.

RabbitMQ ve Mass Transit, kompleks iletişim sorunlarını çözmek için güçlü araçlardır.

Temel RabbitMQ bileşenlerinden sonra, .NET Core üzerinde RabbitMQ entegrasyonunu kolaylaştıran Mass Transit’e de değinmek isterim.

Mass Transit Nedir?

MassTransit, .NET platformu için geliştirilmiş açık kaynaklı bir message bus’tur. Chris Patterson tarafından geliştirilen bu araç, dağıtık sistemlerde problemleri çözmeyi hedefler ve message-based iletişimi kolaylaştırır. MassTransit, çeşitli messaging yapılarını destekler, bunlar arasında RabbitMQ, Amazon SQS, Azure Service Bus ve ActiveMQ gibi popüler sistemler bulunur.

Bu kütüphane, .NET tabanlı uygulamalar arasında güvenilir ve etkili iletişim sağlamak için kullanılır. Mesajlar, class veya interface tipinde olmalıdır ve içerisinde method barındırmamalıdır. MassTransit, Publish ve Send olmak üzere iki farklı message type sağlar. Event yayınlamak için Publish, Command göndermek için ise Send kullanılır.

Varsayılan olarak, MassTransit Fanout Exchange’i kullanır. Bu, mesajları tüm bağlı kuyruklara iletmek için kullanılan bir exchange tipidir.

MassTransit’in esnekliği ve kullanım kolaylığı sayesinde, dağıtık sistemlerde message-based iletişim ihtiyaçlarını karşılamak için tercih edilir.

Masstransit tarafından bize sağlanan hizmetler aşağıdaki gibidir:

  1. Hata Yönetimi (Error Handling): Hata yönetimi, bir uygulamanın çalışması sırasında oluşabilecek hataları ele almak ve bunlara uygun şekilde yanıt vermek anlamına gelir. MassTransit, hata durumlarını ele almak için çeşitli mekanizmalar sunar. Örneğin, hata durumlarında otomatik olarak yeniden deneme yapılabilir veya hata işleme stratejileri belirlenebilir.
  2. Yeniden Deneme (Retry): Yeniden deneme, bir işlemin başarısız olması durumunda otomatik olarak tekrar deneme yapılmasını sağlayan bir mekanizmadır. MassTransit, yeniden deneme stratejilerini yapılandırmak ve uygulamak için esnek bir API sağlar. Bu sayede, geçici hata durumlarında otomatik olarak işlemlerin tekrar denenebilmesi sağlanır.
  3. Bekleme (Backoff): Bekleme, hata durumlarında tekrar deneme yapmadan önce belirli bir süre beklemeyi sağlayan bir stratejidir. MassTransit, belirli bir hata durumunda beklemenin ne kadar süre yapılacağını ve beklemenin nasıl artırılacağını yapılandırmak için çeşitli seçenekler sunar. Bu, sistemdeki yükü azaltmak ve hata durumlarının daha iyi ele alınmasını sağlar.
  4. İşlem Yönetimi (Transaction Management): İşlem yönetimi, bir dizi işlemi bir araya getirerek atomik bir şekilde gerçekleştirmeyi ve başarılı bir şekilde tamamlanması veya başarısız olması durumunda geri alınmasını sağlayan bir mekanizmadır. MassTransit, işlem yönetimini destekler ve mesajların işlenmesi sırasında işlem sınırlarını tanımlamanıza ve işlem başarısız olursa geri alınmasını sağlamanıza olanak tanır.

Bu özellikler, MassTransit’in hata durumlarını ele almak ve uygulamanın güvenilirliğini artırmak için sunduğu mekanizmalardır. Bu sayede, dağıtık sistemlerde hata toleransını artırabilir ve sistemlerin daha sağlam olmasını sağlayabilirsiniz.

Event Ve Command Nedir?

Bu konu, ilk duyduğum andan itibaren en çok karıştırdığım ayrım olmuştur. Bu sebeple ayrı bir başlık altında incelemek istedim.

  • Event (Olay): Event, bir sistemin belirli bir durumu temsil eden ve diğer bileşenlerin bu durumu takip etmesini sağlayan bir bildirimdir. Örneğin, bir kullanıcı hesap oluşturduğunda “Kullanıcı Oluşturuldu” gibi bir event yayınlanabilir. Bu event, diğer bileşenlere kullanıcı oluşturulduğu bilgisini ileterek ilgili işlemlerin yapılmasını tetikleyebilir. Genelde geçmiş zamanı belirten isimler kullanılmaktadır. Send metodu, mesajları doğrudan belirli bir kuyruk (queue) üzerinden gönderir. Bu metod kullanılarak bir mesaj gönderildiğinde, mesaj doğrudan bir queue'ya iletilir ve genellikle direct exchange kullanılır.
public class UserCreatedEvent
{
public int UserId { get; set; }
public string UserName { get; set; }
}
var userCreatedEvent = new UserCreatedEvent { UserId = 1, UserName = "name" };
bus.Publish(userCreatedEvent);
  • Command (Komut): Command, sisteme bir eylem gerçekleştirmesi için talimat veren bir istektir. Diğer bileşenler, bu komutu alarak belirli bir eylemi gerçekleştirir. Örneğin, bir kullanıcının hesabını silmek için “Kullanıcı Sil” gibi bir komut gönderilebilir. Bu komut, kullanıcı hesabının silinmesi işlemini tetikler. Publish metodu, mesajları topic exchange üzerinden yayınlar. Bu metod kullanılarak bir mesaj gönderildiğinde, mesaj ilgili exchange'e yönlendirilir ve exchange bu mesajı uygun routing key ile eşleşen tüm kuyruklara iletir.
public class DeleteUserCommand
{
public int UserId { get; set; }
}
var deleteUserCommand = new DeleteUserCommand { UserId = 1 };
bus.Send(deleteUserCommand);

Eğer aynı mesajı birden fazla servis işleyecekse, genellikle event tipi kullanılır. Ancak, aynı servisin birden fazla instance olsa bile yine de command kullanılabilir. Örneğin, bir servisinde üç farklı konteynerda üç farklı örneği ayağa kaldırabilirsiniz. Kuyrukta ise, ilgili mesajlar varsa, bu mesajlar birer birer işlenir. Aynı mesaj iki kez işlenmez, böylece mesaj sadece bir kere işlenir. Bu yaklaşım, özellikle ödeme gibi hassas işlemlerle ilgili mesajlar gönderirken, istenmeyen durumları önlemek ve bir olayın yalnızca bir kez işlenmesini sağlamak açısından önemlidir.

Sık kullanılan patternler nelerdir?

Publish/Subscribe (Yayın/Abone): Bu desende, bir yayıncı (publisher) mesajları bir veya daha fazla aboneye (subscriber) yayınlar. Yayıncı, mesajları bir değişim (exchange) üzerine yayınlar ve aboneler ilgili kuyruklara bağlanarak bu mesajları alırlar. Bu desen, olay tabanlı (event-driven) sistemlerde sıkça kullanılır.

Günlük yaşam örneği: bir gazete yayıncısı (publisher) ve okuyucular (subscribers) arasındaki ilişkiyi düşünebiliriz. Gazete yayıncısı her gün yeni bir gazeteyi piyasaya sürer ve bu gazeteyi farklı abonelere dağıtır. Aboneler, ilgilendikleri konuları içeren gazeteleri alarak okurlar. Bu durumda, gazete yayıncısı bir yayın (exchange) üzerinden gazeteleri dağıtır ve aboneler (okuyucular) ilgili gazeteleri alır.

Work Queues (İş Kuyrukları): Bu desende, görevlerin kuyruklara eklenip ardından çalışan işçiler (workers) tarafından işlenmesi sağlanır. Bir veya daha fazla üretici (producer) görevleri bir kuyruğa ekler ve bir veya daha fazla işçi bu kuyruktan görevleri alarak işler. Bu desen, yüksek iş yüklerini dağıtmak ve işleri paralel olarak işlemek için kullanılır.

Günlük yaşam örneği : Bir paket dağıtım şirketi, farklı kuryelerin (işçiler) farklı paketleri teslim ettiği bir sistemde çalışabilir. Paketler, kuyruğa eklenir ve kuryeler bu kuyruktan paketleri alarak teslim ederler. Mesela, bir kurye bir paketi alır, teslim eder, ardından bir sonraki paketi almak için kuyruğa geri döner. Bu durumda, paketler bir iş kuyruğunda (queue) saklanır ve kuryeler (işçiler) bu kuyruktan işleri alır.

Request/Response (İstek/Cevap): Bu desende, bir istemci (client) bir istek mesajı gönderir ve bu isteği işleyen bir sunucu (server) tarafından bir cevap mesajı ile yanıtlanır. İstemci, istek mesajını bir kuyruğa gönderir ve sunucu bu kuyruktan isteği alarak işler ve cevap mesajını geri döner. Bu desen, senkronize işlemler ve RPC (Remote Procedure Call) gibi senaryolar için kullanılır.

Günlük yaşam örneği: Bir restoran sipariş sistemi, müşterilerin siparişlerini alır ve mutfak personeline ileterek hazırlanmasını sağlar. Müşteri bir sipariş verir (istek), restoran siparişi alır, mutfak personeline iletir ve mutfak personeli sipariği hazırlar ve müşteriye teslim eder (cevap). Bu durumda, siparişler bir kuyruğa eklenir ve restoran personeli bu kuyruktan siparişleri alır ve işler.

Dead Letter Queue (Ölü Mektup Kuyruğu): Bu desende, işlenemeyen veya hatalı mesajlar özel bir kuyruğa yönlendirilir. Bu kuyruk, işlenemeyen mesajların toplandığı ve incelendiği bir yerdir. İncelenen mesajlar daha sonra tekrar işlenmek üzere geri alınabilir veya uygun aksiyonlar alınabilir. Bu desen, hatalı durumların tespit edilmesi ve düzeltilmesi için kullanılır.

Hedeflenen Kuyruk veya Değişim Yoksa: Bir mesaj, hedeflendiği kuyruk veya değişim bulunamadığında dead letter kuyruğuna yönlendirilebilir. Bu durumda, mesajlar normal işleme akışına dahil edilemez ve işlenemezler. Dead letter kuyruğu, bu tür durumlarda bu mesajların toplanması için kullanılabilir. Mesaj İşlenirken Hata Oluştuğunda: Bir mesaj, işlenirken beklenmeyen bir hata meydana geldiğinde dead letter kuyruğuna yönlendirilebilir.

Örneğin, mesajın içeriği hatalı olabilir veya mesajı işleyen serviste bir yazılım hatası oluşabilir. Bu durumlarda, hatalı mesajların dead letter kuyruğuna yönlendirilerek incelenmesi ve sorunun giderilmesi sağlanabilir.

  • Mesaj Süresi Aşıldığında: Belirli bir zaman aralığında işlenmeyen veya kuyruğa alınan mesajlar, süre aşıldığında dead letter kuyruğuna yönlendirilebilir. Bu durumda, mesajlar normal işleme akışından çıkarılır ve incelenmek üzere dead letter kuyruğuna gönderilir.
  • Mesajın İçeriği Doğrulanamadığında: Bazı durumlarda, gelen mesajın içeriği doğrulanamaz veya geçerli değildir. Örneğin, bir JSON mesajı beklenirken gelen mesaj farklı bir formatta olabilir. Bu durumda, geçersiz mesajlar dead letter kuyruğuna yönlendirilebilir ve analiz edilerek sorunun kaynağı tespit edilebilir.

Günlük yaşam örneği : Bir postane, dağıtım sırasında teslim edilemeyen mektupları özel bir kuyruğa yönlendirir. Bu kuyruk, dağıtıma çıkarılan ancak teslim edilemeyen mektupları toplar. Posta görevlileri, bu kuyruktaki mektupları inceler ve nedeninin belirlenmesi için gerekli adımları atarlar. Örneğin, yanlış adres yazılmış bir mektup ise, geri gönderilir veya adres güncellemesi yapılır.

Rabbit Üzerindeki Kullanımlar ve Alınan Hatalar

Mevcut iş yerimde IoT cihazları içeren bir mikroservis projesi üzerine çalışıyorum. RabbitMQ projemizde IoT cihazlarından gelen komutlarla kayıtlar tutmak veya gerekli aksiyonları almak için veya servisler arasında iletişim için kullanılmıştır. Projelerde rabbitMQ ayarı düz bir şekilde değil MassTransit üzerinden yapılmıştır. Bu durum bize hata yönetimi ve retry mekanizmalarını configuration ile yapılandırmasını sağlamaktadır.

Gözlemlediğim aktif hatalar şunlardır:

  • Cihaz bazlı bir consumer eklenmesi gerektiğinde, yeni bir cihazda consumer’ların oluşmaması veya yeni kurulan mesaj kuyruklarında doğru yapılandırılma olmaması yüzünden consumerların çoklu oluşmaması.
  • Cihaz silindiğinde veya yanlış bir kuyruk eklendiğinde, bu işlem otomatik olarak ona bağlı exchange’i de oluşturur ve kullanılmayan özelliklerde exchange ve kuyruklarda birikme meydana gelir ve silinmez. Bu birikmeler RabbitMQ üzerinde hem belleği tüketir hem de panelin yönetimini zorlaştırır.
  • Bir hata ile karşılaşıldığında retry mekanizması devreye girer ve hata düzeltilmezse o mesajı error kuyruğuna iletir. Ancak, consumer üzerinde bir hata işleme mekanizması (try-catch) mevcutsa, bu mesaj işlenmiş kabul edilerek error kuyruğuna geçmez ve RabbitMQ başarılı bir işlem gibi davranır.
  • RabbitMQ’da kuyruklar oluşturulurken, çok sık mesaj içeren IoT cihaz kuyrukları quorum olarak tanımlanmalıdır.
  • İlk yapılandırmada gönderilen mesajların namespace’leri aynı olmalı ve mümkünse ortak bir core pakette tutulmalıdır.
  • Projelerde, consumer’ların işlevlerine göre virtual host alanlarında çalışılması tavsiye edilir; örneğin IoT cihaz, kullanıcı, alışveriş gibi domainsel ayrımlar kuyruk yönetimini kolaylaştırır.
  • Mevcut projeniz çoklu instance çalışmaya uygun değilse consumerların çoklanması durumunda da projede aksaklıklar meydana getirmektedir.

Asenkron iletişimde RabbitMQ ve Mass Transit, verilerin sessiz dansıdır.

--

--