> Как спроектировать и реализовать rate limiting для платного API с разными тарифами (JavaScript)
Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы
Компании: QueenInteractiveGamesLtd
Стек: Node.js, JavaScript
> Пример ответа
Короткий ответ
Rate limiting для платного API с разными тарифами реализуется через middleware, который проверяет идентификатор клиента (API key или JWT), определяет его тарифный план и применяет соответствующие лимиты (requests per minute/hour/day). Данные о потреблении хранятся в Redis с TTL, равным окну лимита. При превышении возвращается 429 Too Many Requests с заголовками Retry-After и X-RateLimit-*.
Подробное объяснение
Архитектура rate limiting для платного API включает несколько ключевых компонентов:
-
Идентификация клиента - каждый запрос содержит API key или JWT, по которому определяется тарифный план (free, pro, enterprise).
-
Хранение счетчиков - Redis идеально подходит благодаря атомарным операциям (INCR, EXPIRE) и встроенной поддержке TTL. Для sliding window используется sorted sets или Lua-скрипты.
-
Алгоритмы:
- Fixed window - простой, но допускает burst-запросы на границах окна
- Sliding window - более точный, использует sorted set с временными метками
- Token bucket - гибкий, позволяет накапливать неиспользованные запросы
- Leaky bucket - сглаживает burst, но не подходит для накопления
-
Тарифные планы - хранятся в конфигурации или базе данных, содержат лимиты на разные временные окна (10 req/s, 1000 req/h, 10000 req/day).
-
Ответ при превышении - HTTP 429 с заголовками:
X-RateLimit-Limit- общий лимитX-RateLimit-Remaining- оставшиеся запросыX-RateLimit-Reset- время сброса (Unix timestamp)Retry-After- секунды до следующего разрешенного запроса
-
Graceful degradation - при недоступности Redis можно временно пропускать запросы (fail-open) или блокировать (fail-closed), в зависимости от SLA.
На практике
Для Node.js с Express типичная реализация:
-
Создаем middleware, который извлекает API key из заголовка
Authorizationили параметра запроса. -
По API key получаем тарифный план (из кэша или БД). Для ускорения используем in-memory cache с коротким TTL.
-
Формируем ключ для Redis:
ratelimit:{plan}:{apiKey}:{window}. -
Используем Lua-скрипт для атомарного инкремента и проверки лимита:
local key = KEYS[1] local limit = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local now = tonumber(ARGV[3]) -- Удаляем старые записи (для sliding window) redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000) -- Получаем текущее количество local count = redis.call('ZCARD', key) if count >= limit then return {0, count, redis.call('TTL', key)} end -- Добавляем новый запрос redis.call('ZADD', key, now, now .. ':' .. math.random()) redis.call('EXPIRE', key, window) return {1, count + 1, window}
-
В ответе добавляем заголовки с информацией о лимите.
-
Для enterprise-клиентов можно реализовать soft limit - предупреждение при 80% использования, hard limit при 100%.
Пример кода
JAVASCRIPTconst redis = require('redis');const client = redis.createClient();const RATE_LIMIT_SCRIPT = `local key = KEYS[1]local limit = tonumber(ARGV[1])local window = tonumber(ARGV[2])local now = tonumber(ARGV[3])redis.call('ZREMRANGEBYSCORE', key, 0, now - window * 1000)local count = redis.call('ZCARD', key)if count >= limit thenlocal ttl = redis.call('TTL', key)return {0, count, ttl}endredis.call('ZADD', key, now, now .. ':' .. math.random())redis.call('EXPIRE', key, window)return {1, count + 1, window}`;async function rateLimiter(req, res, next) {const apiKey = req.headers['x-api-key'];if (!apiKey) return res.status(401).json({ error: 'API key required' });const plan = await getPlan(apiKey); // из кэша или БДconst limits = PLANS[plan]; // { requestsPerSecond: 10, requestsPerHour: 1000 }const now = Date.now();const windowMs = 1000; // 1 second windowconst key = `ratelimit:${plan}:${apiKey}:second`;const result = await client.eval(RATE_LIMIT_SCRIPT, 1, key, limits.requestsPerSecond, windowMs / 1000, now);const [allowed, remaining, resetIn] = result;res.set({'X-RateLimit-Limit': limits.requestsPerSecond,'X-RateLimit-Remaining': Math.max(0, limits.requestsPerSecond - remaining),'X-RateLimit-Reset': Math.ceil((now + resetIn * 1000) / 1000),'Retry-After': resetIn});if (!allowed) {return res.status(429).json({ error: 'Too many requests' });}next();}
Как отвечать на собеседовании
- Начни с общей архитектуры: middleware, Redis, тарифные планы.
- Объясни выбор алгоритма - для платного API лучше sliding window или token bucket.
- Упомяни заголовки ответа и graceful degradation.
- Покажи понимание trade-off: точность vs производительность, стоимость Redis-операций.
- Добавь про мониторинг - метрики в Prometheus, алерты при превышении лимитов.
- Если спросят про распределенные системы - расскажи про консистентность и атомарность Lua-скриптов.
Что проверяет интервьюер
- Понимание алгоритмов rate limiting и их применимости
- Умение проектировать масштабируемые решения с Redis
- Знание HTTP-протокола и заголовков для rate limiting
- Навыки работы с Lua-скриптами для атомарных операций
- Понимание бизнес-логики: разные тарифы, soft/hard limits
- Умение учитывать edge cases: недоступность Redis, burst-запросы
Типичные ошибки
- Использование fixed window без учета burst на границах
- Хранение счетчиков в памяти процесса (не масштабируется)
- Отсутствие graceful degradation при недоступности Redis
- Неправильный расчет TTL для sliding window
- Игнорирование заголовков Retry-After и X-RateLimit-*
- Блокировка всех запросов при превышении лимита одним клиентом
- Отсутствие кэширования тарифных планов (каждый запрос в БД)
> Похожие задачи по JavaScript
Как отлаживать ошибки в коде
Как реализовать rate limiting с использованием Redis
Как обеспечить корректную обработку запросов при параллельном выполнении для одного пользователя
Как вы относитесь к удаленной работе и переходу в офис
> Похожие задачи по frontend
Как отлаживать ошибки в коде
Как реализовать rate limiting с использованием Redis
Как обеспечить корректную обработку запросов при параллельном выполнении для одного пользователя
Как вы относитесь к удаленной работе и переходу в офис
> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?
Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью