Redis Pub/Sub, yayımlayıcıların (publisher) mesajlarını belirli alıcılara değil kanallara gönderdiği, abonelerin (subscriber) ise ilgilendikleri kanallara abone olup yalnızca o kanallardaki mesajları aldığı bir mesajlaşma modelidir. Yayımlamak için PUBLISH kanal mesaj, dinlemek için SUBSCRIBE kanal ya da kalıpla PSUBSCRIBE kalıp kullanılır. Mesajlar saklanmaz: Redis bir mesajı o anda bağlı olan abonelere iletir ve unutur. Belgelerin ifadesiyle teslim semantiği "en fazla bir kez"dir (at-most-once); bağlı olmayan abone o mesajı bir daha alamaz.
Bu yazıda abonelik durumunun kurallarını, mesaj biçimlerini, kalıp abonelikleri, PUBSUB ile izlemeyi, Redis Cluster için sharded Pub/Sub komutlarını, Pub/Sub ile Streams arasındaki seçimi ve redis-py ile kullanımı belgelerdeki örneklerle anlatıyoruz. Pub/Sub'ın genel kullanım senaryolarına giriş için Redis temelleri yazımıza, serinin tamamı için Redis öğrenme rehberi sayfasına bakın.
Yayımlayıcı ve abone neden birbirini tanımaz?

Pub/Sub'ın temel fikri ayrıştırmadır. Yayımlayıcı hangi abonelerin olduğunu, hatta abone olup olmadığını bilmez; abone de hangi yayımlayıcıların olduğunu bilmez. Mesajlar kanallara göre sınıflanır. Bu ayrıştırma, belgelere göre daha iyi ölçeklenebilirlik ve daha dinamik bir ağ yapısı sağlar: yeni bir tüketici eklemek yayımlayıcının koduna dokunmayı gerektirmez. Kullanım senaryosu sayfası tipik örnekleri sayar: bildirimler, sohbet mesajları, önbellek geçersiz kılma sinyalleri, arayüz güncellemeleri ve birden fazla sunucu düğümüne dağılmış WebSocket istemcilerine anlık güncelleme gönderme.
SUBSCRIBE ve PUBLISH
Bir istemci channel11 ve ch:00 kanallarına abone olmak için kanal adlarını vererek SUBSCRIBE gönderir:
SUBSCRIBE channel11 ch:00Başka istemcilerin bu kanallara gönderdiği mesajlar Redis tarafından tüm abonelere itilir (push) ve aboneler mesajları yayımlanma sırasıyla alır. PUBLISH tamsayı yanıt döndürür: mesajın gönderildiği istemci sayısı. Bir Redis Cluster'da bu sayıya yalnızca yayımlayıcıyla aynı düğüme bağlı istemciler dahildir. Kullanım senaryosu sayfası bu sayıyı pratik bir sağlık kontrolü olarak önerir: sürekli 0 görüyorsanız kanal adında bir yazım hatası vardır ya da aboneniz çökmüştür.
Abonelik durumunda hangi komutlar çalışır?
RESP2 kullanan bir istemci bir veya daha fazla kanala abone olduktan sonra başka komut göndermemelidir; yalnızca SUBSCRIBE, SSUBSCRIBE, PSUBSCRIBE, UNSUBSCRIBE, SUNSUBSCRIBE, PUNSUBSCRIBE, PING, RESET ve QUIT komutlarına izin verilir. RESP3 kullanılıyorsa (HELLO komutuyla seçilir) istemci abonelik durumundayken de her komutu gönderebilir. Bu kural pratikte şu anlama gelir: abonelik için uygulamanızda ayrı bir bağlantı kullanın. redis-py belgeleri de bir PubSub nesnesinin bağlantıyı yalnızca abonelik moduna aldığını, aynı bağlantıda GET veya HSET gibi sıradan komutların takılacağını belirtir.
İtilen mesajların biçimi
Abone olan istemciye gelen her mesaj üç elemanlı bir dizidir ve ilk eleman mesajın türünü söyler. subscribe türü, ikinci elemandaki kanala başarıyla abone olunduğunu, üçüncü eleman ise o anda abone olunan toplam kanal sayısını bildirir. unsubscribe türü aboneliğin bırakıldığını bildirir; son eleman sıfır olduğunda istemci artık hiçbir kanala abone değildir ve her komutu gönderebilir. message türü ise başka bir istemcinin PUBLISH ile gönderdiği gerçek mesajdır: ikinci eleman kaynak kanal, üçüncü eleman mesajın içeriğidir.
Belgelerdeki protokol örneğinde A istemcisi first ve second kanallarına abone olur, B istemcisi PUBLISH second Hello gönderir. A'nın soketinden okunan mesaj şudur:
*3
$7
message
$6
second
$5
HelloArgümansız UNSUBSCRIBE tüm kanallardan çıkar ve her kanal için ayrı bir unsubscribe mesajı gelir; son mesajdaki sayı :0 olduğunda istemci Pub/Sub durumundan çıkmıştır.
Kalıp abonelikleri: PSUBSCRIBE

