Kafka ile RabbitMQ’yu karşılaştırırken genelde bir özellik tablosuyla başlanır: throughput, replay, routing, ordering, retry. Tablo faydalıdır ama tek başına seçim yapmayı pek kolaylaştırmaz, çünkü maddeler birbirinden bağımsız görünür.

Bu farkları başka bir yerden okumayı daha kullanışlı buluyorum:

Bir mesaj broker’a girdikten sonra ne oluyor?

RabbitMQ mesajın teslim durumunu takip eder ve ack geldikten sonra mesajı kuyruktan çıkarır. Kafka ise mesajı bir log’da tutar; nerede kalındığı bilgisini consumer kendi offset’iyle taşır.

İki sistem arasındaki önemli farkların çoğu bu noktadan başlıyor: mesajın broker’daki yaşam döngüsü, consumer’ın konumunu kimin tuttuğu ve mesaj işlendikten sonra broker’ın ne yaptığı. Replay, consumer group, retry ve routing konularına da buradan geçebiliriz.

Önce: broker’a neden ihtiyaç duyuyoruz?

Message broker, iki uygulama arasındaki iletişimi asenkron hale getiren aracı katmandır. Üretici (producer) mesajı broker’a bırakır, tüketici (consumer) uygun olduğunda alır. İki taraf birbirini tanımak ve aynı anda ayakta olmak zorunda değildir.

Sipariş servisinin e-posta, fatura, stok ve kargo servislerini doğrudan HTTP ile çağırdığı bir sistem düşünelim:

@Transactional
public Order createOrder(OrderRequest request) {
    Order order = orderRepository.save(Order.from(request));

    emailService.sendConfirmation(order);      // yavaşsa sipariş yavaşlar
    invoiceService.create(order);               // hata verirse sipariş geri döner
    stockService.reserve(order);
    shippingService.schedule(order);

    return order;
}
@Transactional
public Order createOrder(OrderRequest request) {
    Order order = orderRepository.save(Order.from(request));

    // Sipariş servisi kimin dinlediğini bilmiyor
    broker.publish(new OrderCreated(order.getId(), order.getTotal()));

    return order;
}

Broker kullanmanın temel faydaları birkaç başlıkta toplanabilir:

  1. Decoupling

    Sipariş servisi OrderCreated olayını yayınlar ve gerisini bilmez. Yarın altıncı bir servis eklendiğinde sipariş kodu değişmez.

  2. Tamponlama

    Trafik arttığında mesajlar kuyrukta birikebilir; consumer’lar yetişebildikleri hızda işler.

  3. Dayanıklılık

    Fatura servisi bir süre ayakta olmasa da mesajlar broker’da bekler; servis geri geldiğinde kaldığı yerden devam eder.

  4. Yatay ölçekleme

    Aynı kuyruğu tüketen consumer sayısını artırarak işleme kapasitesini büyütebilirsiniz.

  5. Fan-out

    Tek bir olayı birden çok bağımsız servise ulaştırmak — üretici tarafında bir değişiklik yapmadan.

Her yere broker koymak da doğru değil

  • Anında cevap gereken akışlarda uygun değil. “Bu ürünün fiyatı kaç?” sorusunun cevabı bir kuyruktan gelmez; orada HTTP veya gRPC daha doğrudur.

  • Küçük ve düşük trafikli bir uygulamada maliyeti faydasını geçebilir. Broker kendi operasyonel yükünü getirir: cluster, izleme, alarm, sürüm yükseltme.

  • Basit background job için önce veritabanını değerlendirin. Tek tabloya yazılan bir job kuyruğu belirli bir ölçeğe kadar iş görür.

Broker’a ihtiyacınız olduğuna karar verdiyseniz asıl karar başlıyor: hangisi?

İki model: postane ve arşiv

RabbitMQ — postane

Bir postane gibi düşünülebilir: mesajı adresine göre doğru kuyruğa yönlendirir ve consumer’ın teslim durumunu takip eder.

Mesaj acklendiğinde kuyruktan çıkar.

Kafka — arşiv

Append-only bir arşiv gibi düşünülebilir: mesajlar log’a sırayla yazılır ve retention süresi boyunca orada kalır.

Consumer nerede kaldığını kendi offset’iyle takip eder.

Aynı şeyin akış hali:

