Как реализовать rate limiter?
Подробный ответ
Что ограничивать
Лимиты можно задавать на IP, пользователя, организацию, API key, endpoint или их комбинацию. Например, login endpoint обычно имеет более строгий лимит, чем чтение публичных данных, а premium-клиенты могут иметь отдельный тариф.
Основные алгоритмы
| Алгоритм | Плюсы | Минусы |
|---|---|---|
| Fixed window | Простой и дешёвый | Допускает burst на границе временных окон |
| Sliding log | Точный | Требует хранить timestamps запросов |
| Sliding window counter | Баланс точности и стоимости | Сложнее fixed window |
| Token bucket | Разрешает контролируемые bursts и ограничивает среднюю скорость | Нужно корректно хранить состояние |
| Leaky bucket | Сглаживает выходной поток | Может увеличивать ожидание запросов |
Token bucket
У клиента есть bucket с максимальным числом токенов. Токены добавляются с фиксированной скоростью. Каждый запрос расходует один токен; если токенов нет, запрос отклоняется с 429 Too Many Requests. Такой алгоритм допускает кратковременный burst до размера bucket.
Распределённая архитектура
Client
|
CDN / API Gateway
|
Rate limiter middleware
|
Redis Cluster
|
Application servicesВ памяти одного API-instance хранить лимит нельзя: запросы одного клиента могут попасть на разные реплики. Redis даёт общее состояние, а Redis Lua script или атомарная команда позволяют выполнить проверку и обновление без race condition.
Пример Redis-ключа
ratelimit:{tenant_id}:{user_id}:{endpoint}Поведение при превышении лимита
Сервис возвращает 429 Too Many Requests. При необходимости добавляют Retry-After, X-RateLimit-Limit, X-RateLimit-Remaining и другие согласованные заголовки, чтобы клиент мог корректно замедлиться.
Отказ Redis
Нужно заранее выбрать политику fail-open или fail-closed. Fail-open сохраняет доступность API, но временно ослабляет защиту. Fail-closed лучше защищает дорогие и чувствительные endpoints, но может заблокировать легитимных пользователей при сбое лимитера. Решение зависит от риска конкретного endpoint.
Как ответить на собеседовании
Я выбираю ключ лимита, например user ID или API key, и алгоритм — часто token bucket либо sliding window. В распределённой системе храню состояние в Redis и использую атомарное обновление, чтобы несколько реплик не создали race condition. При превышении возвращаю 429, собираю метрики срабатываний и отдельно определяю fail-open/fail-closed политику для разных API.
Оцени свой прогресс