Redis Pub/Sub glob tarzı kalıpları destekler. PSUBSCRIBE news.* komutu news.art.figurative, news.music.jazz gibi kalıba uyan tüm kanallara gönderilen mesajları alır; birden fazla joker karakter de kullanılabilir. Komut sayfasındaki örneklere göre h?llo hello, hallo ve hxllo ile; h*llo hllo ve heeeello ile; h[ae]llo ise hello ve hallo ile eşleşir ama hillo ile eşleşmez. Özel karakterleri olduğu gibi eşlemek için ters eğik çizgi kullanılır.
Kalıp eşleşmesiyle gelen mesajların türü pmessage'dır ve tür adından sonra üç eleman daha taşır: eşleşen kalıp, kaynak kanal ve mesaj içeriği. Burada ince bir nokta vardır: bir istemci hem SUBSCRIBE foo hem PSUBSCRIBE f* yaptıysa, foo kanalına gönderilen tek bir mesajı iki kez alır, biri message diğeri pmessage olarak. Birden fazla kalıp aynı kanala uyuyorsa da durum aynıdır. Abonelik onay mesajlarındaki sayı ise kanal ve kalıp aboneliklerinin toplamıdır; istemci yalnızca bu sayı sıfıra indiğinde Pub/Sub durumundan çıkar.
Kalıplar her yayımlanan mesaj için değerlendirildiği için kullanım senaryosu sayfası, sık kullanılan yollarda birden çok joker karakterden kaçınmayı önerir: *:*:* her şeyle eşleşir ve düz bir notifications:* kalıbından daha pahalıdır. Aynı sayfa, iki nokta ile ayrılmış hiyerarşik kanal adlarını (notifications:billing:invoice gibi) baştan seçmeyi tavsiye eder; böylece faturalama servisi notifications:billing:*, denetim kaydı servisi ise notifications:* dinleyebilir.
Veritabanı numarası ve kapsam
Pub/Sub'ın anahtar uzayıyla (keyspace) hiçbir ilişkisi yoktur ve hiçbir düzeyde ona karışmaz; veritabanı numaraları da buna dahildir. Belgelerin örneğiyle, 10 numaralı veritabanında yayımlanan bir mesajı 1 numaralı veritabanındaki bir abone de duyar. Ortamları ayırmanız gerekiyorsa kanal adlarına ortamın adını ön ek olarak ekleyin (test, staging, production gibi). Aynı nedenle KEYS veya SCAN kanalları göstermez; anahtar adlandırma ve tarama için keyspace yazımıza bakabilirsiniz.
PUBSUB ile aktif kanalları izlemek
Pub/Sub durumunu görmek için PUBSUB alt komutları kullanılır. PUBSUB CHANNELS [kalıp] en az bir abonesi olan aktif kanalları listeler; kalıpla abone olan istemciler bu listeye dahil değildir. PUBSUB NUMSUB kanal ... her kanal için abone sayısını kanal adı ve sayı çiftleri halinde döndürür; kanal verilmezse boş liste döner. PUBSUB NUMPAT ise istemcilerin abone olduğu benzersiz kalıp sayısını verir; bu, kalıba abone olan istemci sayısı değil, toplam benzersiz kalıp sayısıdır. Cluster'da bu komutların yanıtları yalnızca bağlandığınız düğümün bilgisini yansıtır.
redis-cli pubsub channels '*'
redis-cli pubsub numsub orders:new notifications:billing chat:lobby
redis-cli pubsub numpat
redis-cli psubscribe 'orders:*'Pub/Sub mi, Streams mi?

