> Как реализовать rate limiting с использованием Redis (JavaScript)

Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы

Компании: QueenInteractiveGamesLtd

Стек: Node.js, Redis, JavaScript

> Пример ответа

Короткий ответ

Rate limiting с Redis реализуется через подсчёт запросов за временное окно с помощью INCR и EXPIRE. Основные алгоритмы: sliding window (через Sorted Set) и fixed window (через счётчик с TTL). Redis подходит благодаря атомарности операций, встроенному TTL и высокой производительности. Для распределённых систем используется Lua-скрипт для атомарности проверки и инкремента.

Подробное объяснение

Redis идеально подходит для rate limiting благодаря in-memory хранению, атомарным операциям и встроенному механизму TTL. Основные подходы:

Fixed window: для каждого пользователя хранится счётчик запросов с TTL, равным длине окна. При каждом запросе INCR увеличивает счётчик, EXPIRE устанавливает время жизни. Если счётчик превышает лимит - запрос отклоняется. Минус: возможны всплески на границах окон.

Sliding window: использует Sorted Set с timestamp в качестве score. ZREMRANGEBYSCORE удаляет устаревшие записи, ZCARD считает количество за текущее окно. Более точный, но требует больше памяти и операций.

Token bucket: хранит количество доступных токенов и время последнего пополнения. Каждый запрос проверяет и уменьшает количество токенов. Подходит для burst-трафика.

Для production рекомендуется Lua-скрипт, обеспечивающий атомарность проверки и обновления состояния. Это предотвращает race conditions в распределённой среде.

На практике

В Node.js rate limiting обычно реализуется как middleware для Express/Koa. Ключи в Redis формируются как rate_limit:{userId}:{endpoint}. Для sliding window используется Sorted Set с удалением старых записей перед проверкой.

Важно настроить TTL для автоматической очистки данных. Для fixed window TTL равен размеру окна. Для sliding window TTL устанавливается с запасом (например, двойной размер окна).

При масштабировании нужно учитывать, что Redis - единая точка отказа. Используется Redis Cluster или Sentinel для отказоустойчивости. Альтернатива - in-memory rate limiting с синхронизацией через Redis Pub/Sub.

Пример кода

JAVASCRIPT
const redis = require('redis');
const client = redis.createClient();
// Sliding window rate limiter
async function slidingWindowRateLimit(userId, limit, windowMs) {
const key = `rate_limit:${userId}`;
const now = Date.now();
const windowStart = now - windowMs;
const luaScript = `
redis.call('ZREMRANGEBYSCORE', KEYS[1], 0, ARGV[1])
local count = redis.call('ZCARD', KEYS[1])
if count < tonumber(ARGV[2]) then
redis.call('ZADD', KEYS[1], ARGV[3], ARGV[3])
redis.call('EXPIRE', KEYS[1], ARGV[4])
return {1, count + 1}
else
return {0, count}
end
`;
const result = await client.eval(luaScript, 1, key, windowStart, limit, now, Math.ceil(windowMs / 1000));
return {
allowed: result[0] === 1,
remaining: limit - result[1],
resetTime: now + windowMs
};
}
// Fixed window rate limiter
async function fixedWindowRateLimit(userId, limit, windowMs) {
const key = `rate_limit:${userId}:${Math.floor(Date.now() / windowMs)}`;
const count = await client.incr(key);
if (count === 1) {
await client.expire(key, Math.ceil(windowMs / 1000));
}
return {
allowed: count <= limit,
remaining: Math.max(0, limit - count),
resetTime: Math.ceil(Date.now() / windowMs) * windowMs
};
}

Как отвечать на собеседовании

Начни с объяснения задачи rate limiting и почему Redis подходит для этого. Опиши основные алгоритмы, их trade-off'ы. Упомяни атомарность Lua-скриптов и проблемы race conditions. Приведи пример middleware для Express. Обсуди масштабирование и отказоустойчивость. Покажи понимание edge cases: burst-трафик, распределённые системы, синхронизация между инстансами.

Что проверяет интервьюер

  • Понимание работы Redis и его структур данных
  • Знание алгоритмов rate limiting и их trade-off'ов
  • Умение проектировать распределённые системы
  • Понимание атомарности и race conditions
  • Опыт работы с Node.js и Redis в production
  • Навыки оптимизации производительности

Типичные ошибки

  • Использование только fixed window без учёта burst на границах
  • Отсутствие TTL, приводящее к переполнению памяти
  • Игнорирование race conditions при параллельных запросах
  • Неправильный выбор структуры данных (например, String вместо Sorted Set для sliding window)
  • Отсутствие обработки ошибок Redis (connection lost, timeout)
  • Синхронные операции в асинхронном Node.js коде

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью