RabbitMQ Genel Bakış

Berk Emre Çabuk
Devops Türkiye☁️ 🐧 🐳 ☸️
8 min readJul 18, 2020

--

Tam 3 haftadır makalemi bu hafta sonu yazacağım diye kendime söz verip 2 paragraf yazıp bırakıyordum. Şuan kahvemi aldım ve bitirmeye hazırım :) Kafka vs RabbitMQ karşılaştırmamızın ikinci bölümünde kalmıştık. Bir önceki yazımda temel olarak Kafka’ya değinmiştik eğer okumadıysanız buradan erişebilirsiniz. Sıra geldi diğer bir favorim olan RabbitMQ’dan bahsedeceğiz.

hangi mesaj hangi kuyruğa gidiyor?

Kısaca bir tanım yapmak gerekirse, 2007 yılında LShift ve CohesiveFT şirketleri tarafından Erlang ile geliştirilip open source olarak yayınlanmış ve Message Broker kategorisi altında bize çözüm sunan bir platformdur. AMQP(Active Message Queueing Protocol) protokolünü kullanmaktadır böylelikle platform bağımsız olarak Queue yapısı kullanılarak uygulamalar arası iletişim sağlanmaktadır. Bunun dışında STOMP, MQTT protokollerini de desteklemektedir.

Peki tam olarak bize ne sağlıyor sorusuna hızlıca bir giriş yapalım. Uygulamalarımız arasındaki haberleşme sync/async olmak üzere bir çok farklı şekilde olabilir. RabbitMQ bize bir çok farklı async Messaging Pattern’i kullanmamıza imkan sağlamaktadır. Kurduğunuz topolojiye göre aşağıdaki messaging patternleri kullanmanıza imkan sağlar;

  • Publish/Subscribe, producer’dan fırlatılan mesaj, farklı kuyruklara gönderilip farklı işler gerçekleştiren consumer’lar tarafından alınıp işlenebilir. Örnek vermek gerekirse akıllı evinize siz yaklaştığınızda telefonunuzdan mesafenizi belirten bir mesaj fırlatalım ve bu mesaj farklı işler tarafından yakalanıp gerçekleştirilsin(klima çalışsın, panjurlar açılsın vs).
  • Message Qeueu, producer tarafından gönderilen mesajlar aynı kuyruk içerisine alınıp aynı işi gerçekleştiren consumer’lar tarafından işlenebilir.
  • Many to One, bir çok farklı producer tarafından gönderilen aynı tipteki mesajlar aynı kuyruğa eklenip consumer tarafından işlenebilir.
  • Request/Reply, şuana kadar bahsettiklerim hep fire and forget prensibindeydi. Request/Reply ise producer tarafından gönderilen mesaj consumer tarafından işlendikten sonra response’u başka bir kuyruk üzerinden producer’a geri gönderir.

Kısacası RabbitMQ temel işlev olarak, kuyruk yapısı ve kendi bünyesinde bize sunduğu bir çok avantajı ile yukarıda bahsettiğim messing patternleri kullanarak servislerimiz/uygulamalarımız arasında haberleşmeyi sağlamaktadır.

RabbitMQ’nun paydaşlarından hızlıca bahsedeceğim ardından bize sağladığı imkanlara değinip, Kafka ile arasındaki farkları karşılaştıracağız.

Broker, Kafkadaki belirttiğimden bir farkı bulunmamaktadır. Kısacası RabbitMQ’nun üzerinde çalıştığı sanal veya fiziksel makinedir. Brokerlar master/slave yapısında çalışabilmektedirler ve bunun için ayrı bir platforma/plugine ihtiyaç duymazlar.

Producer, Mesajları RabbitMQ’ya gönderen taraf olup, mesajla birlikte routing key bilgisi de gönderilmektedir. Tabi gönderilen mesaj network sorunlarından dolayı veya buna benzer başka bir sebepten dolayı broker’a iletilemeyebilir. Mesajın ulaşıp ulaşmadığıını “confirm.select” özelliği ile kontrol edebilirsiniz.

Exchange, kendi içerisinde bir çok farklı kuyruğu barındıran ve gelen mesajları routing key sayesinde ilgili kuyruklara yönlendiren sanal bir alan diyebiliriz. 4 farklı exchange türü bulunup yukarıda bahsedilen messaging pattern’leri desteklemesini sağlayan ve RabbitMQ’nun en önemli farklarından birisidir.

  • Direct Exchange
    Exchange’e bind olan her kuyruğun dinlediği sadece bir key vardır. Bu yapı point-to-point veya message queue haberleşmeye imkan sağlamaktadır. Kafamızda daha net canlanması için aşağıdaki örneği inceleyebiliriz.
