Учебный проект демонстрирует работу Redis Pub/Sub через простой веб-интерфейс: пользователь публикует сообщения в Redis-каналы, включает и выключает подписчиков и сразу видит, кто получил сообщение, а для кого оно было потеряно.
Главная идея проекта - показать, что Redis Pub/Sub работает как система мгновенной доставки событий, а не как очередь. Если подписчик был offline во время публикации, Redis не сохранит сообщение для него и не доставит его позже.
Проект помогает наглядно разобрать базовые принципы Redis Pub/Sub:
- сообщение публикуется в конкретный канал через
PUBLISH; - обычные подписчики получают сообщения через
SUBSCRIBE; - pattern-подписчик получает сообщения через
PSUBSCRIBE; - сообщение получают только те подписчики, которые сейчас online и подписаны на подходящий канал;
- offline-подписчики не получают старые сообщения после возвращения online;
- frontend обновляется без перезагрузки страницы через WebSocket.
Интерфейс специально сделан как симулятор: сверху показаны каналы Redis, ниже подписчики, затем форма публикации, кнопки переключения online/offline и лог событий.
На странице есть три основных блока.
Каналы
В блоке каналов отображаются:
- имя канала;
- количество активных подписчиков на этот канал;
- последние опубликованные сообщения.
Каналы в проекте:
chat:room42- общий чат;notifications:user1- личные уведомления пользователя;cache:invalidate- события очистки кеша.
Подписчики
Каждый подписчик отображается отдельной карточкой. В карточке видно:
- имя подписчика;
- статус
onlineилиoffline; - список обычных или pattern-подписок;
- последнее полученное сообщение;
- общее количество полученных сообщений.
Лог событий
Лог показывает, что происходило в системе:
[16:53:25] PUBLISH chat:room42 "Привет!" -> 4 получателя
[16:54:35] PUBLISH cache:invalidate "Событие B" -> 1 получатель
[16:55:10] Sub D: UNSUBSCRIBE offline
Если сообщение не получил никто, в логе видно, что оно потеряно:
PUBLISH cache:invalidate "clear" -> 0 получателей, сообщение потеряно
Когда пользователь нажимает PUBLISH, происходит такой путь:
- Браузер отправляет
POST /api/publishна FastAPI backend. - Backend проверяет канал и текст сообщения.
- Backend формирует JSON payload с
id,channel,text,created_atиpublisher. - Backend публикует payload в Redis через команду
PUBLISH. - Активные подписчики, у которых есть
SUBSCRIBEилиPSUBSCRIBE, получают событие из Redis. - Backend обновляет состояние подписчиков.
- Backend отправляет новое состояние и лог в браузер через WebSocket
/ws. - Интерфейс перерисовывает каналы, подписчиков и лог.
Важно: backend не хранит очередь сообщений. В проекте есть только демонстрационное состояние интерфейса: счетчики, последние сообщения и лог.
Redis Pub/Sub не хранит сообщения.
Если подписчик был offline в момент публикации, сообщение для него потеряно. После возврата online Redis не доставит старые публикации.
Это ключевое отличие от очередей. В очередях или Redis Streams сообщение может храниться до обработки. В Pub/Sub сообщение существует только в момент публикации и доставляется только текущим активным подписчикам.
Для гарантированной доставки лучше использовать:
- Redis Streams;
- очереди задач;
- RabbitMQ;
- Kafka;
- другие брокеры сообщений с подтверждением доставки.
В этом проекте они специально не используются, чтобы не смешивать Pub/Sub с очередями.
redis_pub_sub/
├── app/
│ ├── main.py # FastAPI-приложение, главная страница и WebSocket
│ ├── config.py # настройки приложения
│ ├── redis_client.py # создание Redis-клиента
│ ├── schemas.py # Pydantic-схемы API
│ ├── api/
│ │ ├── publish.py # endpoint публикации сообщений
│ │ └── subscribers.py # состояние, toggle, clear
│ ├── pubsub/
│ │ ├── manager.py # тонкий фасад для API и main.py
│ │ ├── service.py # координация Pub/Sub-компонентов
│ │ ├── publisher.py # Redis PUBLISH и формирование payload
│ │ ├── lifecycle.py # startup/shutdown/toggle подписчиков
│ │ ├── events.py # обработка доставленных Redis-сообщений
│ │ ├── logging.py # события лога и подписи количества получателей
│ │ ├── subscriber.py # отдельный Redis Pub/Sub подписчик
│ │ └── patterns.py # проверка обычных и pattern-подписок
│ ├── simulator/
│ │ ├── state.py # состояние интерфейса, счетчики, лог, аналитика каналов
│ │ └── websocket.py # подключение и broadcast WebSocket-клиентов
│ └── static/
│ └── index.html # HTML/CSS/JavaScript интерфейс
├── docker-compose.yml
├── Dockerfile
├── requirements.txt
├── Makefile
└── README.md
| Подписчик | Стартовый статус | Подписки | Что получает |
|---|---|---|---|
Sub A |
online | chat:room42, notifications:user1 |
сообщения чата и уведомления |
Sub B |
online | chat:room42 |
только сообщения чата |
Sub C |
online | chat:* |
все каналы, подходящие под pattern chat:* |
Sub D |
offline | chat:room42, cache:invalidate |
чат и cache-события, но только когда online |
Sub C использует pattern-подписку. Он получает сообщения из chat:room42, потому что канал подходит под шаблон chat:*, но не получает сообщения из notifications:user1 и cache:invalidate.
Через Docker:
docker compose up --buildВ текущем docker-compose.yml web-приложение проброшено на порт 8010, потому что на машине уже были заняты 8000 и 8001.
Откройте:
http://localhost:8010
Локально без Docker:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
redis-server
uvicorn app.main:app --reloadПри локальном запуске откройте:
http://localhost:8000
Если Redis запущен по другому адресу, задайте переменную окружения:
REDIS_URL=redis://localhost:6379/0 uvicorn app.main:app --reloadRedis-клиенты создаются через общий async connection pool. Размер пула и health-check можно менять переменными:
REDIS_MAX_CONNECTIONS=20
REDIS_HEALTH_CHECK_INTERVAL=30- Выберите канал
chat:room42. - Введите сообщение
Привет!. - Нажмите
PUBLISH.
Ожидаемый результат:
Sub Aполучит сообщение;Sub Bполучит сообщение;Sub Cполучит сообщение через patternchat:*;Sub Dполучит сообщение только если он online.
- Выберите канал
notifications:user1. - Отправьте сообщение
У вас новое уведомление.
Ожидаемый результат:
- сообщение получит только
Sub A; Sub B,Sub CиSub Dего не получат.
- Включите
Sub D, если он offline. - Выберите канал
cache:invalidate. - Отправьте сообщение
clear user:42.
Ожидаемый результат:
- сообщение получит
Sub D; - остальные подписчики его не получат.
- Выключите
Sub D. - Выберите канал
cache:invalidate. - Отправьте сообщение
clear user:42. - Включите
Sub Dобратно.
Ожидаемый результат:
- в момент публикации будет
0 получателей; - в логе будет видно, что сообщение потеряно;
- после возвращения online
Sub Dне получит старое сообщение.
Так проявляется главное свойство Redis Pub/Sub: это не очередь.
| Метод | Endpoint | Назначение |
|---|---|---|
GET |
/ |
HTML-интерфейс |
GET |
/api/state |
состояние каналов, подписчиков и лога |
POST |
/api/publish |
публикация одного сообщения |
POST |
/api/publish/batch |
пакетная публикация 5 сообщений |
POST |
/api/subscribers/{subscriber_id}/toggle |
переключение подписчика online/offline |
POST |
/api/clear |
сброс счетчиков, последних сообщений, визуального списка сообщений и лога |
WS |
/ws |
WebSocket-события для обновления интерфейса |
Пример публикации:
curl -X POST http://localhost:8010/api/publish \
-H "Content-Type: application/json" \
-d '{"channel":"chat:room42","message":"Привет!"}'Пример ответа:
{
"status": "published",
"channel": "chat:room42",
"message": "Привет!",
"delivered_to": 3
}В проекте используются основные команды Redis Pub/Sub:
PUBLISH channel message- публикация сообщения в канал;SUBSCRIBE channel- подписка на конкретный канал;PSUBSCRIBE pattern- подписка на каналы по шаблону;UNSUBSCRIBE channel- отмена обычной подписки;PUNSUBSCRIBE pattern- отмена pattern-подписки.
В проекте нет:
- Redis Streams;
- Celery;
- RabbitMQ;
- Kafka;
- базы данных;
- авторизации;
- регистрации пользователей;
- очередей задач;
- хранения истории сообщений для offline-подписчиков.
Это сделано специально: цель проекта - показать именно Redis Pub/Sub и его ограничение по доставке offline-подписчикам.