Redis и KakaoTalk: реализация push-уведомлений
Push-уведомления стали неотъемлемой частью современных мессенджеров, обеспечивая мгновенную доставку сообщений и удержание пользователей. Реализация эффективной системы требует высокой скорости, масштабируемости и отказоустойчивости. Redis и KakaoTalk демонстрируют синергию в этой области: первый — как инструмент хранения состояния соединений и брокер событий, второй — как кейс массовой доставки уведомлений миллионам активных пользователей.
- Как работает система push-уведомлений в KakaoTalk
- Управление состоянием пользователя
- Роль Redis в реализации push-уведомлений
- Как работает pub/sub в Redis для рассылки уведомлений
- Проектирование архитектуры: от клиента до сервера
- Основные компоненты системы
- Пример потока данных при отправке сообщения
- Пошаговая реализация на основе Redis и WebSocket
- Шаг 1: Настройка окружения
- Шаг 2: Создание WebSocket-шлюза
- Шаг 3: Сервис доставки сообщений
- Шаг 4: Push-воркер для FCM
- Оптимизация и масштабирование системы
- Использование Redis Cluster
- Горизонтальное масштабирование шлюзов
- Типичные ошибки и пути их решения
- Ошибка 1: Пропущенные уведомления при переподключении
- Ошибка 2: Дублирование push-уведомлений
- Ошибка 3: Утечки памяти в Redis
- Экспертное мнение
- Вопросы и ответы
- Заключение
Как работает система push-уведомлений в KakaoTalk
KakaoTalk — один из крупнейших мессенджеров в Азии, особенно популярен в Южной Корее. Свыше 50 миллионов активных пользователей ежедневно обмениваются миллиардами сообщений. Для обеспечения мгновенной доставки даже при переключении между сетями или выключенном экране используется многоуровневая push-система.
Основа — комбинация собственных серверов и внешних push-сервисов (APNs для iOS, FCM для Android). Однако внутренняя маршрутизация уведомлений происходит через централизованную инфраструктуру, где критически важна скорость определения онлайн-статуса пользователя и выбор канала доставки: прямое WebSocket-соединение или внешний пуш.
Когда пользователь в сети, KakaoTalk использует постоянное соединение через шлюзы. При выходе из сети сообщения ставятся в очередь и отправляются через APNs/FCM. Ключевой вызов — минимизация времени перехода между этими режимами и предотвращение дублирования уведомлений.
Управление состоянием пользователя
Состояние «онлайн» или «офлайн» не определяется по таймауту, а поддерживается через регулярные heartbeats от клиентских приложений. Серверы фиксируют время последнего сигнала и ассоциируют его с уникальным ID сессии. Это состояние должно быть доступно всем компонентам системы — от шлюзов до сервисов уведомлений.
- Каждый клиент при подключении регистрирует свою сессию с указанием device_id, user_id и endpoint.
- Серверы обновляют статус каждые 15–30 секунд по heartbeat.
- При разрыве соединения статус меняется на «офлайн» с задержкой не более 60 секунд.
- Информация о состоянии должна быть консистентной и доступной в реальном времени.
Роль Redis в реализации push-уведомлений
Redis — in-memory data structure store — является идеальным решением для хранения временных данных с высокой частотой чтения и записи. В контексте push-уведомлений он выполняет три ключевые функции: хранение сессий, маршрутизацию событий и управление очередями.
Благодаря поддержке различных типов данных (строки, хэши, списки, множества, sorted sets) и встроенным механизмам pub/sub, Redis позволяет строить отказоустойчивые и масштабируемые системы без дополнительных брокеров.
Функция |
Механизм Redis |
Пример использования |
|---|---|---|
Хранение сессий |
Hash + TTL |
HSET session:user:123 device_id «dev_abc» ip «192.168.1.1»; EXPIRE session:user:123 90 |
Маршрутизация событий |
Publish/Subscribe |
PUBLISH channel:new_message ‘{«to»:123,»text»:»Hi»}’ |
Очереди уведомлений |
List + BRPOP |
LPUSH queue:push:123 ‘{«type»:»msg»,»id»:456}’; BRPOP queue:push:* 0 |
Онлайн-пользователи |
Set / Sorted Set |
SADD online_users 123; ZADD last_active 1744790000 123 |
Как работает pub/sub в Redis для рассылки уведомлений
Механизм publish/subscribe в Redis позволяет нескольким сервисам подписываться на каналы и получать события в реальном времени. Например, когда поступает новое сообщение для пользователя 123, сервис может выполнить:
- Проверить, находится ли пользователь в наборе online_users.
- Если да — опубликовать событие в канал user:123:messages.
- WebSocket-шлюз, подписанный на этот канал, немедленно передаст данные клиенту.
- Если пользователь оффлайн — поместить уведомление в очередь для FCM/APNs.
Проектирование архитектуры: от клиента до сервера
Чтобы построить систему, аналогичную KakaoTalk, необходимо чётко разделить зоны ответственности между компонентами. Архитектура должна быть модульной, чтобы каждый элемент можно было масштабировать независимо.
Центральное место занимает Redis, вокруг которого строятся остальные сервисы. Ниже — типичная схема взаимодействия.
Основные компоненты системы
- WebSocket-шлюз — принимает и поддерживает соединения с клиентами. Проверяет авторизацию, регистрирует сессии в Redis и слушает pub/sub-каналы для данного пользователя.
- Сервис сообщений — обрабатывает входящие сообщения, проверяет права, сохраняет в базу и инициирует доставку.
- Push-сервис — отвечает за отправку уведомлений через FCM/APNs. Получает задачи из очереди Redis.
- Redis-кластер — хранит сессии, онлайн-статус, очереди и служит брокером событий.
- Service Discovery — помогает шлюзам находить друг друга и координировать нагрузку (например, через Consul или Etcd).
Пример потока данных при отправке сообщения
Представьте, что пользователь A отправляет сообщение пользователю B.
- Сообщение поступает на сервер через API или напрямую через WebSocket.
- Сервис проверяет, существует ли получатель и есть ли права на отправку.
- Сообщение сохраняется в постоянное хранилище (например, PostgreSQL).
- Система проверяет, находится ли B в Redis-наборе online_users.
- Если да — публикуется событие в канал user:B:messages.
- WebSocket-шлюз, подписанный на канал, получает событие и отправляет его клиенту B.
- Если B оффлайн — формируется push-уведомление и помещается в очередь push:queue:B.
- Push-воркер извлекает задачу и отправляет через FCM/APNs.
Пошаговая реализация на основе Redis и WebSocket
Рассмотрим практический пример создания простой push-системы на Node.js с использованием Redis и WebSocket.
Шаг 1: Настройка окружения
Установите зависимости:
- Node.js (v18+)
- Redis Server (v7+)
- Библиотеки:
ws,ioredis,express
Запустите Redis:
redis-server --port 6379
Шаг 2: Создание WebSocket-шлюза
«`javascript
const WebSocket = require(‘ws’);
const Redis = require(‘ioredis’);
const wss = new WebSocket.Server({ port: 8080 });
const redis = new Redis();
wss.on(‘connection’, (ws, req) => {
const userId = getAuthUserId(req); // извлечение из токена
const sessionId = generateSessionId();
// Регистрация сессии
redis.hset(`session:${userId}`, ‘id’, sessionId, ‘ip’, req.socket.remoteAddress);
redis.sadd(‘online_users’, userId);
redis.expire(`session:${userId}`, 90);
// Подписка на канал пользователя
const subscriber = redis.duplicate();
subscriber.subscribe(`user:${userId}:messages`);
subscriber.on(‘message’, (channel, message) => {
if (ws.readyState === WebSocket.OPEN) {
ws.send(message);
}
});
ws.on(‘close’, () => {
subscriber.unsubscribe();
redis.srem(‘online_users’, userId);
});
});
«`
Шаг 3: Сервис доставки сообщений
«`javascript
function sendMessage(senderId, recipientId, text) {
// Сохранение в БД
saveToDatabase(senderId, recipientId, text);
// Проверка онлайн-статуса и отправка
redis.sismember(‘online_users’, recipientId).then(isOnline => {
if (isOnline) {
redis.publish(`user:${recipientId}:messages`, JSON.stringify({
type: ‘new_message’,
from: senderId,
text: text,
timestamp: Date.now()
}));
} else {
// Ставим в очередь push-уведомлений
redis.lpush(`queue:push:${recipientId}`, JSON.stringify({
title: ‘Новое сообщение’,
body: truncate(text, 50),
data: { messageId: 12345 }
}));
}
});
}
«`
Шаг 4: Push-воркер для FCM
«`javascript
const admin = require(‘firebase-admin’);
admin.initializeApp({ credential: admin.credential.cert(serviceAccount) });
function startPushWorker() {
const multi = redis.multi();
while (true) {
// Блокирующее извлечение из любой очереди
const [err, item] = await redis.brpop([‘queue:push:*’], 0);
if (item) {
const { key, value } = item;
const userId = key.split(‘:’)[2];
const token = await redis.get(`fcm_token:${userId}`);
if (token) {
await admin.messaging().send({
token: token,
notification: JSON.parse(value),
data: { userId }
});
}
}
}
}
«`
Оптимизация и масштабирование системы
Одного экземпляра Redis и шлюза недостаточно для миллионов пользователей. Необходимо применять стратегии масштабирования и повышения отказоустойчивости.
Использование Redis Cluster
Для распределения нагрузки и отказоустойчивости применяйте Redis Cluster. Он автоматически шардирует данные по 16384 хэш-слотам и обеспечивает репликацию.
- Разделите ключи по пользовательским ID: ключ `session:123` попадает в слот на основе хэша.
- Настройте несколько мастер-нод и реплик.
- Используйте клиенты, поддерживающие cluster mode (например, ioredis).
Горизонтальное масштабирование шлюзов
Разверните несколько экземпляров WebSocket-шлюзов за балансировщиком (NGINX, HAProxy). Каждый шлюз должен подписываться на свои каналы, но иметь доступ ко всей Redis-инфраструктуре.
Для синхронизации состояния используйте:
- Общую Redis-базу для сессий и онлайн-статуса.
- Service discovery для отслеживания активных шлюзов.
- Health checks для автоматического исключения неработающих узлов.
Типичные ошибки и пути их решения
Даже правильно спроектированная система может столкнуться с проблемами. Ниже — распространённые ошибки и методы их устранения.
Ошибка 1: Пропущенные уведомления при переподключении
Клиент может потерять соединение на доли секунды, но успеть пропустить сообщение. Решение — краткосрочная буферизация.
- Храните последние 1–3 сообщения в Redis для каждого пользователя (например, в списке с TTL 10 сек).
- При подключении клиент запрашивает missed_messages.
- Сервер возвращает содержимое буфера и очищает его.
Ошибка 2: Дублирование push-уведомлений
Если проверка онлайн-статуса и постановка в очередь не атомарны, возможна ситуация: пользователь вышел из сети, но сообщение уже вошло в pub/sub, а push тоже отправлен.
Решение — Lua-скрипт в Redis:
«`lua
local is_online = redis.call(‘SISMEMBER’, ‘online_users’, KEYS[1])
if is_online == 1 then
redis.call(‘PUBLISH’, ‘user:’ .. KEYS[1] .. ‘:messages’, ARGV[1])
return ‘direct’
else
redis.call(‘LPUSH’, ‘queue:push:’ .. KEYS[1], ARGV[1])
return ‘queued’
end
«`
Вызов:
«`bash
redis-cli —eval script.lua user_id , 123 ‘{«text»:»Hi»}’
«`
Ошибка 3: Утечки памяти в Redis
Если сессии не удаляются после истечения срока, память будет расти. Обязательно используйте TTL и механизмы очистки.
- Устанавливайте EXPIRE для всех временных ключей.
- Настройте maxmemory-policy (например, allkeys-lru).
- Регулярно мониторьте использование памяти через INFO memory.
Экспертное мнение
При проектировании push-систем ориентируйтесь на принципы: минимальная задержка, максимальная доставка, предсказуемое поведение. Redis — отличный выбор для хранения состояния, но не стоит полагаться на него как на единственный брокер для критичных событий.
Вместо этого используйте Redis для горячих данных, а для надёжной доставки — persistent очереди (например, Kafka) в связке с Redis. Так вы получите и скорость, и отказоустойчивость.
Автоматизация проверки состояния, тестирование сценариев потери соединения и нагрузочное тестирование — обязательные этапы перед выходом в продакшн. Моделируйте миллионы подключений, чтобы убедиться в стабильности.
Вопросы и ответы
Заключение
Реализация push-уведомлений в стиле KakaoTalk требует грамотного сочетания технологий и архитектурных решений. Redis играет здесь центральную роль, обеспечивая сверхбыстрое управление состоянием и маршрутизацию событий в реальном времени.
Ключ к успеху — чёткое разделение ответственностей: WebSocket-шлюзы для соединений, Redis для состояния и pub/sub, внешние сервисы для offline-доставки. Масштабирование достигается за счёт кластеризации Redis и горизонтального развертывания шлюзов.
- Redis идеально подходит для хранения сессий и pub/sub в push-системах.
- Используйте атомарные операции для предотвращения дублирования уведомлений.
- Комбинируйте Redis с persistent очередями для критичных событий.
- Масштабируйте шлюзы и настройте кластеризацию Redis.
- Обязательно тестируйте работу при потере соединения и сбоях.
⚠️ Дисклеймер — нажмите, чтобы развернуть
Материалы, опубликованные в разделе «Блог» на сайте RU DESIGN SHOP (rudesignshop.ru), носят исключительно информационный и ознакомительный характер и не являются руководством к действию, финансовой рекомендацией, медицинской услугой, ветеринарным назначением либо рекламой товаров и услуг, включая азартные игры. Публикации не содержат призывов к участию в азартных играх и не направлены на продвижение соответствующих операторов.
Безопасность применения товаров и веществ: при использовании строительных материалов, бытовой химии, пестицидов и агрохимикатов необходимо строго следовать инструкциям производителя и действующему законодательству Российской Федерации, включая Федеральный закон РФ от 19.07.1997 № 109-ФЗ «О безопасном обращении с пестицидами и агрохимикатами».
Упоминание товарных знаков, брендов и организаций носит исключительно информационный характер и не означает наличие партнёрских отношений или одобрения со стороны правообладателей.
Материалы, содержащие сведения о медицинских, ветеринарных или косметических средствах, представлены в справочных целях и не являются медицинской консультацией или назначением. Перед применением рекомендуется обратиться к врачу, ветеринарному специалисту или иному сертифицированному профессионалу.
Возрастные ограничения: материалы, содержащие сведения о продукции категории 18+, включая алкоголь или азартные игры, предназначены исключительно для совершеннолетней аудитории и публикуются в информационных целях.
Правовая ответственность: решения, принятые на основе опубликованной информации, пользователь принимает самостоятельно и на свой риск; редакция и авторы несут ответственность в пределах, установленных законодательством Российской Федерации.
Редакция не допускает публикаций, содержащих пропаганду экстремизма, терроризма, наркотических средств или суицида; подобные материалы подлежат немедленному удалению.
Упоминание организаций с ограниченным статусом: компания Meta Platforms Inc. (социальные сети Facebook и Instagram) признана экстремистской организацией решением суда РФ, её деятельность запрещена на территории Российской Федерации; любые упоминания приводятся исключительно в информационных целях.
Авторские права и источники: информация собирается из открытых источников; её актуальность указывается на дату публикации и может изменяться.
Изображения и иллюстрации используются на условиях, разрешённых правообладателями. При возникновении претензий редакция готова оперативно рассмотреть обращение и внести необходимые изменения.
Персональные данные и cookies: сайт использует cookies и обрабатывает персональные данные пользователей в соответствии с Федеральным законом № 152-ФЗ «О персональных данных» и Политикой конфиденциальности RU DESIGN SHOP.
Мнения авторов могут не совпадать с позицией государственных органов или коммерческих организаций, упомянутых в материалах.