Direct Exchange

Gönderilen 3 mesaj içerisinde payment.* key’i ile gönderilen mesaj herhangi bir kuyruğa yönlendirilmez. Diğerleri ile ilgili key’e bind olmuş kuyruklara Exchange tarafından yönlendirilir.

Peki biz direct exchange’i nerede kullanacağız? Gerçekleştireceğiniz işlem spesifik bir işlem ise ve bu işlem sadece belirli bir durumda gerçekleşecek ise tercih edilebilir. Daha net bir şekilde anlaşılması için örnek üzerinden gidebiliriz. Bir ürün satın aldınız ve ödeme yöntemi olarak kredi kartı veya eft tercih edebilirsiniz. Bu iki işlemin gerçekleştireceği processler birbirinden farklı olacağından dolayı ayrı servisler olsun. Kullanıcı her iki ödeme yöntemini birden kullanamayacağından dolayı mesajı fırlattığında bu mesaj sadece bir kuyruk tarafından yakalanıp işlenmesi gerekir. Ayrıca bu servisler sadece belirli bir tipe ait ödeme işlemi gerçekleştireceğinden dolayı birden fazla key’i dinlemesine gerek yoktur. Bundan dolayı oluşturduğumuz bu senaryoda Direct Exchange kullanabiliriz.

  • Topic Exchange
    Bence RabbitMQ’nun tercih edilme sebeplerinden biri topic exchange yapısıdır. Burada amaç bir mesajın key yapısına göre bir veya bir çok kuyruğa ilgili mesajın gönderilmesidir. Bu exchange sayesinde Publish/Subscribe yapısını desteklemektedir.
Topic Exchange

Yine net anlaşılması için örnek üzerinden gidersek eğer, routling key yapımız burada 2 parçadan oluşmaktadır. Exchange, Queue-1 kuyruğuna key’in sonu “Email” ile biten bütün mesajları yönlendirmektedir. Benzer şekilde Exchange iki parçalı keyde “alert” ile başlayan bütün mesajları Queue-2'ye yönlendirmektedir.

Peki biz direct exchange’i nerede kullanacağız? Consumer tarafında gerçekleştireceğimiz işlem birden fazla senaryo tarafından kullanılacaksa veya gönderilen mesaj birden fazla farklı işi gerçekleştiren Consumer’lar tarafından işlenecekse Topic Exchange kullanabiliriz. Örnek üzerinden gidersek eğer senaryo kullanıcıya bir durum(bilgilendirme,alarm vs) hakkında iletişim kanalları üzerinden haber vermek üzerine kuruludur. Key 2 parçadan oluşmaktadır burada birinci bölüm mesajın tipini ikinci bölüm ise iletişim kanalını belirtmektedir.

Bu key yapısını dinleyen 3 kuyruk bulunmaktadır ve bu kuyruklardan ilki Email servisi olup mesajın durumu ne olursa olsun kullanıcıya email atacaktır bundan dolayı “*.Email” key’ine bind olmuştur. İkinci kuyruk ise acil durumlarda monitoring yapan bir servis olsun bundan dolayı “alert.*” key’ine bind olmuştur. Böyle bir durumda “alert.Email” keyi ile fırlatılan bir mesaj exchange tarafından her iki kuyruğada gönderilip paralelde farklı processler gerçekleştirilecektir.

  • Fanout Exchange
    Producer tarafından gönderilen mesajların key bilgisine bakılmaksızın mesajlar exchange’e bind olan bütün kuyruklara gönderilmektedir. Bu da Publish/Subscribe yapısını desteklemektedir.
Fanout Exchange

Fanout Exchange genellikle broadcast gibi durumlarda kullanılmaktadır.

  • Header Exchange
    Aslında Topic Exchange yapısına çok benzer sadece key üzerinden eşleştirme değilde gönderilen mesajın header bilgisi içerisinde search edip eşleşen key bilgisi var ise ilgili kuyruklara mesajları yönlendirmektedir. Çok kullanışlı bir exchange türü değildir çünkü diğerlerine göre daha fazla efor gerektirir.

Exchange’leri uzun uzun açıkladıktan sonra diğer paydaşlardan devam ederken Kafka yazısında bahsettiğim açıklamaların aynısını yapmaktan çok farkları neler daha çok onları belirtmeye çalışacağım.

