Реализация 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.