graph LR
    P1["Producer"] --> EX{{"Exchange"}}
    EX -->|"routing key"| Q1[("Queue")]
    Q1 -->|"push"| C1["Consumer"]
    C1 -.->|"ack → mesaj kuyruktan çıkar"| Q1

RabbitMQ’da akış tek yönlü ve sonlu: broker mesajı bir consumer’a iter, ack geldiğinde kuyruktan çıkarır. Geri dönen tek şey teslim bilgisidir, mesajın kendisi değil.

graph LR
    P2["Producer"] --> T["Topic partition<br/>append-only log"]
    T -->|"offset 512"| C2["Consumer A"]
    T -->|"offset 40"| C3["Consumer B"]
    T -->|"offset 0'dan itibaren"| C4["Consumer C<br/>yeni katıldı"]

Kafka’da log ortada durur ve her consumer kendi konumundan okur. Consumer B geriden geliyor, Consumer C yeni bağlandı ve geçmişi baştan okuyor. Hiçbiri diğerini etkilemiyor, log’a dokunulmuyor.

Bundan sonraki bölümlerde bu iki modelin nelere yol açtığına bakacağız.

Mesaj log’da kalınca: replay

Mesaj okunduktan sonra da yerinde duruyorsa geri dönüp tekrar okunabilir. Kafka’nın replay yeteneği buradan geliyor.

Bunun nasıl çalıştığını görmek için Kafka’nın dört kavramı yeterli.

Topic ve partition

Topic, mesajların yazıldığı mantıksal kategoridir: orders, user-signups, payment-events.

Bir topic partition denen parçalara bölünür. Her partition, yalnızca sonuna ekleme yapılan sıralı bir log dosyasıdır:

orders topic'i, 3 partition

partition-0:  [0][1][2][3][4][5] ← yeni mesajlar buraya
partition-1:  [0][1][2][3]
partition-2:  [0][1][2][3][4][5][6][7]

Partition sadece bir depolama detayı değil, aynı zamanda Kafka’nın paralellik birimidir. Bunun sonuçlarına birazdan geleceğiz.

Bir mesajın hangi partition’a gideceğini partitioner belirler. Varsayılan davranışta:

  • Mesajın key‘i varsa: key’in hash’ine göre bir partition seçilir; aynı key aynı partition’a gider.
  • Key yoksa: mesajlar partition’lara dengeli dağıtılır.

Offset

Partition içindeki her mesajın sıra numarasıdır. Consumer “4523’e kadar okudum” bilgisini offset olarak commit eder.

Replay de bundan ibaret: offset geri alınır, aynı mesajlar tekrar akar.

mevcut durum:      offset = 4523
offset geri alınır: offset = 4100
sonuç:             423 mesaj yeniden işlenir

E-posta servisinde bir hata yüzünden son iki saatte yanlış içerik gittiyse, kodu düzeltip offset’i geri almak yeterli olur. RabbitMQ’da o mesajlar acklendiği anda kuyruktan çıkmıştı.

Consumer group

Aynı group.id‘ye sahip consumer’lar bir grup oluşturur ve iki kuralı vardır:

Grup içinde: iş bölüşülür

Bir partition, grup içinde yalnızca bir consumer’a atanır. Böylece aynı mesaj grup içinde iki kez işlenmez.

Gruplar arasında: iş kopyalanır

Farklı gruplar aynı topic’i birbirinden bağımsız okur; her grubun kendi offset’i vardır. Fan-out böyle yapılır.

graph LR
    T["orders topic<br/>3 partition"]

    T --> G1["Grup: fatura-servisi<br/>offset 4523"]
    T --> G2["Grup: analitik<br/>offset 4523"]
    T --> G3["Grup: arama-indeksi<br/>offset 120 — geriden geliyor"]

Bunun pratik bir sonucu var: yarın arama indeksini sıfırdan kurmanız gerekirse yeni bir consumer group açıp offset’i başa alırsınız. Topic’teki geçmiş yeni servisi besler ve mevcut servislerin bir şey yapması gerekmez.

Retention

Mesajlar okunduktan sonra silinmediğine göre bir yerde durmaları gerekir. Retention politikası bunu yönetir: süre bazlı (örneğin 7 gün), boyut bazlı ya da log compaction ile — her key’in yalnızca son değeri saklanır.

