Примеры использования Redis: кэширование, очереди и ограничение скорости

Backend2026-10-06TryQuickToolBox

Вы создали веб-приложение, которое отлично работает с небольшим числом пользователей, но с ростом трафика вы замечаете медленную загрузку страниц, дублирующиеся фоновые задачи и API, подвергающиеся атакам злоумышленников. Это классические проблемы масштабирования, которые Redis может элегантно решить. Redis — это хранилище данных в оперативной памяти, которое отлично справляется с тремя ключевыми задачами: кэшированием, очередями и ограничением скорости. В этой статье мы рассмотрим каждый вариант использования с практическими примерами и лучшими практиками.

Почему Redis для этих задач?

Redis хранит данные в памяти, что делает его невероятно быстрым — часто время отклика составляет менее миллисекунды. Он поддерживает богатые структуры данных, такие как строки, списки, множества, сортированные множества и хэши, которые естественно подходят для кэширования, очередей и ограничения скорости. Он также предлагает атомарные операции, pub/sub и возможности персистентности, что делает его универсальным инструментом для бэкенд-разработчиков.

1. Кэширование с помощью Redis

Кэширование — самый распространенный вариант использования Redis. Сохраняя часто запрашиваемые данные в Redis, вы снижаете нагрузку на базу данных и ускоряете ответы.

Как работает кэширование

Когда приходит запрос, проверьте, есть ли данные в Redis. Если да (попадание в кэш), верните их напрямую. Если нет (промах кэша), извлеките данные из базы данных, сохраните их в Redis с временем жизни и верните. Этот шаблон называется cache-aside.

// Пример на Node.js с использованием ioredis
const Redis = require('ioredis');
const redis = new Redis();

async function getUser(userId) {
  const cacheKey = `user:${userId}`;
  const cached = await redis.get(cacheKey);
  if (cached) return JSON.parse(cached);

  const user = await db.query('SELECT * FROM users WHERE id = ?', [userId]);
  await redis.set(cacheKey, JSON.stringify(user), 'EX', 3600); // 1 hour TTL
  return user;
}

Лучшие практики кэширования

Стратегии инвалидации кэша

Инвалидация — сложная задача. Распространенные стратегии включают:

2. Очереди с помощью Redis

Очереди разделяют трудоемкие задачи и цикл запрос-ответ. Например, отправка электронных писем, обработка изображений или генерация отчетов могут выполняться асинхронно.

Реализация простой очереди

Списки Redis идеально подходят для очередей. Используйте LPUSH, чтобы добавить задачи слева, и BRPOP, чтобы заблокироваться и извлечь справа (FIFO).

// Производитель: добавить задачу
await redis.lpush('email_queue', JSON.stringify({ to: 'user@example.com', subject: 'Welcome' }));

// Потребитель: обработать задачу (в воркере)
while (true) {
  const job = await redis.brpop('email_queue', 0); // 0 = блокироваться бесконечно
  const { to, subject } = JSON.parse(job[1]);
  await sendEmail(to, subject);
}

Надежные очереди с BRPOPLPUSH

Чтобы избежать потери задач при сбое воркера, используйте BRPOPLPUSH, чтобы атомарно переместить задачу в список обработки. После успешной обработки удалите ее из списка обработки. Если воркер умирает, другой воркер может восстановить задачи из списка обработки.

const job = await redis.brpoplpush('email_queue', 'processing_queue', 0);
try {
  // обработать задачу
  await redis.lrem('processing_queue', 1, job);
} catch (err) {
  // обработать ошибку, возможно, переставить в очередь
}

Специализированные библиотеки для очередей

Для продакшена рассмотрите такие библиотеки, как Bull (Node.js), RQ (Python) или Sidekiq (Ruby). Они предоставляют повторные попытки, планирование и мониторинг из коробки.

3. Ограничение скорости с помощью Redis

Ограничение скорости защищает ваш API от злоупотреблений и обеспечивает справедливое использование. Атомарные операции Redis делают его идеальным для подсчета запросов.

Ограничение скорости с фиксированным окном

Увеличивайте счетчик для каждого пользователя за временное окно. Если счетчик превышает лимит, отклоните запрос.

async function isAllowed(userId, limit = 100, windowSec = 60) {
  const key = `rate:${userId}:${Math.floor(Date.now() / 1000 / windowSec)}`;
  const count = await redis.incr(key);
  if (count === 1) await redis.expire(key, windowSec);
  return count