⚙️ Теория
🎯 Зачем нужен rate limiting
Rate limiting решает минимум три задачи:
- защита от brute force;
- ограничение API abuse;
- смягчение L7 flood.
Важно: это не только security-инструмент, но и механизм контроля стоимости ресурса origin.
🧮 Алгоритмы
Token Bucket
- В «ведре» есть токены.
- Запрос расходует токен.
- Токены пополняются с заданной скоростью.
- Есть controlled burst за счёт накопленных токенов.
Хорошо подходит для API, где нужна эластичность на коротких пиках.
Leaky Bucket
- Входящие запросы выравниваются к фиксированной скорости «утечки».
- Избыточные заявки задерживаются или отбрасываются.
В NGINX limit_req описан как leaky bucket.
Fixed Window
- Счётчик запросов в фиксированном интервале (
N req / 1m). - Просто и дёшево.
- Минус: boundary burst на границе окон.
Sliding Window
- Учитывает текущее окно + часть предыдущего.
- Меньше boundary-артефактов, чем fixed window.
- Дороже по вычислению/состоянию.
Sliding Log
- Хранит timestamp каждого события в скользящем интервале.
- Самая точная модель лимита.
- Самая дорогая по памяти/операциям.
🌐 Distributed Rate Limiting
Проблемы распределённого лимитера:
race conditionsпри конкурентных обновлениях;consistencyмежду узлами;replication lag(особенно при async replication);- hot keys и неравномерная нагрузка.
Рабочие паттерны:
- sharded counters;
- consistent hashing по limit key;
- local + global hybrid (локальный токен-бакет + глобальный decision service).
🛠️ Ключевые параметры
limit key: IP / user / API token / route + token.burst: сколько допускаем кратковременно сверх steady rate.delay vs drop: деградировать мягко или жёстко отбрасывать.sliding granularity: точность окна vs стоимость вычисления.
❓ Что нужно уметь объяснить
Почему distributed rate limiting сложнее локального?
Локальный лимитер видит только локальный поток.
Distributed лимитер должен синхронно (или квазисинхронно) согласовать состояние между узлами под гонками и задержками репликации.
Почему ключ лимита важнее «самого алгоритма»?
Плохой key (IP за NAT, общий service token) даёт или массовые FP, или бесполезно слабую защиту.
Когда лучше delay, а когда drop?
delay: когда важно мягко сгладить всплеск.drop/429: когда нужно быстро освободить ресурсы origin.
🧪 Практика
1. Базовый лимит в NGINX
limit_req_zone $binary_remote_addr zone=api_limit:20m rate=20r/s;
location /api/ {
limit_req zone=api_limit burst=40 nodelay;
proxy_pass http://backend;
}
2. Проверьте поведение на burst
wrk -t8 -c400 -d30s https://example.com/api/
Смотрите:
- долю
429/503; - latency p95/p99;
- CPU на edge/origin.
3. Разделите ключи лимита
Сравните:
- только
IP; API token;- composite key (
route + token).
4. Для distributed-модели разделите local/global слой
- local limiter на edge (быстрый fail-fast);
- global limiter для account-wide quota/policy.
🧾 Вывод
Rate limiting это задача про математику + распределённые системы.
Надёжная защита достигается не выбором «модного» алгоритма, а правильной комбинацией key, burst, local/global архитектуры и измерением side-effects на latency/FP.
📚 Ссылки
- NGINX docs: limit_req (leaky bucket)
- RFC 2697: A Single Rate Three Color Marker (token bucket model)
- Envoy docs: Local rate limit (token bucket)
- Envoy docs: Global rate limit filter
- Envoy ratelimit service (reference implementation)
- RFC 6585: HTTP 429 Too Many Requests