Log compaction, bir topic’i “olay akışı” olmaktan çıkarıp “durum tablosu"na yaklaştırır. user-profiles topic’inde her kullanıcı id’si bir key ise, compaction sonrası her kullanıcının en güncel hali kalır. Yeni bir servis topic’i baştan okuyup kendi yerel kopyasını kurabilir.

Broker teslimi takip edince: mesajı reddedebilmek

Şimdi ters yöne bakalım. RabbitMQ’da mesaj bir consumer’a teslim edildiğinde broker bunu bilir ve mesajı “uçuşta” sayar. Bu bilgi, Kafka’da doğrudan karşılığı olmayan bir şeyi mümkün kılıyor: mesajı reddetmek.

Ack, nack, requeue

Consumer mesajı işleyince ack gönderir ve mesaj kuyruktan çıkar. İşleyemezse nack ile ya tekrar kuyruğa koydurur (requeue) ya da düşürtür.

@RabbitListener(queues = "email-jobs")
public void handle(EmailJob job, Channel channel,
                   @Header(AmqpHeaders.DELIVERY_TAG) long tag) throws IOException {
    try {
        emailSender.send(job);
        channel.basicAck(tag, false);              // işlendi
    } catch (TransientFailure e) {
        channel.basicNack(tag, false, true);       // geçici hata, kuyruğa geri koy
    } catch (PermanentFailure e) {
        channel.basicNack(tag, false, false);      // kalıcı hata, DLX'e yönlendir
    }
}

Kafka’da bu üç satırın doğrudan karşılığı yok. Orada mesaj bazında bir reddetme mekanizması değil, offset vardır:

  • Başarısız mesajın offset’ini commit ederseniz o mesaj atlanmış olur.
  • Commit etmezseniz o partition’daki sıra ilerlemez ve arkadaki mesajlar bekler.

İkincisine head-of-line blocking deniyor ve task queue senaryolarında en sık karşılaşılan zorluklardan biri.

Dead Letter Exchange

Reddedilen, TTL’i dolan veya kuyruk limitini aşan mesajlar bir DLX’e yönlendirilir. Hatalı mesajları karantinaya almanın ve gecikmeli retry kurmanın yerleşik yolu budur:

graph LR
    Q["email-jobs"] -->|"nack (requeue=false)"| DLX{{"email-dlx"}}
    DLX --> DLQ[("email-dlq<br/>karantina")]
    DLQ -.->|"incele, düzelt,<br/>elle geri gönder"| Q

RabbitMQ bunu yerleşik olarak sunarken Kafka’da genellikle retry topic’leri ve ayrı bir DLQ akışı tasarlamanız gerekir:

email-jobs → (hata) → email-retry-5m → (hata) → email-retry-30m → (hata) → email-dlq

Yaygın ve işleyen bir pattern, ama yazılması ve işletilmesi gereken ek bir katman.

Prefetch

Broker’ın bir consumer’a ack beklemeden aynı anda kaç mesaj göndereceğini belirler:

spring:
  rabbitmq:
    listener:
      simple:
        prefetch: 1
        acknowledge-mode: manual

Süresi öngörülemeyen işlerde prefetch değerini düşük tutmak işe yarar. Yüksek bir değerde worker’a peşin verilen işler, o worker yavaş bir işe takıldığında onunla birlikte bekler; düşük değerde her worker müsait oldukça sıradaki işi alır.

RabbitMQ’nun mesaj bazında kontrol sunan başka özellikleri de var:

  • TTL — mesaja veya kuyruğa yaşam süresi verme
  • Priority queue — öncelikli mesajları öne alma
  • Delayed messages — plugin ile, mesajı belirli süre sonra teslim etme
  • Quorum queues — Raft tabanlı replikasyon; modern kurulumlarda klasik mirrored queue yerine tercih edilir

Bunların Kafka’da yerleşik karşılığı yok; benzer davranışlar uygulama tarafında kurulur.

Peki RabbitMQ Streams? O da replay yapmıyor mu?

Yeni RabbitMQ sürümlerinde Streams adında, append-only bir log tipi var: mesaj okunduğunda silinmiyor ve offset ile geri okunabiliyor.

Yani “replay yalnızca Kafka’da var” demek doğru olmaz. Ama iki şeyi ayırmakta fayda var:

  • Özellik olarak replay RabbitMQ tarafında da mevcut.
  • Ekosistem olarak fark sürüyor: Kafka Connect, Kafka Streams, ksqlDB, Debezium, şema kayıt defteri ve olgun izleme araçları Kafka tarafında.

