Прямой ответ
API rate limiting — ограничение частоты запросов от клиента (retry — «100 req/min на клиента» — ёмкость — «защита от перегрузки» — API Gateway), зачем: защита от перегрузки (ёмкость — «один клиент — все сервисы легли» — rate limiting — retry), fairness (ёмкость — «один клиент — все ресурсы» — rate limiting — «равные доли» — ёмкость), защита от атак (OWASP — «DDoS/brute force» — rate limiting — OWASP); стратегии rate limiting: (1) Token bucket (token bucket — «ведро с токенами» — token bucket — каждый запрос — токен из ведра — token bucket — «токены кончились — 429» — retry — токены пополняются с фиксированной скоростью (token bucket — «10 токенов/с» — token bucket — «ведро 100 токенов» — burst до 100 — token bucket — гибкий (burst + sustained — token bucket — «100 сразу, потом 10/с» — token bucket); (2) Sliding window (sliding window — «скользящее окно» — sliding window — подсчёт запросов за последние N секунд — sliding window — «100 req за последние 60 с» — sliding window — точнее fixed window (sliding window — «нет скачков на границе окна» — sliding window — сложнее (хранить timestamps — sliding window — Redis); (3) Fixed window (fixed window — «фиксированное окно» — fixed window — подсчёт запросов за фиксированный интервал — fixed window — «100 req с 00:00 до 00:01» — fixed window — проще sliding window (fixed window — «проще реализовать» — fixed window — но скачки на границе (fixed window — «00:00:59 — 100 req, 00:01:00 — 100 req» — 200 за 2 с — fixed window); (4) Leaky bucket (leaky bucket — «дырявое ведро» — leaky bucket — запросы — в очередь, обработка — с фиксированной скоростью — leaky bucket — «очередь полна — 429» — leaky bucket — сглаживает burst (leaky bucket — «100 сразу — обработаются по 10/с» — leaky bucket); где применять rate limiting: (1) API Gateway (API Gateway — Kong/nginx (API Gateway — «rate limiting на gateway» — API Gateway — «все запросы через gateway» — API Gateway — Kong); (2) nginx (nginx — limit_req_zone (nginx — «rate limiting в nginx» — nginx — nginx); (3) приложение (retry — middleware (retry — «rate limiting в приложении» — retry — гибче (по user_id/API-ключу — retry — Redis); ключи rate limiting (по чему ограничивать): IP (retry — «100 req/min на IP» — retry — «защита от DDoS» — OWASP), user_id (retry — «100 req/min на юзера» — retry — «fairness» — ёмкость), API-ключ (retry — «100 req/min на ключ» — retry — «разные лимиты для разных клиентов» — ёмкость), endpoint (retry — «10 req/min на /expensive» — retry — «дорогие эндпоинты — строже» — ёмкость); ответ при превышении (429 Too Many Requests): HTTP/1.1 429 Too Many Requests Retry-After: 60 X-RateLimit-Limit: 100 X-RateLimit-Remaining: 0 (retry — «429 + Retry-After» — retry — «клиент знает, когда повторить» — retry); когда rate limiting НЕ нужен: внутренние сервисы (микросервисы — «внутренние API — без rate limiting» — техдолг — «rate limiting для внутренних» — оверхед — техдолг), доверенные клиенты (retry — «доверенный клиент — без лимитов» — retry — «rate limiting для доверенных» — оверхед — техдолг); антипаттерны: rate limiting без Retry-After (retry — «429 без Retry-After» — клиент не знает, когда повторить — retry — «Retry-After обязателен» — retry), rate limiting только по IP (retry — «только IP» — NAT/proxy — много юзеров за одним IP — retry — «user_id/API-ключ точнее» — retry), rate limiting без мониторинга (метрики — «429 растут» — метрика — алерт — алерты — «лимиты слишком строгие» — incident).
Практика: rate limiting в nginx и Kong
nginx (nginx: http { limit_req_zone $binary_remote_addr zone=one:10m rate=10r/s; server { location /api/ { limit_req zone=one burst=20 nodelay; } } } (nginx — «10 req/s на IP, burst 20» — nginx — nginx); Kong (Kong: plugins: - name: rate-limiting config: minute: 100 policy: redis redis: host: redis port: 6379 (Kong — «100 req/min, Redis» — Kong — API Gateway); Redis (Redis — sliding window): 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) local count = redis.call('ZCARD', key) if count < limit then redis.call('ZADD', key, now, now) redis.call('EXPIRE', key, window) return 1 else return 0 end (Redis — «sliding window в Redis» — Redis — sliding window); Мониторинг (метрики: «rate_limit_hits», «429_responses» — метрики — алерты — алерты — «429 растут» — лимиты строгие — incident).
Практический сценарий: публичный API — rate limiting по API-ключу
Контекст: публичный API (внешние клиенты — OWASP), боль: один клиент шлёт 1000 req/s (ёмкость — API легло — incident — «один клиент — все легли» — ёмкость); решение — rate limiting по API-ключу: (1) Kong (API Gateway — rate-limiting плагин (Kong — «rate limiting на gateway» — API Gateway); (2) ключ — API-ключ (retry — «100 req/min на API-ключ» — retry — «разные клиенты — разные ключи» — retry); (3) лимиты (free — 100 req/min, pro — 1000 req/min (retry — «разные тарифы — разные лимиты» — ёмкость); (4) ответ 429 (retry — «429 + Retry-After: 60» — retry — «клиент знает, когда повторить» — retry); (5) Redis (Redis — sliding window (Redis — «100 req за последние 60 с» — Redis — sliding window); (6) мониторинг (метрики — «429 по клиентам» — метрики — алерты — алерты — «клиент X — много 429» — incident); результат: один клиент — не более 100 req/min (retry — «1000 req/s → 100 req/min» — retry — API не легло (ёмкость — 429 + Retry-After (retry — «клиент знает, когда повторить» — retry), разные тарифы (ёмкость — free/pro — retry); вывод: rate limiting решили задачу (защита от перегрузки — retry — ADR — «rate limiting без Retry-After» — антипаттерн — техдолг).
FAQ
Token bucket против sliding window — что выбрать? Token bucket (token bucket — «ведро с токенами» — token bucket — гибкий (burst + sustained — token bucket — «100 сразу, потом 10/с» — token bucket — проще реализовать (token bucket — «проще sliding window» — token bucket); Sliding window (sliding window — «скользящее окно» — sliding window — точнее (sliding window — «нет скачков на границе» — sliding window — сложнее (хранить timestamps — sliding window — Redis); когда token bucket: burst важен (token bucket — «100 сразу» — token bucket — проще (token bucket — «проще реализовать» — token bucket), когда sliding window: точность важна (sliding window — «нет скачков» — sliding window — Redis); «token bucket везде» — ок (проще, гибче — token bucket — ADR — техдолг).
Rate limiting по IP или по user_id/API-ключу? IP (retry — «100 req/min на IP» — retry — «защита от DDoS» — OWASP — но NAT/proxy (retry — «много юзеров за одним IP» — retry — «IP — неточно» — retry); user_id/API-ключ (retry — «100 req/min на юзера/ключ» — retry — «точно» — retry — «разные клиенты — разные лимиты» — retry); когда IP: DDoS-защита (OWASP — «IP — первый уровень» — OWASP), когда user_id/API-ключ: fairness (ёмкость — «разные клиенты — разные лимиты» — retry); «только IP» — антипаттерн (NAT/proxy — неточно — retry — user_id/API-ключ точнее — retry — ADR — техдолг).
Что такое Retry-After и зачем он нужен? Retry-After — HTTP-заголовок в ответе 429 (retry — «Retry-After: 60» — retry — «повторить через 60 с» — retry); зачем: клиент знает, когда повторить (retry — «429 + Retry-After» — retry — «клиент ждёт 60 с» — retry — «не спамить сервер» — retry); без Retry-After: клиент не знает, когда повторить (retry — «429 без Retry-After» — retry — «клиент спамит» — retry — «сервер перегружен» — retry); практика: всегда Retry-After (retry — «429 + Retry-After» — retry — «клиент знает» — retry — ADR — «429 без Retry-After» — антипаттерн — техдолг).
Rate limiting в nginx или Kong — что выбрать? nginx (nginx — «rate limiting в nginx» — nginx — проще (nginx — «nginx проще Kong» — nginx — но ограничен (nginx — «только IP» — nginx — «nginx — базовый rate limiting» — nginx); Kong (Kong — «rate limiting в Kong» — Kong — гибче (Kong — «по API-ключу/тарифу» — Kong — Redis (Kong — «Kong + Redis» — Kong — API Gateway); когда nginx: простой rate limiting (nginx — «по IP» — nginx — «nginx достаточно» — nginx), когда Kong: гибкий (Kong — «по API-ключу/тарифу» — Kong — API Gateway); «nginx для всего» — ок (проще — nginx — «Kong — оверхед» — техдолг — ADR — техдолг).
Источники: nginx docs, Kong docs, Wikipedia (token bucket) — ссылки в шапке.