Главная / Статьи / Redis за пределами кэширования: сессии, ограничения скорости, очереди и модель Pub/Sub в Node.js

Redis за пределами кэширования: сессии, ограничения скорости, очереди и модель Pub/Sub в Node.js

Семь шаблонов использования Redis для серверных частей на Node.js — кэширование с TTL, ключи OTP, ограничения скорости запросов, задачи BullMQ, сессии, ограничения модели Pub/Sub и случаи, когда не стоит использовать Redis.

1424 слов

В начальных когнитивных моделях Redis рассматривается как «просто кэш»: сохраняется значение, устанавливается срок действия, оно читается быстрее, чем из базы данных, и всё готово.

Эта картина не ошибочна. Однако она неполная.

На практике Redis используется для обработки быстрых ответов API, совместных сессий, счётчиков злоупотреблений, одноразовых кодов, очередей задач с отложенной обработкой, лёгкого распространения данных в реальном времени и других краткосрочных значений.

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

1. Redis может существенно ускорить API

Начните с кэширования.

Рассмотрим запрос GET /products.

Без кэша каждый запрос может выглядеть так:

Клиент → API Node.js → PostgreSQL → API Node.js → Клиент

Дорогостоящий запрос при тысячах вызовов означает, что база данных выполняет одну и ту же работу снова и снова.

Redis может стоять перед таким запросом.

Клиент → API Node.js → Redis

При успешном поиске возвращать результат немедленно. При неудачном поиске запрашивать данные в PostgreSQL, сохранять результат в Redis, а затем возвращать его.

Простой пример использования ioredis:

import Redis from "ioredis";
const redis = new Redis(process.env.REDIS_URL);
async function getProducts() {
  const cached = await redis.get("products");
  if (cached) {
    return JSON.parse(cached);
  }
  const products = await database.product.findMany();
  await redis.set(
    "products",
    JSON.stringify(products),
    "EX",
    300
  );
  return products;
}h

Здесь EX означает, что данные в кэше истекают через 300 секунд.

Существует серьёзная проблема: аннулирование кэша.

Предположим, в Redis хранится запись product:123 → price:500, в то время как в PostgreSQL значение равно 600. База данных содержит правильные данные, но Redis всё равно может вернуть значение 500.

Кэширование — это не просто «хранение всего в Redis». Командам по-прежнему необходимо учитывать:

  • Время жизни кэша
  • Аннулирование данных в кэше
  • Устаревшие данные
  • Неудачные поиски
  • Сбои кэша

Записать значение в Redis легко. Сохранить его корректность сложнее.

2. Redis хорошо подходит для временных данных

Значения с коротким сроком жизни идеально подходят для таких случаев: одноразовые коды входа, ссылки на сброс пароля, токены верификации, временные данные сессий, счетчики злоупотреблений и временные блокировки.

Пример: выдача одноразового кода:

const otp = "482913";
await redis.set(
  `otp:${userId}`,
  otp,
  "EX",
  300
);

Одноразовый код автоматически истекает через пять минут.

Не требуется отдельная таблица для хранения одноразовых кодов, так же как и отдельная задача на очистку данных.

Чтение значения кода осуществляется с помощью:

const otp = await redis.get(`otp:${userId}`);

По истечении срока действия Redis удаляет ключ в соответствии с заданным механизмом истечения.

Именно поэтому Redis является подходящим хранилищем для данных приложений с коротким сроком жизни.

3. Redis может помочь с ограничением частоты запросов

Возьмем метод POST /login.

Без ограничений клиент может отправлять массу запросов на вход — тысячи запросов подряд.

Redis может хранить счетчик для такого клиента:

const key = `login-attempts:${ip}`;
const attempts = await redis.incr(key);
if (attempts === 1) {
  await redis.expire(key, 60);
}
if (attempts > 10) {
  throw new Error("Too many requests");
}

Структура представляет собой: IP-адрес → счетчик в Redis → текущее значение → установленный лимит.

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

4. Redis может использоваться для выполнения фоновых задач

При создании учетной записи может потребоваться создать пользователя, отправить письмо с приветствием, сгенерировать данные, уведомить другой сервис и выполнить другую работу.

HTTP-запрос не должен ждать завершения всего этого.

Вместо этого отправьте задачу в очередь:

Клиент → API → Очередь → Ответ

Затем:

Очередь → Рабочий процесс → Выполнение задачи

BullMQ — популярный вариант для Node.js, использующий Redis:

await emailQueue.add("welcome-email", {
  userId: user.id,
  email: user.email,
});

Рабочий процесс обрабатывает задачу отдельно:

