Pub/Sub: рассылка событий в реальном времени
Pub/Sub превращает Redis в брокер сообщений: издатели шлют события в каналы, подписчики мгновенно их получают. Никто никого не знает напрямую.
Издатель кричит в канал, не зная, кто слушает. Подписчики слышат, не зная, кто кричал. Эта развязанность — суть паблиш-сабскрайб.
Publish/Subscribe (Pub/Sub) — модель обмена сообщениями, где отправители (publishers) шлют сообщения в именованные каналы, а получатели (subscribers) подписываются на каналы и получают всё, что туда приходит. Отправители и получатели не знают друг о друге — это слабая связанность.
Команды
# Терминал 1 — подписчик
SUBSCRIBE news
# теперь ждёт сообщений из канала news
# Терминал 2 — издатель
PUBLISH news "Вышла новая версия!"
(integer) 1 # число получивших подписчиков
# Подписка по шаблону
PSUBSCRIBE news.* # все каналы news.sport, news.tech ...
SUBSCRIBE подписывает на каналы, PUBLISH шлёт сообщение (возвращает число доставленных подписчиков), PSUBSCRIBE подписывает по шаблону с подстановкой.
Схема Pub/Sub
Издатель Подписчики
-------- ----------
PUBLISH news "..." --> [канал: news] --> подписчик A
--> подписчик B
PUBLISH chat "..." --> [канал: chat] --> подписчик C
Сообщение получают ВСЕ подписчики канала одновременно.
Кто не подписан в момент отправки — НЕ получит ничего.
Ключевое свойство: fire-and-forget
Pub/Sub доставляет сообщение только тем, кто подписан прямо сейчас. Сообщения не хранятся. Если подписчик отключился — он пропустит всё, что было в это время. Это «выстрелил и забыл»: быстро, но без гарантий доставки.
Демонстрация: простой Pub/Sub на словаре подписок
# Моделируем брокер Pub/Sub: канал -> список подписчиков
channels = {}
def subscribe(channel, name, inbox):
channels.setdefault(channel, []).append((name, inbox))
print(f"{name} подписался на '{channel}'")
def publish(channel, message):
subs = channels.get(channel, [])
for name, inbox in subs:
inbox.append(message) # доставляем КАЖДОМУ подписчику
print(f"PUBLISH '{channel}': '{message}' -> доставлено {len(subs)}")
return len(subs)
# Подписчики с личными "ящиками"
alice_inbox, bob_inbox, carol_inbox = [], [], []
subscribe("news", "Alice", alice_inbox)
subscribe("news", "Bob", bob_inbox)
subscribe("chat", "Carol", carol_inbox)
print()
publish("news", "Релиз 2.0!") # получат Alice и Bob
publish("chat", "Привет!") # получит только Carol
publish("sport", "Гол!") # подписчиков нет -> 0
print("\nЯщик Alice:", alice_inbox)
print("Ящик Carol:", carol_inbox)
print("Сообщение 'sport' потеряно — никто не слушал (fire-and-forget).")
Обратите внимание: сообщение в канал sport исчезло бесследно — подписчиков не было. Это и есть природа Pub/Sub: без подписчика в моменте сообщение теряется.
Как работает под капотом
Внутри Redis держит словарь «канал → множество подписавшихся клиентов». При PUBLISH он проходит по этому множеству и проталкивает сообщение в сокет каждого подписчика синхронно. Для PSUBSCRIBE хранится отдельный список шаблонов, которые матчатся при каждой публикации. Никакого хранения, очередей и подтверждений — отсюда и скорость, и отсутствие гарантий.
Частые ошибки
- Ждать гарантированной доставки. Pub/Sub теряет сообщения для отключённых подписчиков. Нужна надёжность — это Streams (следующий урок).
- Тяжёлая обработка в подписчике. Медленный подписчик копит буфер на сервере; разгружайте обработку.
- Использовать для очередей задач. Pub/Sub рассылает всем, а очередь должна отдавать задачу одному воркеру.
Best practices
- Используйте Pub/Sub для мгновенных уведомлений живым клиентам: чаты, нотификации, обновления дашбордов.
- Если важна доставка пропущенного — берите Redis Streams.
- Держите обработчики подписчиков лёгкими; тяжёлую работу выносите в отдельную очередь.
Итог: Pub/Sub — рассылка событий в реальном времени по каналам со слабой связанностью издателей и подписчиков. Это fire-and-forget: быстро, но без хранения и гарантий. Идеален для уведомлений, не подходит для надёжных очередей.
Паттерны на Pub/Sub и его пределы
Несмотря на простоту, Pub/Sub лежит в основе многих реактивных фич. Понимание его сильных и слабых сторон помогает выбрать его осознанно.
- Живые уведомления. Новое сообщение в чате, обновление статуса заказа, всплывающее оповещение — всё это естественно ложится на Pub/Sub: событие нужно доставить тем, кто онлайн прямо сейчас.
- Инвалидация кэша между узлами. Один сервер изменил данные и публикует событие «инвалидируй ключ X»; остальные узлы, подписанные на канал, чистят свои локальные кэши.
- Фан-аут событий. Одно событие нужно разослать многим независимым обработчикам сразу — Pub/Sub делает это одной командой.
Но границы важны не меньше возможностей. Pub/Sub не хранит историю, не гарантирует доставку и не умеет распределять задачу одному воркеру из группы. Если подписчик отключился — он навсегда пропустил то, что было в это время. Поэтому как только появляется требование «не потерять ни одного события» или «обработать каждое ровно одним воркером», нужно переходить на Redis Streams, о которых пойдёт речь дальше. Pub/Sub — про скорость и простоту, Streams — про надёжность.