Лимитер запросов кажется задачей про счётчик, но на деле это кейс про справедливое распределение ресурса, всплески нагрузки и поведение системы в момент отказа защитного слоя.
Глава помогает сравнить Token Bucket, Leaky Bucket и Sliding Window, выбрать точку применения лимита, способ хранения счётчиков и режим работы при сбоях.
В интервью и архитектурных обсуждениях этот кейс быстро показывает, умеете ли вы рассуждать о злоупотреблениях, критическом пути и цене неудачной политики по умолчанию.
Control Plane
Фокус на governance-политиках, лимитах, маршрутизации и стабильности edge-поведения.
Data Path
Нужно держать предсказуемую latency и throughput при росте трафика и burst-нагрузке.
Failure Modes
Покрывайте fail-open/fail-close, деградацию и безопасный резервный сценарий при частичных отказах.
Ops Ready
Показывайте мониторинг saturation, retry storm и операционные защитные ограничения.
Rate Limiter — классический кейс про защиту публичного API и справедливое распределение ресурса. На поверхности это просто счётчик запросов, но на практике приходится решать, где именно применять лимит, как хранить состояние между инстансами и что делать, если сам лимитер начинает ошибаться или недоступен.
Именно поэтому задача так полезна на интервью: она быстро выводит разговор на между точностью ограничения, на критическом пути и поведением системы при отказе самого защитного слоя.
Глава 4
Alex Xu: Rate Limiter
Подробный разбор алгоритмов и архитектурных решений в книге
Зачем нужен лимитер запросов
Что происходит без лимитов
- DDoS атаки — перегрузка сервиса вредоносным трафиком
- Каскадные отказы — один источник трафика может перегрузить весь сервис
- Перерасход ресурсов — бесконтрольное потребление API
- Неравномерная нагрузка — "шумные соседи" мешают другим
Что даёт лимитер запросов
- Защита сервисов — предотвращение перегрузки
- Справедливое распределение — квоты для каждого клиента
- Экономия денег — контроль использования платных API
- Предсказуемость — стабильная производительность
Типичные требования
Функциональные
- FR1Ограничение запросов по IP-адресу, идентификатору пользователя или API-ключу
- FR2Настраиваемые лимиты для разных эндпоинтов
- FR3Информативный ответ при превышении лимита (429)
- FR4Работа в распределённой среде
Нефункциональные
- NFR1Низкая задержка — меньше 1 мс дополнительного времени на запрос
- NFR2Высокая доступность — сбой лимитера ≠ сбой сервиса
- NFR3Точность лимитов (допустима погрешность до ±5% в распределённой среде)
- NFR4Горизонтальное масштабирование
Алгоритмы ограничения запросов
У каждого классического алгоритма свой профиль точности, памяти и поведения под всплесками нагрузки:
Интерактивная визуализация
1. Token Bucket
Бакет с токенами пополняется с постоянной скоростью. Каждый запрос забирает один токен.
Преимущества
- Простая реализация
- Хорошо переживает всплески нагрузки
- Экономно расходует память (два числа на пользователя)
Недостатки
- Непросто выбрать размер бакета
- В распределённой среде возможны гонки при обновлении счётчиков
# Параметры:
bucket_size = 10 # максимум токенов
refill_rate = 2 # скорость пополнения
Визуализация алгоритма
Архитектура решения
Связанный кейс
API Gateway
Полный разбор API Gateway: маршрутизация, безопасность, BFF-паттерн и сравнение технологий.
Где лучше ставить лимитер запросов?
На стороне клиента
Такой лимит легко обойти, поэтому он годится только как дополнительная оптимизация.
API Gateway
Самая удобная точка: единый вход в систему перед прикладными сервисами. AWS API Gateway, Kong, Envoy.
Внутри сервиса
Даёт гибкость, но приводит к дублированию логики и усложняет синхронизацию лимитов.
Zhiyong Tan
Acing SDI: Rate Limiter
Сравнение вариантов размещения лимитера в реальных системах
Реализация на базе Redis
Redis часто выбирают для лимитера запросов из-за работы в памяти и атомарных операций.
Sliding Window Counter на Redis
-- Lua-скрипт для атомарного обновления
local key = KEYS[1]
local window = tonumber(ARGV[1]) -- размер окна в секундах
local limit = tonumber(ARGV[2]) -- лимит запросов
local now = tonumber(ARGV[3]) -- текущая метка времени
-- Удаляем устаревшие записи
redis.call('ZREMRANGEBYSCORE', key, 0, now - window)
-- Считаем запросы в текущем окне
local count = redis.call('ZCARD', key)
if count < limit then
-- Добавляем новый запрос
redis.call('ZADD', key, now, now .. '-' .. math.random())
redis.call('EXPIRE', key, window)
return 1 -- запрос разрешён
else
return 0 -- запрос отклонён
endЗащита от гонок
Проблема: Параллельные запросы могут одновременно увидеть одно и то же значение счётчика.
Решение: Lua-скрипты выполняются в Redis атомарно.
Альтернатива: Redis MULTI/EXEC или Redlock, если нужна распределённая блокировка.
Синхронизация между инстансами
Централизованный вариант: Один Redis-кластер обслуживает все инстансы приложения.
Согласованность в конечном счёте: Локальные счётчики с периодической синхронизацией.
Компромисс: Точность против задержки.
HTTP-заголовки ответа
# При успешном запросе:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1640995200
# При превышении лимита:
HTTP/1.1 429 Too Many Requests
Retry-After: 30
Глубже: многоуровневые лимиты
В реальных системах лимиты почти всегда задают на нескольких уровнях одновременно:
Уровни лимитов
- Глобальный: 10 млн запросов в сутки на весь сервис
- На пользователя: 1000 запросов в час
- На эндпоинт: 100 запросов в минуту на `/api/search`
- На IP-адрес: защита от перегрузки с одного источника
Мягкая деградация
- Мягкий лимит: предупреждение в заголовках ответа
- Жёсткий лимит: ответ `429` и `Retry-After`
- Сбой Redis: пропускать запросы или, наоборот, блокировать их?
- Наблюдаемость: оповещения об аномалиях трафика
Что важно проговорить на интервью
- 1Начните с требований: какой профиль трафика, допустима ли погрешность и нужна ли работа в распределённой среде?
- 2Выберите алгоритм: Token Bucket подходит для всплесков, Sliding Window Counter — когда нужна более точная оценка текущей нагрузки.
- 3Проговорите компромиссы: память против точности, задержка против строгости синхронизации между инстансами.
- 4Redis + Lua: атомарные операции убирают гонки при обновлении счётчиков.
- 5Поведение при сбое: что делать, если сам лимитер недоступен?
Для дополнительной практики и сравнения реализаций полезно посмотреть System Design Primer: Rate Limiter и Cloudflare Rate Limiting Rules.
Связанные главы
- System Design Primer (краткий обзор) - даёт базовую рамку для требований, ограничений и оценки узких мест в задачах про лимиты запросов.
- System Design Interview - Alex Xu - содержит классический интервью-разбор лимитера запросов с акцентом на алгоритмы и архитектурные компромиссы.
- Acing the System Design Interview - дополняет тему практическими вариантами размещения лимитера в распределённой среде.
- API Gateway - показывает основной эксплуатационный контур, где лимиты запросов чаще всего вводят централизованно.
- API Security Patterns - связывает лимиты запросов с более широкой защитой API: аутентификацией, борьбой со злоупотреблениями и исполнением политик доступа.
- Стратегии кэширования - помогает выбрать подход к хранению счётчиков и кэшированию так, чтобы не раздувать дополнительную задержку.
- Resilience Patterns - расширяет тему поведения лимитера при сбоях: когда пропускать запросы, когда блокировать и как деградировать без каскадных отказов.
- Notification System - показывает прикладной кейс, где многоуровневые лимиты сдерживают всплески нагрузки и защищают зависимые сервисы.
- Web Crawler - демонстрирует необходимость лимитов для вежливого и контролируемого доступа к внешним источникам.
- Payment System - даёт контекст критичных систем, где лимиты запросов защищают дорогостоящие и чувствительные операции.
