Антон Ларичев

Введение
Redis на практике используется в большинстве современных бэкенд-приложений. Это in-memory хранилище данных, которое решает три ключевые задачи: кеширование, очереди сообщений и pub/sub. Если вы разрабатываете API на Node.js и до сих пор не используете Redis, вы теряете производительность и усложняете архитектуру.
В этой статье разберем три основных сценария использования Redis для бэкенда с практическими примерами кода на Node.js и TypeScript. Каждый сценарий снабжен готовыми фрагментами, которые можно адаптировать к вашему проекту.
Как настроить Redis и подключиться из Node.js
Перед началом работы установите Redis локально или запустите его через Docker:
docker run -d --name redis -p 6379:6379 redis:7-alpine
Для подключения из Node.js используйте библиотеку ioredis — она поддерживает кластеры, пайплайны и автоматическое переподключение:
npm install ioredis
import Redis from 'ioredis';
const redis = new Redis({
host: 'localhost',
port: 6379,
maxRetriesPerRequest: 3,
retryStrategy(times) {
// Экспоненциальная задержка при переподключении
return Math.min(times * 200, 5000);
},
});
redis.on('connect', () => console.log('Redis подключен'));
redis.on('error', (err) => console.error('Ошибка Redis:', err));
Кеширование данных в Redis: паттерн Cache-Aside
Кеширование Redis — самый распространенный сценарий. Паттерн Cache-Aside работает просто: сначала проверяем кеш, если данных нет — идем в базу и сохраняем результат в Redis с TTL.
async function getUserById(id: string) {
const cacheKey = `user:${id}`;
// Проверяем кеш
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
// Запрос в базу данных
const user = await db.users.findById(id);
if (!user) return null;
// Сохраняем в кеш на 5 минут
await redis.set(cacheKey, JSON.stringify(user), 'EX', 300);
return user;
}
Как правильно инвалидировать кеш Redis
Инвалидация кеша — самая сложная часть кеширования. Есть три стратегии:
// 1. Удаление при обновлении данных
async function updateUser(id: string, data: Partial<User>) {
await db.users.update(id, data);
await redis.del(`user:${id}`);
}
// 2. Обновление кеша вместе с данными (Write-Through)
async function updateUserWriteThrough(id: string, data: Partial<User>) {
const updated = await db.users.update(id, data);
await redis.set(`user:${id}`, JSON.stringify(updated), 'EX', 300);
}
// 3. Удаление по паттерну для связанных ключей
async function clearUserCache(id: string) {
const keys = await redis.keys(`user:${id}:*`);
if (keys.length > 0) {
await redis.del(...keys);
}
}
Для продакшна рекомендую первый вариант — он проще и не создает рассинхронизации между кешем и базой. TTL выступает страховкой на случай, если инвалидация не сработает.
Redis как очередь сообщений для фоновых задач
Очереди Redis решают задачу асинхронной обработки: отправка email, генерация отчетов, обработка изображений. Вместо того чтобы блокировать HTTP-запрос, задача добавляется в очередь и выполняется воркером.
Используйте библиотеку BullMQ — она построена поверх Redis и поддерживает повторные попытки, приоритеты и задержки:
npm install bullmq
import { Queue, Worker } from 'bullmq';
// Создаем очередь
const emailQueue = new Queue('emails', {
connection: { host: 'localhost', port: 6379 },
});
// Добавляем задачу в очередь
async function sendWelcomeEmail(userId: string, email: string) {
await emailQueue.add('welcome', {
userId,
email,
template: 'welcome',
}, {
attempts: 3, // Три попытки при ошибке
backoff: { type: 'exponential', delay: 2000 },
});
}
Воркер для обработки очереди
const emailWorker = new Worker('emails', async (job) => {
const { email, template } = job.data;
// Отправляем письмо через почтовый сервис
await mailer.send({
to: email,
template,
});
console.log(`Письмо ${template} отправлено на ${email}`);
}, {
connection: { host: 'localhost', port: 6379 },
concurrency: 5, // Обрабатываем до 5 задач параллельно
});
emailWorker.on('failed', (job, err) => {
console.error(`Задача ${job?.id} провалена:`, err.message);
});
BullMQ хранит все задачи в Redis с помощью структур данных List и Sorted Set. Это дает надежность: если воркер упадет, задача вернется в очередь и будет обработана другим воркером.
Redis Pub/Sub: уведомления в реальном времени
Pub/Sub в Redis позволяет отправлять сообщения между сервисами без прямой связи. Издатель публикует сообщение в канал, все подписчики этого канала получают его мгновенно.
import Redis from 'ioredis';
// Подписчик — отдельное соединение Redis
const subscriber = new Redis();
// Издатель — основное соединение
const publisher = new Redis();
// Подписываемся на канал уведомлений
subscriber.subscribe('notifications', (err) => {
if (err) console.error('Ошибка подписки:', err);
});
subscriber.on('message', (channel, message) => {
const data = JSON.parse(message);
console.log(`Канал ${channel}:`, data);
// Отправляем клиенту через WebSocket
wss.broadcast(channel, data);
});
// Публикуем событие из другого сервиса
async function notifyOrderCreated(orderId: string) {
await publisher.publish('notifications', JSON.stringify({
type: 'order_created',
orderId,
timestamp: Date.now(),
}));
}
Важный нюанс: Redis Pub/Sub работает по принципу at-most-once. Если подписчик отключен в момент публикации, сообщение будет потеряно. Для гарантированной доставки используйте Redis Streams вместо Pub/Sub.
Когда использовать Pub/Sub, а когда очереди
| Сценарий | Pub/Sub | Очередь |
|---|---|---|
| Уведомления в реальном времени | Да | Нет |
| Фоновая обработка задач | Нет | Да |
| Рассылка события всем сервисам | Да | Нет |
| Гарантированная доставка | Нет | Да |
| Обработка одним воркером | Нет | Да |
Частые ошибки при работе с Redis
Один клиент для pub/sub и команд. Redis-клиент в режиме подписки не может выполнять обычные команды. Всегда создавайте отдельное соединение для подписчика.
Отсутствие TTL на ключах кеша. Без TTL данные остаются в памяти навсегда. Redis работает in-memory, и память не бесконечна. Всегда устанавливайте время жизни ключа.
Хранение слишком больших объектов. Redis оптимален для данных до 100 КБ. Если вы кешируете объекты по несколько мегабайт, это замедлит сериализацию и сеть.
Использование KEYS * в продакшне. Команда KEYS блокирует Redis и сканирует все ключи. Используйте SCAN для итеративного перебора без блокировки.
// Плохо — блокирует Redis
const keys = await redis.keys('user:*');
// Хорошо — итеративный перебор
async function scanKeys(pattern: string) {
const result: string[] = [];
let cursor = '0';
do {
const [next, keys] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
cursor = next;
result.push(...keys);
} while (cursor !== '0');
return result;
}
Заключение
Redis на практике закрывает три ключевые потребности бэкенда: быстрое кеширование с TTL и инвалидацией, надежные очереди сообщений через BullMQ и мгновенный pub/sub для событий реального времени. Начните с кеширования — это даст заметный прирост производительности уже в первый день. Затем добавьте очереди для фоновых задач и pub/sub для межсервисного взаимодействия. Redis прост в установке через Docker и интегрируется с Node.js за считанные минуты.






Комментарии
0