Message, burada farklı olarak mesajlara öncelik verebiliyor olmamızdır. Yani Kafkada mesajlar ilgili partitionlara gönderildiğinde sıralı olarak işleniyordu. RabbitMQ’da her kuyruk için mesajların alabileceği max priority (x-max-priority) belirtebiliyorsunuz. Böylelikle mesajlarda belirttiğiniz Priority değerine(Max değeri kuyrukta belirtilen kadar olabilir) göre kuyruk içerisinde mesajın sırası değiştirilip işlenme önceliği tanınabiliyor.

Queue, aslında Kafkada yer alan partitionlara benziyor diyebiliriz. Görevi mesajları kendi üzerinde tutup consumer’lara mesajları eşit bir şekilde dağıtmaktır. Kuyrukların yukarıda bahsettiğimiz max priority gibi bir çok özelliği bulunmaktadır en sık kullanılanlara değinmek gerekirse;

  • Message TTL(x-message-ttl), sayesinde mesajlarınız kuyruk üzerinde belirlenen sürenin üzerinde beklediyse kuyruktan otomatik olarak silinmektedir. Kullanım örneği olarak ise 5dk için geçerli olan kampanyanızı duyurmak için yararlanabilecek müşteriler için ilgili mesajı Notification kuyruğuna eklediniz burada 5dk TTL süresi verebilirsiniz, böylelikle 5 dakikadan fazla kuyrukta bekliyorsa kampanyayı müşteriye bildirmenin bir anlamı olmayacağı için mesaj kuyruktan silinecektir.
Mesaj belirtilen x-message-ttl(30 saniye) sonra silinmiştir

Normal şartlarda bir mesajın ömrü consumer tarafından alınıp işleninceye kadardır. Kafka’da mesaj consumer tarafından işlendikten sonra bile durabilir ama RabbitMQ’da silinir. Bu özellik ihtiyaca göre avantaj veya dezavantaj olabilir.

  • Max Lenght Bytes(x-max-length-bytes), mesajlarınızı belli bir boyut ile sınırlandırabilirsiniz. Boyutu geçen mesajlar kuyruğa alınmamaktadır. Kafka’da temel mantığı streaming işlemleri üzerine olduğundan dolayı mesajlar büyük boyutlu olabiliyordu ama büyük boyutlu mesajlar RabbitMQ’da performans açısından sorun oluşturabilir. Peki bu duruma örnek vermek gerekirse, performansa ihtiyaç duyduğumuz ve mesajların kaybolmasının önemli olmadığı bir broadcast düşünelim burada Fanout Exchange içerisinde tanımladığımız kuyrukların mesaj boyutlarını 2mb ile sınırlandırabiliriz böylelikle kuyruğu yoracak mesajlardan kurtulmuş oluruz.(ne kadar doğru tartışılır tabi)
  • Layz Mode(x-queue-mode), kuyruk içerisindeki mesajların consumer olmadığı zamanlar diske yazılıp ramdeki alanını boşaltmasını sağlar. Tekrardan ilgili kuyruğa consumer bağlandığında diske yazılan mesajlar ram’e alınır. Böylelikle ama efektif bir kaynak kullanımı sağlanır. Her zaman çalışması gerekmeyen consumer’ların olduğu bir yapımız varsa eğer bu özelliği kullanabiliriz.
  • Durability, kuyruğu oluştururken Durability özelliği olarak iki(Durable/Transient) seçenek bulunmaktadır. Bu özellik sayesinde kuyruğunuzun broker restart olduğunda kaldığı yerden devam edip etmemesine karar veriyorsunuz. Eğer Durable seçerseniz kuyruk hakkındaki bilgileri disk üzerinde saklar ve default olarak gelen budur. Transientte ise ram’de saklar.
  • Auto Delete, özelliği sayesinde eğer kuyruğa subscribe olan consumer yoksa kuyruk otomatik olarak silinmektedir.

Consumer, kuyruklara subscribe olup, ilgili kuyruklardan mesajları alıp işleyen taraftır. Mesajı alıp işledikten sonra RabbitMQ’ya ack bilgisini dönmektedir, ack bilgisi alındıktan sonra mesaj kuyruktan çıkarılır. Consumer’lar scale edilmek istenildiği zaman herhangi bir değişiklik yapılmasına gerek yoktur. Yani yeni ayağa kaldırdığınız consumerlarda veya RabbitMQ üzerindeki konfigürasyonlarda değişikliğe gerek duymadan kuyruklara bağlanıp mesajları işlemeye başlayabilirler(Kafkada consumer-partition dengesi için değişiklikler yapmamız gerekebiliyordu).