const worker = new Worker(
  "email",
  async (job) => {
    if (job.name === "welcome-email") {
      await sendWelcomeEmail(job.data.email);
    }
  },
  {
    connection: redisConnection,
  }
);

API не обязан ждать ответа от поставщика электронной почты перед тем, как ответить пользователю.

Это подходит для задач, которые выполняются медленно, могут быть перепробованы, зависят от внешних сервисов, требуют больших вычислительных ресурсов или не являются обязательными до того, как API ответит.

Разделение обязанностей: API обрабатывает запрос; рабочий процесс выполняет тяжелую работу.

5. Redis может хранить сессии

Управление сессиями — еще одна область применения. Ключ вроде session:abc123 может содержать:

{
  "userId": "123",
  "role": "ADMIN"
}

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

Важное различие: Redis не автоматически делает аутентификацию безопасной.

Командам по-прежнему необходимо обрабатывать идентификаторы сессий, защищенные куки, срок действия, CSRF при необходимости, аутентификацию и авторизацию.

Redis — это инфраструктура, а не стратегия безопасности.

6. Redis может помочь с функциями в реальном времени

Механизм Pub/Sub позволяет распространять события между инстансами. Один из вариантов работы: клиент обращается к серверу A, отправляет данные в Redis, на сервере B запускается обработчик подписки, после чего данные доставляются другому клиенту.

Иллюстрация:

await redis.publish(
  "notifications",
  JSON.stringify({
    userId: "123",
    message: "Your order has shipped",
  })
);
await subscriber.subscribe("notifications")
subscriber.on("message", (channel, message) => {
  console.log(channel, message);
});

Полезно для уведомлений, реального времени обновлений, процессов, связанных с чатом, и распространения событий.

Ограничение: Redis Pub/Sub не является надежной очередью сообщений.

Если важны надежная обработка, повторные попытки или гарантированная доставка, в зависимости от ситуации лучше использовать очередь или Redis Streams.

Важно понимать эту разницу.

7. Redis становится проблемой, если его используют везде

Самый важный урок: как только появляется возможность использовать Redis, возникает соблазн хранить там всё.

Не делайте этого.

Сама скорость не означает, что каждый датасет должен находиться в памяти.

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

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

Если с самого начала провести четкое разделение, это избавит от множества последующих изменений конструкции.

Структуры данных Redis имеют значение

Redis — это не просто ключ → строка. Он предлагает несколько типов структур.

Строки

Простые значения, такие как user:123:name → „Mit“.

Хэш-строки

Несколько полей под одним ключом:

user:123
name → Mit
role → ADMIN
email → example@email.com

Списки

Упорядоченные коллекции и некоторые структуры, похожие на очереди.

Множества

Уникальные значения.

Отсортированные множества

Элементы, отсортированные по оценке — например, табло лидеров:

1000 → Player A
900  → Player B
800  → Player C

Выбор подходящей структуры часто упрощает решение задачи.

Распространенные ошибки при использовании Redis, которых следует избегать

Ошибка 1: кэширование всего подряд

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

Ошибка 2: отсутствие срока действия

Временные данные без указания срока действия накапливаются. Если данные не должны храниться вечно, установите для них политику истечения срока.

Ошибка 3: рассмотрение Redis как постоянной базы данных

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

Ошибка 4: игнорирование сбоев Redis

Определите, что произойдет, если Redis выйдет из строя. Для многих случаев использования кэша допустимо переключение на базу данных. Правильная стратегия зависит от роли, которую играет Redis.

Ошибка 5: использование Redis без понимания нагрузки

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

Как Redis меняет подход к разработке бэкендов

Многие решения начинаются с обработчика запросов, который напрямую взаимодействует с СУБД типа SQL.

Появление хранилищ в памяти и задач, выполняемых позже, часто приводит к усложнению структуры: обработчик сначала проверяет Redis, затем базу данных; либо он записывает задачу в очередь для выполнения специальным процессом, который взаимодействует с внешним поставщиком услуг.

Бэкенды превращаются в совокупность специализированных компонентов. Redis — один из таких компонентов, который может выполнять несколько функций.

Заключение

Не стоит внедрять Redis только потому, что это делают все остальные.

Используйте его тогда, когда ситуация это требует: кэширование для часто запрашиваемых данных, ключи с TTL для временных секретов, счетчики для ограничения чрезмерного использования, очереди для отложенных задач, общий хранилище сессий между инстансами или модель Pub/Sub, когда достаточно легкого распространения данных.

Избегайте ситуаций, когда Redis становится стандартным решением для любой задачи на стороне бэкенда.

Хорошие системы — это не те, у которых самый длинный список инструментов. Это те, где каждый инструмент заслуживает своего места.