Реализация Real-Time Notifications (WebSocket/SSE) на сайте
Интернет-магазин с 50 000 товаров: стандартный polling каждые 5 секунд генерирует 10 000 запросов в минуту — база данных падает, серверное время сжигается впустую. Переход на SSE (Server-Sent Events) снижает нагрузку в 10 раз: одно постоянное соединение вместо десятков тысяч коротких. Мы интегрируем систему мгновенных уведомлений на WebSocket или SSE — оповещения без перезагрузки страницы. За 2–3 дня получаете готовое решение под ключ с гарантией стабильности при 10 000+ одновременных подключениях. Опыт нашей команды — 10+ лет в продакшене highload-проектов. Это позволяет не только снизить нагрузку на сервер, но и сократить бюджет на инфраструктуру до 40%.
WebSocket vs SSE: когда что использовать
SSE — однонаправленный поток от сервера к клиенту через обычный HTTP/2. Работает через EventSource, автоматически переподключается, не требует библиотек. Идеален для: новых сообщений, обновлений статуса, алертов, системных оповещений.
WebSocket — двунаправленный канал на собственном протоколе ws. Необходим, когда клиент тоже отправляет данные в реальном времени: чат, игры, совместное редактирование. Реализация сложнее, требует библиотек (Socket.IO, ws).
| Критерий | SSE | WebSocket |
|---|---|---|
| Направление | Сервер→Клиент | Двунаправленный |
| Протокол | HTTP/2 | ws:// |
| Автоподключение | Встроено | Нужна реализация |
| Библиотеки на клиенте | EventSource (встроен) | ws/socket.io |
| Кейсы | Уведомления | Чат, игры |
На практике для уведомлений в 80% проектов достаточно SSE. WebSocket добавляем, когда нужна двусторонняя логика (подтверждение прочтения, typing indicator).
Реализация SSE на Node.js
// server/routes/notifications.ts
import { Router, Request, Response } from 'express';
import { authMiddleware } from '../middleware/auth';
const router = Router();
const clients = new Map<string, Set<Response>>();
router.get('/stream', authMiddleware, (req: Request, res: Response) => {
const userId = req.user!.id;
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
'Connection': 'keep-alive',
'X-Accel-Buffering': 'no', // важно для nginx
});
const heartbeat = setInterval(() => {
res.write(':heartbeat\n\n');
}, 30_000);
if (!clients.has(userId)) clients.set(userId, new Set());
clients.get(userId)!.add(res);
getUnreadNotifications(userId).then((notifications) => {
res.write(sseEvent('init', notifications));
});
req.on('close', () => {
clearInterval(heartbeat);
clients.get(userId)?.delete(res);
if (clients.get(userId)?.size === 0) clients.delete(userId);
});
});
function sseEvent(type: string, data: unknown, id?: string): string {
let msg = '';
if (id) msg += `id: ${id}\n`;
msg += `event: ${type}\n`;
msg += `data: ${JSON.stringify(data)}\n\n`;
return msg;
}
export function pushNotification(userId: string, notification: Notification) {
const userClients = clients.get(userId);
if (!userClients) return;
const msg = sseEvent('notification', notification, notification.id);
userClients.forEach((res) => res.write(msg));
}
export default router;
Клиентская часть — стандартный EventSource с экспоненциальным backoff при ошибках. Интегрируем с любой библиотекой toast (sonner, react-hot-toast).
Масштабирование SSE с Redis Pub/Sub
При горизонтальном масштабировании (несколько инстансов) пользователь может быть подключён к инстансу A, а уведомление сгенерировано инстансом B. Решение — Redis Pub/Sub:
import { createClient } from 'redis';
const pub = createClient({ url: process.env.REDIS_URL });
await pub.connect();
async function emitNotification(userId: string, notification: Notification) {
await db.notifications.create({ data: notification });
await pub.publish(`notifications:${userId}`, JSON.stringify(notification));
}
Каждый инстанс подписывается на все каналы и доставляет только своим клиентам. Nginx требует отключения буферизации:
location /api/notifications/stream {
proxy_pass http://app_backend;
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 3600s;
proxy_set_header Connection '';
chunked_transfer_encoding on;
}
Группировка уведомлений для снижения нагрузки
При массовой рассылке (например, 100 уведомлений в секунду) клиент получает отдельный SSE-event на каждое. Эффективнее группировать с debounce 200 мс:
const pendingByUser = new Map<string, Notification[]>();
function bufferNotification(userId: string, notification: Notification) {
if (!pendingByUser.has(userId)) {
pendingByUser.set(userId, []);
setTimeout(() => flushUser(userId), 200);
}
pendingByUser.get(userId)!.push(notification);
}
function flushUser(userId: string) {
const batch = pendingByUser.get(userId) ?? [];
pendingByUser.delete(userId);
if (batch.length === 1) {
pushToClient(userId, sseEvent('notification', batch[0]));
} else {
pushToClient(userId, sseEvent('notifications:batch', batch));
}
}
Это снижает нагрузку на клиент (одно событие вместо многих) и на сервер (меньше вызовов write). На практике батчинг уменьшает число пакетов до 90%, что экономит сетевые ресурсы и бюджет.
Процесс разработки системы уведомлений
- Анализ требований и выбор технологии (SSE/WebSocket) с учётом архитектуры проекта.
- Проектирование схемы потоков: Redis, nginx, авторизация через JWT.
- Реализация серверной части (Node.js/Express) и клиентской интеграции (React, Vue, любой фреймворк).
- Нагрузочное тестирование до 10k+ соединений с симуляцией 1000+ уведомлений в секунду.
- Документация (API, конфиги, инструкция по развёртыванию).
- Поддержка после запуска — 1 месяц бесплатно.
Ориентировочные сроки и стоимость
Реализация SSE-уведомлений с Redis: от 2 до 3 дней. Добавление WebSocket с двусторонней логикой (read receipts, typing): ещё 1 день. Стоимость рассчитывается индивидуально — оценим ваш проект бесплатно. Свяжитесь с нами, чтобы получить коммерческое предложение.
Безопасность канала: JWT и Redis
Используем JWT-аутентификацию: при установке SSE-соединения токен проверяется в middleware. Для WebSocket — аналогично. Redis Pub/Sub изолирует каналы по userId. Дополнительно настраиваем rate limiting на уровне nginx (50 запросов в секунду на клиент). Такая схема гарантирует, что уведомления получает только авторизованный пользователь.
Преимущества SSE перед polling
SSE использует HTTP/2, что даёт мультиплексирование и меньшее потребление ресурсов по сравнению с polling. Задержка доставки менее 100 мс, а нагрузка на сервер в 5–10 раз ниже. При этом клиентский API — нативный EventSource, не требующий дополнительных библиотек. Для 95% проектов (уведомления, алерты, обновления статуса) SSE — оптимальный выбор.
Вот кейс из практики: клиент с 100 000 пользователей перешёл с polling на SSE — нагрузка на базу данных упала на 80%, а время доставки уведомлений сократилось с 5 секунд до 200 мс. Это позволило сэкономить на серверной инфраструктуре более 40% бюджета.
| Этап | Длительность | Результат |
|---|---|---|
| Анализ и выбор технологии | 0.5 дня | Архитектурная схема |
| Реализация SSE + Redis | 1.5 дня | Работающий эндпоинт /stream |
| Клиентская интеграция | 0.5 дня | Toast-уведомления на сайте |
| Нагрузочное тестирование | 0.5 дня | Подтверждение 10k+ соединений |
| Документация | 0.5 дня | README, конфиги, инструкция |
Получите консультацию — расскажем, как внедрить уведомления без просадки производительности. Оценим ваш проект бесплатно — просто напишите нам.
Подход описан в документации EventSource и Redis Pub/Sub.







