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:
Decoupling
Sipariş servisi
OrderCreatedolayını yayınlar ve gerisini bilmez. Yarın altıncı bir servis eklendiğinde sipariş kodu değişmez.Tamponlama
Trafik arttığında mesajlar kuyrukta birikebilir; consumer’lar yetişebildikleri hızda işler.
Dayanıklılık
Fatura servisi bir süre ayakta olmasa da mesajlar broker’da bekler; servis geri geldiğinde kaldığı yerden devam eder.
Yatay ölçekleme
Aynı kuyruğu tüketen consumer sayısını artırarak işleme kapasitesini büyütebilirsiniz.
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"| Q1RabbitMQ’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"| QRabbitMQ 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:
| Tip | Nasıl karar verir | Örnek |
|---|---|---|
| Direct | Routing key tam eşleşirse | payment.failed → yalnızca bu key ile bind edilmiş kuyruk |
| Fanout | Key’e bakmaz, bağlı tüm kuyruklara kopyalar | Broadcast |
| Topic | Pattern eşleşmesi | order.* → order.created, order.cancelled |
| Headers | Routing 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"] --> P1Topic 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:
| Garanti | Anlamı | Riski |
|---|---|---|
| At-most-once | En fazla bir kez teslim edilir | Mesaj kaybolabilir |
| At-least-once | En az bir kez teslim edilir | Mesaj tekrarlanabilir |
| Exactly-once | Tam bir kez işlenir | En 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?
Retry akışı
Bir e-posta gönderimi başarısız olduğunda RabbitMQ’da
nackyeterlidir; 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.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.
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.
İş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ş?
| Durum | Tercih |
|---|---|
| Klasik background job, orta ölçek | RabbitMQ |
| Job başına retry / öncelik / gecikme gerekiyor | RabbitMQ |
| İşlenen job’ları geriye dönük yeniden oynatmak gerekiyor | Kafka |
| Aynı olayı birden çok bağımsız servis tüketecek | Kafka |
| Çok yüksek hacimli job akışı | Kafka |
| Altyapıda zaten Kafka var, RabbitMQ yok | Kafka (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
| Özellik | Kafka | RabbitMQ |
|---|---|---|
| Model | Dağıtık log / event streaming | Message queue |
| Mesaj saklama | Retention süresince log’da kalır | Ack sonrası kuyruktan çıkar |
| Replay | Offset ile | Streams dışında yok |
| Routing | Topic, partition, key | Dört exchange tipi |
| Sıralama | Partition içinde garantili | Tek consumer’da FIFO |
| Delivery | At-most / at-least-once, sınırlı exactly-once | At-most / at-least-once |
| Retry & hata | Retry topic + DLQ tasarlanır | Yerleşik — nack, requeue, DLX, TTL |
| Push / pull | Pull | Push, prefetch ile kontrollü |
| Grup paralelliği | Partition sayısıyla sınırlı | Partition sayısıyla sınırlı değil |
| Throughput | Çok yüksek | Yüksek |
| Gecikme | Düşük, batch ayarına bağlı | Çok düşük |
| Öncelikli mesaj | Uygulama tarafında | Yerleşik |
| Gecikmeli mesaj | Uygulama tarafında | Plugin ile |
| RPC deseni | Uygun değil | Doğal destek |
| Operasyonel yük | Daha yüksek | Daha düşük |
| Ekosistem | Connect, Streams, ksqlDB, Debezium | Geniş 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.

Yorumlar