Elinizde zaten RabbitMQ varsa ve ihtiyacınız sınırlı bir replay ise Streams’e bakmaya değer. Bir event backbone kuruyorsanız ekosistem farkı belirleyici olabilir.

Yönlendirmeyi kim yapıyor?

Mesaj ack sonrası kuyruktan çıkıyorsa, doğru kuyruğa gitmesi broker’ın sorumluluğundadır. Mesaj log’da duruyorsa, kimin neyi okuyacağı okuyan tarafın kararıdır.

Bu yüzden iki sistemin routing modeli birbirine hiç benzemiyor.

RabbitMQ: exchange’ler

Producer mesajı doğrudan kuyruğa değil, bir exchange’e gönderir. Exchange, binding kurallarına ve mesajın routing key‘ine bakarak hangi kuyruklara kopyalayacağına karar verir.

Producer → Exchange → (Binding + Routing Key) → Queue → Consumer

Dört exchange tipi var:

TipNasıl karar verirÖrnek
DirectRouting key tam eşleşirsepayment.failed → yalnızca bu key ile bind edilmiş kuyruk
FanoutKey’e bakmaz, bağlı tüm kuyruklara kopyalarBroadcast
TopicPattern eşleşmesiorder.*order.created, order.cancelled
HeadersRouting key yerine mesaj header’larına bakar{"format": "pdf", "priority": "high"}

Topic exchange’te * tek kelime, # sıfır veya daha çok kelime anlamına gelir. order.# deseni order.eu.created gibi çok seviyeli key’leri de yakalar.

RabbitMQ burada daha esnek bir routing modeli sunuyor: mesajın hangi kuyruklara gideceği broker konfigürasyonuyla belirlenir, üretici veya tüketici kodunu değiştirmeden.

Kafka: topic, partition, key

Kafka’da RabbitMQ’daki exchange/binding modelinin karşılığı yok. Producer bir topic’e yazar, partition seçimi key ve partitioner üzerinden yapılır. Consumer hangi topic’i okuyacağını kendisi bilir.

Filtreleme gerekiyorsa iki yol var: ya konuları ayrı topic’lere bölersiniz, ya da consumer tarafında ilgilenmediğiniz mesajları atlarsınız. İkisi de exchange’lerin verdiği esnekliğin yerini tam olarak tutmaz.

Paralellik neyle sınırlı?

Partition, Kafka’nın paralellik birimi olduğuna göre aynı consumer group’ta partition sayısından fazla aktif consumer olamaz.

orders topic'i, 4 partition, tek consumer group

4 consumer  → her biri 1 partition alır       ✅
6 consumer  → 4'ü çalışır, 2'si boş bekler    ⚠️

RabbitMQ’da partition sayısına bağlı böyle bir consumer-group sınırı yok; bir kuyruğa istediğiniz kadar consumer bağlayabilirsiniz. Elbette burada da throughput broker, ağ ve consumer kaynaklarıyla sınırlıdır — ama sınır yapısal bir tavan değil, kapasite meselesidir.

Kafka:     consumer group paralelliği ≤ partition sayısı
RabbitMQ:  bir kuyruğun consumer sayısı partition sayısıyla sınırlı değil

Partition sayısı geri alınması kolay bir karar değil

Varsayılan partitioning davranışında partition sayısını değiştirmek, key’in partition’a eşlenmesini de değiştirir. Yani aynı key geçiş sonrası farklı bir partition’a düşebilir ve o key için sıralama garantisi geçiş anında kırılır.

Partition sayısını azaltmak ise desteklenmez.

Bu yüzden partition sayısı yalnızca kapasite kararı değil, key bazlı sıralama açısından da düşünülmesi gereken bir karardır.

Sıcak key, tek partition'a yığılmaya yol açar

Key seçimi hem sıralamayı hem yük dağılımını belirler. tenantId‘yi key yaparsanız ve müşterilerinizden biri trafiğin büyük kısmını üretiyorsa, o trafik tek bir partition’a — dolayısıyla tek bir consumer’a — düşer. Diğer partition’lar boş dururken o partition birikmeye başlar.