Paydaşlardan kısaca bahsettikten sonra RabbitMQ’nun bize sağladığı genel avantajlara ve Kafka ile arasındaki farklara senaryolar üzerinden inceleyebiliriz;

Smart Broker/Dumb Consumer, sayesinde kontrolün consumer tarafında değil broker tarafında olduğunu biliyoruz. RabbitMQ, consumer’a gönderdiği mesaj için ack bilgisi geldiği taktirde bu mesajı kuyruktan çıkarmaktadır böylelikle mesajın iletildiğini garanti altına almaktadır. Kuyruk üzerinde nerede kaldığı bilgisini broker tutmaktadır. Kafkada ise tam tersi bir yapı vardır. Peki hangisini tercih etmeliyim?
Eğer işlediğiniz mesajları tekrar işlemeniz gerekiyor ise ve her consumer farklı pointerlar ile mesajları okuyacak ise (canlı yayın broadcast ediliyor ve geri sarıp izleme özelliğiniz bulunuyor bu sayede her consumer hangi mesajda kaldığını kendi belirliyor) Kafka tercih edilebilir.

Yada bir alış veriş yaptınız ve tedarikçiye bu sipariş bilgileri gönderildi, ilgili consumerlar tarafından mesajın sadece bir kez işlendiğini bilmemiz ve bu mesajın tekrar işlememesi gerekiyor ise(aksi taktirde mükerrer sipariş olur) RabbitMQ tercih edilebilir.

Push Model, prensibi ile çalışmaktadır. Yani mesajları broker ilgili consumer’lara gönderir böylelikle mesajların eşit bir şekilde consumer’lara dağıtılmasını broker üstlenmiş olur. Broker mesajları dağıtırken round-robin algoritmasını kullanmaktadır.

Mesajlar kuyruklarda persistent olarak saklanabilir. Yani broker restart edildiği taktirde mesajlarınız kaybolmaz kaldığı yerden devam eder. Bu bilgi Kafkadaki gibi mesajların kalıcılığı ile karıştırılmasın, RabbitMQ üzerinde mesajın ömrü en geç consumer tarafından işlenildikten sonra biter(TTL süresi belirtildiği taktirde daha erkende bitebilir). Buradaki persistent flag’i sadece o sırada broker bir sebepten ötürü down olursa mesajlarınızın storagedan getirilmesini sağlamaktadır. Bu özellik aktif edildiği taktirde mesajların diske yazılma maliyeti de olacağından dolayı broker’ın hızı biraz daha yavaşlamaktadır.

DeadLetter yapısı bulunmaktadır. Yani consumer mesajı aldı process süresinde bir hata alırsa böyle bir durumda mesaj deadletter exchange’e gönderiliyor. Deadletter exchange üzerinden kurmak istediğiniz Fault Handling yapısı ne ise ona yönelik kurgulayabilirsiniz.

Fault Handling demişken başka bir özellik olan retry message ise, hata alan mesajların belirttiğiniz count kadar tekrar consumer’a gönderilip denenmesidir. Consumer’da hata alan mesajlar, consumer tarafından kuyruğa nack bilgisini gönderdikten sonra kuyruk ilgili mesajı tekrardan consumer’a gönderip bir kaç kez daha işlenmesini denemektedir. Peki bu özellik ne işimize yarayacak zaten hata almış. Eğer alınan hata geçici(Transient failure) bir sebepten ötürü ise (db connection timeout vs) terkrar denendiğinde işlemi gerçekleştirebiliriz. Eğer tekrarlar sonucunda halen daha hata alıyorsak Deadletter’a yönlendirip kuracağımız fault handling yapısına göre ilerleyebiliriz.

Son olarak brokerlar master/slave yapısında distributed şekilde çalışabilmektedir. Yani brokerlardan bir tanesi down olduğu zaman herhangi bir kaybımız olmayıp kaldığımız yerden devam edebiliriz.

Mümkün olduğu kadar Kafka ve RabbitMQ’yu temel seviyede anlatmaya çalıştım. Tamamen teorik bir şekilde ilerledim ama sizlere bahsettiğim özellikler için küçük örnekler hazırlayıp github hesabımda paylaşacağım. Umarım yararlı olmuştur.

Kaynaklar:

https://www.rabbitmq.com/documentation.html

--

--