Pub/Sub'ın en önemli sınırı teslim garantisidir. Sunucu bir mesajı gönderdikten sonra onu bir daha göndermez; abone hata yüzünden mesajı işleyemezse ya da ağ bağlantısı koparsa mesaj sonsuza dek kaybolur. Belgeler daha güçlü garanti gerekiyorsa Redis Streams'e bakmayı önerir: Stream mesajları saklanır ve hem en fazla bir kez hem de en az bir kez (at-least-once) teslim desteklenir.
| Özellik | Pub/Sub | Streams |
|---|---|---|
| Mesaj saklama | Yok, iletildikten sonra atılır | Var, stream anahtarında durur |
| Teslim semantiği | En fazla bir kez | En fazla bir kez veya en az bir kez |
| Çevrimdışı abone | Kaçırdığı mesajları alamaz | Kaldığı yerden okuyabilir |
| Keyspace ile ilişki | Yok, veritabanı numarasından bağımsız | Stream bir anahtardır |
| Tipik kullanım | Anlık yayın, bildirim, geçersiz kılma sinyali | İş akışı, olay günlüğü, tüketici grupları |
Kullanım senaryosu sayfasının önerisi iki yaklaşımı birlikte kullanmaktır: kaybedilmemesi gereken olayın kalıcı kaydını (sipariş satırı, önbellek anahtarının sürümü, denetim kaydı) önce birincil depoya yazın, ardından canlı tüketicilerin hemen haberdar olması için PUBLISH ile bildirim gönderin. Yeniden bağlanan tüketici kaçırdığı mesajları beklemek yerine kalıcı depoyu okuyarak durumunu eşitler. Tekrar oynatma ya da en az bir kez teslim gerçekten gerekiyorsa Redis Streams yazımıza ve tüketici grupları için XREADGROUP yazımıza bakın.
redis-py ile abone olmak
Kullanım senaryosunun redis-py örneği, abonelik için pubsub() ile bir PubSub nesnesi oluşturur, kanallara geri çağırma fonksiyonları bağlar ve mesajları arka planda bir iş parçacığında dağıtır. Belgedeki parça şöyledir:
self._pubsub = hub.redis.pubsub(ignore_subscribe_messages=True)
bindings = {target: self._dispatch for target in self.targets}
if is_pattern:
self._pubsub.psubscribe(**bindings)
else:
self._pubsub.subscribe(**bindings)
self._thread = self._pubsub.run_in_thread(sleep_time=0.01, daemon=True)Burada üç ayrıntı önemlidir. ignore_subscribe_messages=True, Redis'in aynı sokete gönderdiği subscribe ve unsubscribe onaylarını atlar; böylece geri çağırma yalnızca gerçek mesajları görür. Her abonelik kendi PubSub nesnesine, dolayısıyla kendi bağlantısına sahiptir; tek bağlantıyı paylaşmak abonelerin ömürlerini birbirine bağlar. run_in_thread ise listen() üzerinde sonsuza dek bloklanmak yerine kısa aralıklı bir yoklama döngüsü çalıştırır, bu da aboneliği temiz biçimde kapatmayı kolaylaştırır. Kalıp aboneliğinde geri çağırmaya gelen sözlükte channel mesajın gönderildiği gerçek kanalı, pattern eşleşen kalıbı, data ise içeriği taşır.
Yayımlama tarafı basittir: belgedeki yardımcı mesajı JSON'a çevirip self.redis.publish(channel, payload) çağırır ve dönen teslim sayısını sayaçlarına ekler. Sayfanın üretim önerilerinden biri de geri çağırma içinde ağır iş yapmamaktır: dağıtım iş parçacığı mesajları tek soketten okur, geri çağırma bloklanırsa sonraki mesaj onun arkasında bekler. Ağır işi bir işçi havuzuna, bir kuyruğa ya da kalıcı teslim için bir Streams tüketici grubuna devredin. redis-py bağlantı ayrıntıları için redis-py yazımıza bakın.
Betikler, anahtar bildirimleri ve istemci önbelleği
Pub/Sub başka özelliklerin de taşıyıcısıdır. Anahtar değişikliklerini (bir anahtarın yazılması, süresinin dolması ya da bellekten çıkarılması) aynı kanal altyapısıyla almak için keyspace notifications yazımıza bakın. RESP2 bağlantılarında istemci tarafı önbelleğin geçersiz kılma mesajları da Pub/Sub üzerinden gelir; bu konu client-side caching yazımızda anlatılıyor. Lua betikleri ve fonksiyonlar içinden PUBLISH çağırabilirsiniz, ancak salt okunur betiklerde PUBLISH ve SPUBLISH yazma komutu sayılır; ayrıntı Lua yazımızda.
Sık yapılan hatalar
- Pub/Sub'ı kalıcı bir kuyruk sanmak: bağlı olmayan abone mesajı kaçırır. Kaybedilmemesi gereken iş için Streams kullanın.
- Abone bağlantısında normal komut çalıştırmak: RESP2'de abonelik durumundaki bağlantı yalnızca abonelik komutlarını kabul eder.
- Veritabanı numarasıyla ortam ayırmaya çalışmak: Pub/Sub veritabanından bağımsızdır, kanal adına ön ek ekleyin.
- Hem kanal hem kalıp aboneliği yapıp aynı mesajı iki kez işlemek: message ve pmessage ayrı ayrı gelir.
- PUBSUB CHANNELS ile kalıp abonelerini aramak: kalıp aboneleri bu listede görünmez, PUBSUB NUMPAT kullanın.
- Cluster'da yoğun trafiği klasik PUBLISH ile taşımak: sharded Pub/Sub mesajları yalnızca ilgili shard içinde tutar.
Sık Sorulan Sorular
Abone yokken yayımlanan mesaj ne olur?
Kaybolur. PUBLISH o anda abone olan istemci sayısını döndürür; yanıt 0 ise mesaj kimseye ulaşmamıştır ve Redis onu saklamaz.
Mesajların sırası korunur mu?
Belgelere göre aboneler mesajları yayımlanma sırasıyla alır. Ancak teslim en fazla bir kez olduğu için kopan bir bağlantıda aradaki mesajlar eksik kalır.
Cluster'da PUBLISH herhangi bir düğüme gönderilebilir mi?
Evet. PUBLISH sayfasına göre istemciler her düğümde yayımlayabilir ve cluster mesajları gerektiği gibi iletir; aboneler de herhangi bir düğüme bağlanabilir. SPUBLISH ise kanalın slotunun sahibi olan düğüme gönderilmelidir.
RESP3 kullanırsam ayrı bağlantıya gerek kalır mı?
RESP3'te abonelik durumundaki istemci her komutu gönderebilir. Yine de istemci kütüphanenizin bu modu nasıl desteklediğine bakın; redis-py örneği her abonelik için ayrı bir PubSub nesnesi ve bağlantı kullanmayı önerir.
Marka adları sahiplerine aittir. Bu yazı bağımsız bir teknik rehberdir; adı geçen kuruluşlarla bayilik veya ortaklık ilişkisi bulunduğu anlamına gelmez.
İlgili içerikler: Redis öğrenme rehberi · Redis keyspace notifications · Redis Streams · Redis consumer group · Redis client-side caching · Redis Cluster