Giriş: Mesaj Kuyrukları ve RabbitMQ’ya Genel Bakış
Modern yazılım mimarilerinde sistemler arası iletişimin verimli, güvenilir ve esnek olması büyük önem taşır. Bu ihtiyacı karşılamak üzere mesaj kuyrukları (message queues) devreye girer. Mesaj kuyrukları, farklı servislerin birbirleriyle doğrudan bağlantı kurmadan, asenkron bir şekilde iletişim kurmasını sağlayan aracılar olarak işlev görür. Bu sayede servisler arasında bağımlılık azalır, sistem daha esnek ve ölçeklenebilir hale gelir.
RabbitMQ, AMQP (Advanced Message Queuing Protocol) standardını uygulayan, açık kaynaklı, popüler bir mesaj aracısıdır (message broker). Yüksek performans, güvenilirlik ve esneklik sunarak dağıtık sistemlerde mesajlaşma ihtiyaçlarını karşılar. RabbitMQ’nun temel bileşenleri şunlardır:
* Producer (Üretici): Mesajları RabbitMQ’ya gönderen uygulama.
* Consumer (Tüketici): Mesajları RabbitMQ’dan alıp işleyen uygulama.
* Exchange (Değiş Tokuş): Üreticiden gelen mesajları, belirli kurallara göre kuyruklara yönlendiren mekanizma.
* Queue (Kuyruk): Mesajların tüketiciler tarafından alınana kadar bekletildiği yer.
* Binding (Bağlama): Bir kuyruk ile bir exchange arasındaki bağlantıyı tanımlayan kural. Bu kural genellikle bir “routing key” içerir.
Bu rehberde, RabbitMQ’nun güçlü mesaj yönlendirme yeteneklerinden biri olan “routing key” mekanizmasını, Python’ın Puka kütüphanesi ile nasıl kullanacağımızı detaylı bir şekilde inceleyeceğiz.
Routing Keys ve Exchange Tipleri: Derinlemesine Bakış
RabbitMQ’da mesajların doğru kuyruklara ulaşmasını sağlayan en önemli mekanizmalardan biri routing key’lerdir. Routing key, bir mesajın gönderildiği zaman atanmış bir etikettir ve exchange tarafından mesajın hangi kuyruklara yönlendirileceğini belirlemek için kullanılır.
Routing Key Nedir?
Routing key, bir mesajın “adresini” veya “kategorisini” belirten, nokta (.) ile ayrılmış kelimelerden oluşan bir dizedir (örneğin, log.error.system_a, order.processed.web). Exchange, bu routing key’i kullanarak bağlı olduğu kuyruklarla arasındaki “binding key”leri karşılaştırır ve eşleşen kuyruklara mesajı iletir.
Exchange Tipleri ve Routing Keys İlişkisi
RabbitMQ’da dört ana exchange tipi bulunur ve her birinin routing key’leri işleme şekli farklıdır:
1. Direct Exchange (Doğrudan Değiş Tokuş):
* Mesajı, routing key’i ile binding key’i tam olarak eşleşen kuyruklara gönderir.
* En basit yönlendirme tipidir. Örneğin, log.info routing key’ine sahip bir mesaj, sadece log.info binding key’i ile bağlanmış bir kuyruğa gider.
* Tek bir tüketici veya belirli bir kategoriye ait mesajları spesifik bir kuyruğa yönlendirmek için idealdir.
2. Topic Exchange (Konu Değiş Tokuş):
* Routing key’i ve binding key’i arasında “desen eşleştirme” (pattern matching) yaparak mesajları kuyruklara yönlendirir.
* Binding key’lerde iki özel karakter kullanılabilir:
(yıldız): Bir kelimeyi temsil eder. Örneğin, log.*.critical binding key’i, log.system_a.critical veya log.system_b.critical routing key’lerine sahip mesajları yakalar.
* # (kare): Sıfır veya daha fazla kelimeyi temsil eder. Örneğin, log.# binding key’i, log.info, log.error.system_a, log.debug.network.server1 gibi tüm routing key’lerine sahip mesajları yakalar.
* Esnek ve hiyerarşik mesaj yönlendirmesi gerektiğinde çok kullanışlıdır.
3. Fanout Exchange (Yayıncı Değiş Tokuş):
* Routing key’leri tamamen göz ardı eder. Kendisine gelen her mesajı, kendisine bağlı olan tüm kuyruklara kopyalayarak gönderir.
* Bir mesajı birden çok tüketiciye aynı anda iletmek (yayın yapmak) için kullanılır.
4. Headers Exchange (Başlıklar Değiş Tokuş):
* Routing key yerine, mesajın başlık (header) özelliklerine göre yönlendirme yapar.
* x-match argümanı (any veya all) ile başlık eşleştirme kuralları belirlenir.
Bu rehberde, özellikle direct ve topic exchange tiplerini kullanarak routing key’ler ile mesaj yönlendirmeye odaklanacağız, çünkü bunlar routing key’lerin gücünü en iyi gösteren tiplerdir.
Puka Kütüphanesi ile RabbitMQ Entegrasyonu
Puka, Python için hafif, asenkron ve kullanımı kolay bir RabbitMQ istemci kütüphanesidir. asyncio üzerine inşa edilmiştir, bu da onu yüksek performanslı ve eşzamanlı uygulamalar için uygun kılar.
Neden Puka?
* Asenkron: asyncio ile sorunsuz entegrasyon sayesinde blocking operasyonları minimize eder.
* Basit API: RabbitMQ’nun karmaşık AMQP protokolünü daha basit ve Pythonic bir arayüzle sunar.
* Güvenilir: Bağlantı kesilmeleri ve yeniden bağlantı gibi durumları yönetme yeteneğine sahiptir.
Kurulum
Puka’yı projenize dahil etmek için pip kullanabilirsiniz:
pip install puka
Temel Bağlantı ve Yapılandırma
Puka ile RabbitMQ’ya bağlanmak oldukça basittir. Genellikle puka.Client nesnesi oluşturulur ve connect() metodu çağrılır.
Mesaj Gönderen (Producer) Uygulaması
Aşağıdaki örnek, direct exchange kullanarak belirli bir routing key ile mesaj gönderen bir üretici uygulamasıdır.
# producer.py
import puka
import asyncio
async def publish_message():
client = puka.Client("amqp://localhost/") # RabbitMQ sunucusuna bağlan
await client.connect()
print("RabbitMQ'ya başarıyla bağlandı.")
exchange_name = "my_direct_exchange" # Kullanılacak exchange adı
routing_key = "log.info" # Mesajın routing key'i
message_body = "Bu bir bilgi mesajıdır: Sistem durumu normal."
# Exchange'i declare et (oluştur veya varsa kullan)
# type='direct' ile direct exchange olduğunu belirtiyoruz
# durable=True ile RabbitMQ yeniden başlatıldığında exchange'in kalıcı olmasını sağlıyoruz
await client.declare_exchange(exchange=exchange_name, type='direct', durable=True)
print(f"Exchange '{exchange_name}' declare edildi.")
# Mesajı yayımla
# exchange, routing_key ve mesajın kendisi (body) belirtilir
await client.publish(exchange=exchange_name, routing_key=routing_key, body=message_body.encode())
print(f"Mesaj gönderildi: '{message_body}' routing key: '{routing_key}'")
await client.close() # Bağlantıyı kapat
print("Bağlantı kapatıldı.")
if __name__ == "__main__":
asyncio.run(publish_message())
Bu kodu çalıştırdığınızda, my_direct_exchange adında bir direct exchange oluşturulacak (eğer yoksa) ve log.info routing key’i ile bir mesaj gönderilecektir.
Mesaj Alan (Consumer) Uygulaması
Şimdi de gönderilen bu mesajı alacak bir tüketici uygulaması yazalım. Bu tüketici, my_direct_exchange exchange’ine log.info binding key’i ile bağlanmış bir kuyruk oluşturacak ve bu kuyruğa gelen mesajları dinleyecektir.
# consumer.py
import puka
import asyncio
async def on_message(client, channel, delivery, body):
"""
Mesaj alındığında çağrılacak callback fonksiyonu.
"""
print(f"Mesaj alındı: '{body.decode()}'")
print(f" Exchange: {delivery.exchange}")
print(f" Routing Key: {delivery.routing_key}")
print(f" Delivery Tag: {delivery.delivery_tag}")
# Mesajı başarıyla işlediğimizi RabbitMQ'ya bildiriyoruz.
# Bu, mesajın kuyruktan silinmesini sağlar.
await client.basic_ack(delivery.delivery_tag)
async def consume_messages():
client = puka.Client("amqp://localhost/")
await client.connect()
print("RabbitMQ'ya başarıyla bağlandı.")
exchange_name = "my_direct_exchange" # Üreticinin kullandığı exchange adı
queue_name = "my_info_queue" # Bu tüketiciye özel kuyruk adı
binding_key = "log.info" # Kuyruğun exchange'e bağlanırken kullanacağı routing key
# Exchange'i declare et (üreticinin declare ettiği ile aynı olmalı)
await client.declare_exchange(exchange=exchange_name, type='direct', durable=True)
print(f"Exchange '{exchange_name}' declare edildi.")
# Kuyruğu declare et
# durable=True ile RabbitMQ yeniden başlatıldığında kuyruğun kalıcı olmasını sağlıyoruz
queue_info = await client.declare_queue(queue=queue_name, durable=True)
print(f"Kuyruk '{queue_info.queue}' declare edildi.")
# Kuyruğu exchange'e routing key ile bağla
await client.queue_bind(queue=queue_info.queue, exchange=exchange_name, routing_key=binding_key)
print(f"Kuyruk '{queue_info.queue}' exchange '{exchange_name}'e '{binding_key}' routing key'i ile bağlandı.")
# Mesajları tüketmeye başla
# no_ack=False ile mesajları manuel olarak onaylayacağımızı belirtiyoruz
await client.basic_consume(queue=queue_info.queue, no_ack=False, callback=on_message)
print(f"Mesajlar dinleniyor. Çıkmak için CTRL+C.")
# Client'ı açık tutarak mesajları dinlemeye devam et
await client.wait()
if __name__ == "__main__":
try:
asyncio.run(consume_messages())
except KeyboardInterrupt:
print("\nConsumer durduruldu.")
Önce consumer.py‘yi çalıştırın, ardından producer.py‘yi çalıştırın. Tüketici terminalinde mesajın alındığını göreceksiniz. log.error gibi farklı bir routing key ile mesaj göndermeyi denerseniz, log.info‘ya bağlı olan bu tüketici mesajı almayacaktır.
Topic Exchange ile Desen Eşleştirme
Topic exchange kullanmak için producer.py ve consumer.py dosyalarında exchange_name ve type parametrelerini değiştirmemiz yeterlidir.
Producer (topic_producer.py):
# topic_producer.py
import puka
import asyncio
async def publish_topic_message():
client = puka.Client("amqp://localhost/")
await client.connect()
print("RabbitMQ'ya başarıyla bağlandı.")
exchange_name = "my_topic_exchange"
# Farklı routing key'ler ile mesaj gönderelim
messages = [
("stock.us.nyse.ibm", "IBM hissesi yükseldi."),
("stock.eu.london.vod", "Vodafone hissesi düştü."),
("log.error.db", "Veritabanı hatası!"),
("log.info.web", "Web sunucusu bilgisi."),
("log.warn.api", "API uyarısı."),
]
await client.declare_exchange(exchange=exchange_name, type='topic', durable=True)
print(f"Topic Exchange '{exchange_name}' declare edildi.")
for routing_key, message_body in messages:
await client.publish(exchange=exchange_name, routing_key=routing_key, body=message_body.encode())
print(f"Mesaj gönderildi: '{message_body}' routing key: '{routing_key}'")
await asyncio.sleep(0.5) # Biraz bekle
await client.close()
print("Bağlantı kapatıldı.")
if __name__ == "__main__":
asyncio.run(publish_topic_message())
Consumer (topic_consumer.py):
Birden fazla tüketici oluşturup farklı binding key’lerle bağlanarak topic exchange’in gücünü gösterebiliriz.
# topic_consumer.py
import puka
import asyncio
import sys
async def on_message(client, channel, delivery, body):
print(f"[{sys.argv[1]}] Mesaj alındı: '{body.decode()}' (Routing Key: {delivery.routing_key})")
await client.basic_ack(delivery.delivery_tag)
async def consume_topic_messages(consumer_name, binding_key):
client = puka.Client("amqp://localhost/")
await client.connect()
print(f"[{consumer_name}] RabbitMQ'ya başarıyla bağlandı.")
exchange_name = "my_topic_exchange"
queue_name = f"queue_{consumer_name}" # Her tüketici için farklı bir kuyruk adı
await client.declare_exchange(exchange=exchange_name, type='topic', durable=True)
print(f"[{consumer_name}] Topic Exchange '{exchange_name}' declare edildi.")
queue_info = await client.declare_queue(queue=queue_name, durable=True)
print(f"[{consumer_name}] Kuyruk '{queue_info.queue}' declare edildi.")
await client.queue_bind(queue=queue_info.queue, exchange=exchange_name, routing_key=binding_key)
print(f"[{consumer_name}] Kuyruk '{queue_info.queue}' exchange '{exchange_name}'e '{binding_key}' ile bağlandı.")
await client.basic_consume(queue=queue_info.queue, no_ack=False, callback=on_message)
print(f"[{consumer_name}] Mesajlar dinleniyor (Binding Key: '{binding_key}'). Çıkmak için CTRL+C.")
await client.wait()
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Kullanım: python topic_consumer.py ")
print("Örnek: python topic_consumer.py all_logs 'log.#'")
print("Örnek: python topic_consumer.py us_stocks 'stock.us.#'")
print("Örnek: python topic_consumer.py error_logs 'log.error.*'")
sys.exit(1)
consumer_name = sys.argv[1]
binding_key = sys.argv[2]
try:
asyncio.run(consume_topic_messages(consumer_name, binding_key))
except KeyboardInterrupt:
print(f"\n[{consumer_name}] Consumer durduruldu.")
Şimdi farklı terminallerde bu topic_consumer.py‘yi farklı binding key’lerle çalıştırın:
* python topic_consumer.py all_logs 'log.#' (Tüm log mesajlarını alır)
python topic_consumer.py us_stocks 'stock.us.' (Sadece ABD hisse senedi mesajlarını alır)
* python topic_consumer.py error_logs 'log.error.#' (Sadece hata loglarını alır)
Ardından topic_producer.py‘yi çalıştırın. Her tüketicinin sadece kendi binding key’ine uyan mesajları aldığını göreceksiniz. Bu, topic exchange’in esnek yönlendirme gücünü gösterir.
Pratik İpuçları ve En İyi Uygulamalar
RabbitMQ ve Puka ile çalışırken uygulamanızın güvenilirliğini ve performansını artırmak için bazı pratik ipuçları:
Routing Key İsimlendirme Kuralları
* Hiyerarşik Yapı: Routing key’leri nokta (.) ile ayrılmış, anlamlı kelimelerden oluşan hiyerarşik bir yapıya sahip olacak şekilde tasarlayın (örn. product.update.inventory, user.login.failed).
* Küçük Harf Kullanımı: Tutarlılık için genellikle küçük harf ve tire/nokta ayırıcılar tercih edilir.
* Spesifikten Genele: En spesifik bilgiyi en sağa, en genel bilgiyi en sola koymak, topic exchange’lerde desen eşleştirmeyi kolaylaştırır.
Kalıcılık (Durability)
* Exchange Kalıcılığı: declare_exchange(durable=True) ile RabbitMQ yeniden başlatıldığında exchange’in kaybolmamasını sağlayın.
* Kuyruk Kalıcılığı: declare_queue(durable=True) ile kuyrukların da kalıcı olmasını sağlayın.
* Mesaj Kalıcılığı: publish(properties={'delivery_mode': 2}) ile mesajların diskte kalıcı olmasını sağlayın. Bu, RabbitMQ çökse bile mesajların kaybolmamasını garantiler. Ancak performans maliyeti olabilir.
Mesaj Onaylama (Acknowledgements)
* Tüketiciler, bir mesajı başarıyla işlediklerinde RabbitMQ’ya bir ack (acknowledgement) göndermelidir. client.basic_ack(delivery.delivery_tag) ile yapılır.
* Eğer bir mesaj işlenemezse, nack (negative acknowledgement) gönderilebilir. Bu, mesajın başka bir tüketiciye yeniden teslim edilmesini veya bir “dead-letter queue”ya yönlendirilmesini sağlayabilir.
* no_ack=False kullanmak, mesajların kaybolmamasını garanti altına alır. Tüketici çökse bile, işlenmemiş mesajlar kuyrukta kalır ve başka bir tüketici tarafından alınabilir.
Hata Yönetimi ve Yeniden Denemeler
* Mesaj işleme sırasında oluşan hataları yakalayın. Hatalı mesajlar için yeniden deneme mekanizmaları veya “dead-letter queue” (ölü harf kuyruğu) kullanarak bu mesajları ayrı bir kuyruğa yönlendirin. Bu, ana iş akışının tıkanmasını önler.
Ölçeklenebilirlik
* Birden çok tüketici aynı kuyruğa bağlanabilir. RabbitMQ, mesajları bu tüketiciler arasında Round-Robin (sırayla) dağıtarak iş yükünü dengeleyecektir. Bu, uygulamanızın yatayda ölçeklenmesini sağlar.
Bağlantı Yönetimi
* Puka, asenkron yapısı sayesinde bağlantı kesintilerini daha iyi yönetir. Ancak yine de uygulamanızın yeniden bağlantı mantığını (retry logic) uygun şekilde kurduğundan emin olun.
Sonuç ve Sıkça Sorulan Sorular (SSS)
Bu rehberde, RabbitMQ’nun routing key mekanizmasını Puka Python kütüphanesi ile nasıl kullanacağımızı detaylı bir şekilde inceledik. Direct ve Topic exchange’ler aracılığıyla mesajların belirli kurallara göre nasıl yönlendirileceğini öğrendik. Routing key’ler, mesaj tabanlı mimarilerde esnek, güçlü ve ölçeklenebilir bir iletişim altyapısı kurmak için vazgeçilmez bir araçtır. Puka’nın asenkron yapısı ise Python uygulamalarınızda yüksek performanslı mesajlaşma çözümleri geliştirmenize olanak tanır.
Sıkça Sorulan Sorular (SSS)
S: Routing key ve binding key arasındaki fark nedir?
C: Routing key, bir mesajın gönderildiği zaman atanmış etikettir. Binding key ise bir kuyruğun bir exchange’e bağlanırken kullandığı filtredir. Exchange, mesajın routing key’ini, bağlı kuyrukların binding key’leriyle karşılaştırarak mesajı doğru kuyruğa yönlendirir.
S: Hangi exchange tipini kullanmalıyım?
C:
* Direct Exchange: Mesajı, routing key’i ile binding key’i tam olarak eşleşen tek bir veya belirli bir kategoriye ait kuyruğa göndermek istediğinizde.
* Topic Exchange: Esnek desen eşleştirme ile hiyerarşik routing key’lere sahip mesajları birden çok farklı kritere göre kuyruklara yönlendirmek istediğinizde.
* Fanout Exchange: Routing key’i göz ardı ederek gelen her mesajı tüm bağlı kuyruklara yayınlamak istediğinizde.
* Headers Exchange: Mesajın başlıklarına göre yönlendirme yapmak istediğinizde.
S: Puka yerine başka Python kütüphaneleri var mı?
C: Evet, Python için popüler RabbitMQ kütüphaneleri arasında pika (hem senkron hem de asenkron kullanım seçenekleri sunar) ve aio-pika (Puka gibi asyncio tabanlı, modern ve yaygın kullanılan bir diğer kütüphane) bulunmaktadır. Seçim, projenizin gereksinimlerine ve tercih ettiğiniz asenkron programlama stiline göre değişebilir. Puka, basitliği ve asyncio entegrasyonu ile iyi bir seçenektir.
S: Mesajların kaybolmamasını nasıl sağlarım?
C: Exchange’leri ve kuyrukları durable=True ile kalıcı yapın. Mesajları delivery_mode=2 (persistent) ile gönderin. Tüketicilerde no_ack=False kullanarak mesajları başarıyla işledikten sonra basic_ack ile onaylayın.