Ölçüt şu: key’in kardinalitesi partition sayısından belirgin şekilde büyük ve dağılımı görece dengeli olmalı.

Sıralama

Kafka’nın sıralama garantisi topic genelinde değil, partition içindedir. Bu ayrım önemli, çünkü genellikle “tüm olaylar sırayla işlensin” değil, “aynı entity’nin olayları sırayla işlensin” ihtiyacına cevap verir.

Key mekanizması bu ihtiyacı karşılar:

// key = orderId → bu siparişin tüm olayları aynı partition'da, sırayla
kafkaTemplate.send("order-events", order.getId(), new OrderCreated(...));
kafkaTemplate.send("order-events", order.getId(), new OrderPaid(...));
kafkaTemplate.send("order-events", order.getId(), new OrderShipped(...));
graph LR
    A["orderId = 1042"] --> P1["partition-2<br/>created → paid → shipped"]
    B["orderId = 1043"] --> P2["partition-0<br/>created → paid"]
    C["orderId = 1044"] --> P1

Topic genelinde global sıra gerekiyorsa tek partition kullanmak gerekir; bu da paralelliği ortadan kaldırır ve nadiren tercih edilir.

RabbitMQ tarafında ise tek kuyruk ve tek consumer varsa FIFO sıra korunur. Ölçek için birden çok consumer bağlandığında mesajlar farklı worker’lara dağılır ve işlenme sırası garanti edilmez. Requeue de sırayı etkiler: nack ile geri konan mesaj kuyruğun sonuna girer.

Kafka’daki key→partition modeline benzer bir davranış istiyorsanız Consistent Hash Exchange plugin’i ile mesajları key’e göre ayrı kuyruklara dağıtabilirsiniz; ancak bu, Kafka’daki kadar yerleşik bir mekanizma değil.

Entity bazlı sıralama gereksiniminiz varsa — event sourcing, durum makineleri, sipariş yaşam döngüsü — Kafka’nın modeli bu ihtiyaca daha doğrudan cevap veriyor.

İki sistemde de aynı olan konu: delivery garantileri

Buraya kadar farklara baktık. Delivery garantileri ise iki sistemde de aynı üç seviyede tanımlanır:

GarantiAnlamıRiski
At-most-onceEn fazla bir kez teslim edilirMesaj kaybolabilir
At-least-onceEn az bir kez teslim edilirMesaj tekrarlanabilir
Exactly-onceTam bir kez işlenirEn zor ve en maliyetli

Kafka’da producer tarafında acks=all ve min.insync.replicas ile mesajın yeterli sayıda replica’ya yazılması beklenir; enable.idempotence=true producer retry’larının duplicate üretmesini engeller. Consumer tarafında garantiyi belirleyen şey offset’i ne zaman commit ettiğinizdir:

// Önce işle, sonra commit et
process(record);
consumer.commitSync();

// İşlemeden sonra, commit'ten önce çökerse
// → aynı mesaj tekrar gelir
// Önce commit et, sonra işle
consumer.commitSync();
process(record);

// Commit'ten sonra, işlemeden önce çökerse
// → mesaj işlenmemiş olur

RabbitMQ’da producer tarafında publisher confirms broker’ın mesajı aldığını doğrular; mesajın persistent ve kuyruğun durable olması diske yazılmasını sağlar. Consumer tarafında manual ack at-least-once, auto-ack ise at-most-once anlamına gelir.

Kafka’nın transactions özelliği exactly-once sunar, ancak kapsamı esas olarak “Kafka’dan oku → işle → Kafka’ya yaz” akışlarıdır. Dış bir sisteme yazarken — veritabanı, HTTP API, e-posta sağlayıcısı — bu garanti geçerli değildir. RabbitMQ zaten exactly-once iddiasında bulunmaz.

Her iki broker’da da pratik yaklaşım genellikle at-least-once teslimat ve idempotent consumer kullanmaktır. Bu, çoğu zaman mesajın taşıdığı bir id’yi kaydedip tekrar kontrolü yapmaktan ibarettir:

if (!processedEvents.markIfAbsent(event.id())) {
    return;   // zaten işlenmiş
}
process(event);

Task queue işleri: e-posta, PDF, rapor

En sık sorulan somut soru bu: e-posta gönderimi, PDF üretimi, resim işleme, rapor oluşturma gibi işler için hangisi?

