Налаштування Redis для Pub/Sub сповіщень
Redis Pub/Sub — це механізм доставки повідомлень у режимі "fire and forget". Брокер не зберігає історію: якщо підписник не слухає в момент публікації, повідомлення втрачається. Це відрізняє його від Redis Streams і робить придатним для коротких real-time сповіщень — оповіщень про події, інвалідації кешу, синхронізації стану між кількома екземплярами сервісу.
Коли використовувати Pub/Sub проти черги
Pub/Sub розв'язує задачу broadcast: один видавець, кілька підписників, всі отримують одне й те саме повідомлення одночасно. Якщо потрібна гарантована доставка, історія повідомлень або групи споживачів — Redis Streams або RabbitMQ.
Типовий випадок для сайту: користувач виконує дію → бекенд публікує подію в канал → WebSocket-сервер отримує подію і рассилає push всім підключеним клієнтам відповідної "кімнати". Без Pub/Sub горизонтальне масштабування WebSocket-серверів неможливе: кожен екземпляр знає лише про свої підключення.
Базова налаштування
Redis підтримує Pub/Sub з коробки без конфігурації. Але варто виставити кілька параметрів у redis.conf:
# Лімітування пам'яті — критично, якщо Redis використовується як кеш
maxmemory 512mb
maxmemory-policy allkeys-lru
# Кількість баз — однієї логічної БД достатньо для prod
databases 16
# Відключити persistence для чистого pub/sub-брокера
save ""
appendonly no
Для production Redis повинен бути в режимі Sentinel або Cluster. Pub/Sub у Cluster має обмеження: повідомлення поширюються лише в межах шарду, якщо не використовувати SPUBLISH/SSUBSCRIBE (Sharded Pub/Sub, Redis 7+).
Реалізація на Node.js з ioredis
import Redis from 'ioredis';
const publisher = new Redis({ host: 'redis', port: 6379 });
const subscriber = new Redis({ host: 'redis', port: 6379 });
// Підписник — окреме з'єднання, заблоковане на слуханні
subscriber.subscribe('notifications:user:*', (err, count) => {
if (err) throw err;
console.log(`Subscribed to ${count} channels`);
});
subscriber.on('pmessage', (pattern, channel, message) => {
// channel = "notifications:user:42"
const userId = channel.split(':')[2];
const payload = JSON.parse(message);
broadcastToUser(userId, payload);
});
// Публікація звідусіль
async function notifyUser(userId: string, event: object) {
const channel = `notifications:user:${userId}`;
const count = await publisher.publish(channel, JSON.stringify(event));
// count — кількість підписників, які отримали повідомлення
return count;
}
Важливо: publisher і subscriber — різні з'єднання. Після виклику subscribe/psubscribe з'єднання переходить у спеціальний режим і приймає лише команди SUBSCRIBE, UNSUBSCRIBE, PING, RESET, QUIT.
Pattern-matching підписки
PSUBSCRIBE підтримує глоб-шаблони: * (будь-які символи), ? (один символ), [chars] (набір символів). Корисно для підписки на цілий клас каналів:
// Всі события конкретного тенанту
subscriber.psubscribe('tenant:acme:*');
// Конкретний тип события для всіх користувачів
subscriber.psubscribe('*:message:new');
Інтеграція з WebSocket (Socket.io)
Класична схема масштабування Socket.io через Redis Adapter:
import { createServer } from 'http';
import { Server } from 'socket.io';
import { createAdapter } from '@socket.io/redis-adapter';
import { createClient } from 'redis';
const httpServer = createServer();
const io = new Server(httpServer);
const pubClient = createClient({ url: 'redis://redis:6379' });
const subClient = pubClient.duplicate();
await Promise.all([pubClient.connect(), subClient.connect()]);
io.adapter(createAdapter(pubClient, subClient));
// Тепер io.to('room').emit() працює на всіх екземплярах сервера
httpServer.listen(3000);
Redis Adapter використовує Pub/Sub всередині: при виклику io.to('room').emit() на одному екземплярі команда публікується в Redis-канал, і всі інші екземпляри її отримують і рассилають своїм підключеним клієнтам з цієї кімнати.
Інтеграція з Laravel
Laravel Echo Server або Soketi — стандартний шлях. Але можна прямо через predis або phpredis:
// Публікація события з Laravel
use Illuminate\Support\Facades\Redis;
Redis::publish('notifications:user:' . $userId, json_encode([
'type' => 'order.status_changed',
'orderId' => $order->id,
'status' => $order->status,
'timestamp' => now()->toISOString(),
]));
Для Laravel Broadcasting з Redis:
// config/broadcasting.php
'connections' => [
'redis' => [
'driver' => 'redis',
'connection' => 'default',
'queue' => '{default}',
'retry_after' => 90,
],
],
# Запуск Laravel Echo Server
npx laravel-echo-server start
# або Soketi (більш сучасна альтернатива)
soketi start --config=soketi.json
Моніторинг
# Перегляд активних Pub/Sub каналів
redis-cli PUBSUB CHANNELS "*"
# Кількість підписників на каналі
redis-cli PUBSUB NUMSUB notifications:user:42
# Pattern-підписки
redis-cli PUBSUB NUMPAT
# Моніторинг всіх команд у реальному часі (обережно на prod)
redis-cli MONITOR
У Redis Exporter для Prometheus метрики Pub/Sub доступні через redis_connected_slaves і спеціальні скрипти. Важлива метрика — instantaneous_ops_per_sec: якщо вона різко зростає при Pub/Sub навантаженні, перевірте розмір повідомлень і частоту публікації.
Обмеження та підводні камені
Втрата повідомлень при реконнекті. Якщо підписник відключився і переподключився, пропущені повідомлення не восстанавливаються. Для критичних сповіщень — Redis Streams з групами споживачів або збереження останніх подій у окремому ключі.
Немає підтвердження доставки. PUBLISH повертає кількість отримувачів, але не гарантує, що вони обробили повідомлення. Для at-least-once доставки потрібна черга.
Навантаження на CPU при великій кількості шаблонів. PSUBSCRIBE зіставляє кожне опубліковане повідомлення зі всіма зареєстрованими шаблонами. При 10,000+ шаблонах це стає помітним.
Терміни
Базова інтеграція Redis Pub/Sub з Socket.io або Soketi на існуючому проекті — 1–2 дні. Повноцінна система сповіщень з персоналізацією каналів, обробкою переподключень і моніторингом — 4–6 днів.