Bu işler Kafka ile de yapılır. Mesajı email-jobs topic’ine yazarsınız, consumer group’taki worker’lar tüketir. Yine de çoğu ekip bu senaryoda RabbitMQ’yu tercih ediyor — sebebi Kafka’nın eksik olması değil, tasarımının task queue problemine doğrudan optimize edilmemiş olması.

Bu senaryoda RabbitMQ neden daha doğal gelebilir?

  1. Retry akışı

    Bir e-posta gönderimi başarısız olduğunda RabbitMQ’da nack yeterlidir; mesaj requeue olur ya da DLX’e yönlendirilir. Kafka’da aynı davranış için retry topic’leri ve bir DLQ akışı tasarlamanız gerekir.

  2. Süresi öngörülemeyen işler

    PDF üretimi bunu iyi gösteren bir örnek: bir PDF iki saniye, diğeri üç dakika sürebilir. Kafka’da bir partition’ı tek consumer sıralı işlediği için uzun süren işin arkasındaki mesajlar bekler. RabbitMQ’da düşük prefetch ile her worker müsait oldukça sıradaki işi alır.

  3. Worker sayısı

    Kafka’da aynı gruptaki aktif worker sayısı partition sayısıyla sınırlıdır. Yoğun bir günde worker sayısını hızla artırmak istiyorsanız RabbitMQ tarafında bu bir deployment ayarıdır.

  4. İşe özgü özellikler

    Öncelikli mesaj, gecikmeli teslim ve mesaj bazında TTL, RabbitMQ’da yerleşiktir. Kafka’da bunların karşılığını uygulama tarafında kurmanız gerekir.

Buna operasyonel maliyet de ekleniyor: cluster yönetimi, partition ve replication planlaması, consumer lag izleme. Yalnızca birkaç job türü için bu yük ağır kalabilir.

Kafka’nın bu senaryodaki avantajları

Replay

E-posta servisinde bir hata yüzünden yanlış içerik gittiyse offset’i geri alıp düzeltilmiş kodla yeniden işleyebilirsiniz.

Çoklu tüketici

OrderCreated olayını e-posta worker’ı, analitik ve stok servisi bağımsız gruplarla okuyabilir.

Yüksek hacim

Job sayısı çok yüksekse Kafka’nın throughput’u ve diske dayalı tamponu avantaj sağlar.

Mevcut altyapı

Kafka zaten event backbone olarak duruyorsa, birkaç job türü için yanına RabbitMQ eklemek yerine retry-topic pattern’iyle Kafka’da çözmek daha az operasyonel yük getirebilir.

Son madde pratikte sanıldığından daha belirleyici. “Hangisi daha uygun” sorusunun cevabı çoğu zaman “hangisi zaten kurulu ve ekip hangisini işletmeyi biliyor” tarafından şekilleniyor; bu da meşru bir mühendislik kararı.

Karar özeti

Bu işlerde soru “hangisi yapabilir” değil; ikisi de yapar. Sorulacak soru şu:

İş başına kontrol mü gerekiyor — retry, öncelik, gecikme, işi reddetme? Yoksa akış başına özellikler mi — replay, çoklu tüketici, geçmiş?

DurumTercih
Klasik background job, orta ölçekRabbitMQ
Job başına retry / öncelik / gecikme gerekiyorRabbitMQ
İşlenen job’ları geriye dönük yeniden oynatmak gerekiyorKafka
Aynı olayı birden çok bağımsız servis tüketecekKafka
Çok yüksek hacimli job akışıKafka
Altyapıda zaten Kafka var, RabbitMQ yokKafka (retry-topic pattern’iyle)

Broker seçiminden önce cevaplanması gereken bir soru daha var: mesajın kuyruğa girdiğini garanti edebiliyor musunuz? Veritabanına yazdıktan sonra broker’a publish edene kadar süreç çökerse, sipariş kaydedilmiş ama e-posta hiç gönderilmemiş olur.

Bu problem broker seçiminden bağımsızdır ve çözümü outbox pattern’idir. Asenkron e-posta gönderim sistemi yazısında bu tasarımı stack’ten bağımsız olarak ele almıştım.

Genel karar

Task queue dışına çıkıp resmin tamamına bakalım.

Kafka daha uygun olabilir

  • Event streaming / event sourcing yapıyorsanız; olaylar sistemin kalıcı kaydıysa
  • Çok yüksek throughput gerekiyorsa — log toplama, clickstream, telemetri, IoT
  • Aynı veriyi birden çok bağımsız tüketici okuyacaksa
  • Entity bazlı sıralama gerekiyorsa
  • Stream processing yapacaksanız — Kafka Streams, Flink, ksqlDB
  • Yeni servisleri geçmiş veriyle besleyerek devreye almak istiyorsanız
  • CDC kuruyorsanız — Debezium ile veritabanı değişikliklerini yayınlamak

RabbitMQ daha uygun olabilir

  • Klasik task queue / background job ihtiyacınız varsa
  • Karmaşık routing gerekiyorsa — içeriğe veya etikete göre dağıtım
  • Mesaj başına iş takibi istiyorsanız — ack, nack, retry, DLX
  • Request/reply (RPC) deseni kuracaksanız
  • Öncelik, TTL, gecikmeli mesaj gibi kontroller gerekiyorsa
  • Mesaj işlendikten sonra saklanmasına gerek yoksa
  • Daha basit operasyon ve düşük kaynak tüketimi istiyorsanız
  • AMQP, MQTT, STOMP gibi farklı protokoller gerekiyorsa

Nereden başlamalı sorusuna da şöyle bakılabilir: klasik bir task queue ihtiyacınız varsa RabbitMQ daha doğal bir başlangıç noktasıdır. Event streaming, replay veya yüksek hacimli akış işleme ihtiyacı varsa Kafka’yı değerlendirmek daha anlamlı olur.

İkisi rakip olmaktan çok tamamlayıcı; büyük sistemlerde ikisi bir arada kullanılır — event backbone için Kafka, iş kuyrukları için RabbitMQ.

Hangisini seçerseniz seçin, izlenecek metrik farklı: Kafka’da consumer lag (grubun kaç mesaj geride olduğu), RabbitMQ’da kuyruk derinliği ve unacked mesaj sayısı. İkisi de “consumer’lar yetişemiyor” durumunun ilk göstergesidir.

Hızlı karşılaştırma

ÖzellikKafkaRabbitMQ
ModelDağıtık log / event streamingMessage queue
Mesaj saklamaRetention süresince log’da kalırAck sonrası kuyruktan çıkar
ReplayOffset ileStreams dışında yok
RoutingTopic, partition, keyDört exchange tipi
SıralamaPartition içinde garantiliTek consumer’da FIFO
DeliveryAt-most / at-least-once, sınırlı exactly-onceAt-most / at-least-once
Retry & hataRetry topic + DLQ tasarlanırYerleşik — nack, requeue, DLX, TTL
Push / pullPullPush, prefetch ile kontrollü
Grup paralelliğiPartition sayısıyla sınırlıPartition sayısıyla sınırlı değil
ThroughputÇok yüksekYüksek
GecikmeDüşük, batch ayarına bağlıÇok düşük
Öncelikli mesajUygulama tarafındaYerleşik
Gecikmeli mesajUygulama tarafındaPlugin ile
RPC deseniUygun değilDoğal destek
Operasyonel yükDaha yüksekDaha düşük
EkosistemConnect, Streams, ksqlDB, DebeziumGeniş dil desteği, yönetim UI’ı, plugin’ler

Toparlarsak

Kafka ve RabbitMQ aynı probleme farklı modellerle yaklaşıyor.

Kafka’da mesajlar log’da kalıyor ve consumer’lar kendi konumlarını takip ediyor. Bu model replay, bağımsız consumer grupları ve event streaming gibi ihtiyaçlarda güçlü.

RabbitMQ’da ise broker mesajın teslim durumunu takip ediyor. Ack, requeue, routing ve DLX gibi özellikler task queue senaryolarında daha doğal bir model sunuyor.

Bu yüzden seçim yaparken “hangisi daha hızlı?” sorusundan önce mesajın sisteminizde nasıl kullanılacağını düşünmek daha faydalı. Geçmişi tekrar okumak ve aynı olayı birden fazla consumer’ın bağımsız işlemesi önemliyse Kafka öne çıkar; mesajın tek bir iş olarak işlenip tamamlanması, retry ve routing’in broker tarafından yönetilmesi önemliyse RabbitMQ daha doğal bir seçim olabilir.

İki sistemi aynı mimaride birlikte kullanmak da gayet normaldir.