Rate Limiting API: Алгоритмы и реализация
Ваш API под атакой. Не изощрённых хакеров, а простого скрипта, который бомбардирует ваши эндпоинты тысячи раз в секунду, потребляя ресурсы и ухудшая сервис для легитимных пользователей. Без ограничения скорости один неправильно работающий клиент может обрушить всё приложение. Это руководство объясняет основные алгоритмы и показывает практические реализации в Node.js и Nginx для защиты вашего API.
Почему ограничение скорости важно
Ограничение скорости контролирует, сколько запросов клиент может сделать за заданный временной интервал. Это ваша первая линия защиты от:
- Атак методом перебора на эндпоинты входа или API-ключей
- Отказа в обслуживании (DoS) из-за чрезмерных запросов
- Истощения ресурсов из-за дорогостоящих операций, таких как запросы к базе данных или обработка файлов
- Злоупотребления бесплатными тарифами скраперами или ботами
Помимо безопасности, ограничение скорости обеспечивает справедливое использование и помогает применять бизнес-правила, например, тарифные планы.
Алгоритмы ограничения скорости
Четыре алгоритма доминируют в ограничении скорости API. У каждого есть компромиссы в точности, использовании памяти и обработке всплесков.
1. Фиксированное окно
Подсчитывает запросы в фиксированных временных интервалах (например, 100 запросов в минуту). Когда окно сбрасывается, счётчик сбрасывается.
- Плюсы: Простой, низкое потребление памяти (один счётчик на клиента).
- Минусы: Всплеск на границах окна. Клиент может отправить 100 запросов в 12:00:59 и ещё 100 в 12:01:00, фактически 200 запросов за две секунды.
2. Скользящее окно
Отслеживает временные метки каждого запроса и подсчитывает, сколько попадает в последние N секунд. Это сглаживает всплески.
- Плюсы: Точный, без пиков на границах.
- Минусы: Более высокое потребление памяти (хранение временных меток) или использование счётчика скользящего окна со взвешенным средним.
3. Маркерное ведро
Ведро содержит маркеры. Маркеры добавляются с фиксированной скоростью. Каждый запрос потребляет один маркер. Если ведро пусто, запрос отклоняется.
- Плюсы: Позволяет всплески до размера ведра, затем обеспечивает среднюю скорость.
- Минусы: Требуется хранить количество маркеров и время последнего пополнения для каждого клиента.
4. Дырявое ведро
Запросы попадают в очередь (ведро) и обрабатываются с постоянной скоростью. Если очередь заполнена, запросы отбрасываются.
- Плюсы: Сглаживает трафик до постоянной скорости, идеально для защиты нижестоящих сервисов.
- Минусы: Добавляет задержку; не подходит для API реального времени.
| Алгоритм | Обработка всплесков | Память | Сценарий использования |
|---|---|---|---|
| Фиксированное окно | Плохо | Низкая | Простые API |
| Скользящее окно | Хорошо | Средняя | Общего назначения |
| Маркерное ведро | Отлично | Средняя | API с допустимыми всплесками |
| Дырявое ведро | Нет | Средняя | Сглаживание трафика |
Реализация ограничения скорости в Node.js
Мы реализуем ограничитель скорости на основе маркерного ведра с использованием Express и Redis для распределённого состояния. Redis необходим, когда у вас несколько экземпляров сервера.
Шаг 1: Установите зависимости
npm install express redis
Шаг 2: Создайте middleware ограничителя скорости
const redis = require('redis');
const client = redis.createClient();
async function tokenBucketLimiter(req, res, next) {
const key = `rate_limit:${req.ip}`;
const capacity = 10; // max tokens
const refillRate = 1; // tokens per second
const now = Date.now();
const data = await client.hGetAll(key);
let tokens = data.tokens ? parseFloat(data.tokens) : capacity;
let lastRefill = data.lastRefill ? parseInt(data.lastRefill) : now;
// Refill tokens based on elapsed time
const elapsed = (now - lastRefill) / 1000;
tokens = Math.min(capacity, tokens + elapsed * refillRate);
if (tokens < 1) {
return res.status(429).json({ error: 'Too many requests' });
}
tokens -= 1;
await client.hSet(key, {
tokens: tokens.toString(),
lastRefill: now.toString()
});
await client.expire(key, 60); // auto-cleanup
next();
}
app.use(tokenBucketLimiter);
Этот middleware проверяет и обновляет количество маркеров атомарно. Для продакшена используйте транзакции Redis или скрипты Lua, чтобы избежать состояний гонки.
Реализация ограничения скорости в Nginx
Nginx предлагает встроенное ограничение скорости с модулем limit_req. Он использует алгоритм дырявого ведра.
Шаг 1: Определите зону ограничения скорости
В блоке http файла nginx.conf:
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
Это создаёт зону размером 10 МБ с именем api, которая допускает 10 запросов в секунду на IP.
Шаг 2: Примените ограничение
В вашем блоке location:
location /api/ {
limit_req zone=api burst=20 nodelay;
proxy_pass http://backend;
}
burst=20 допускает короткие всплески до 20 запросов. nodelay обрабатывает всплесковые запросы немедленно, а не ставит их в очередь.
Лучшие практики ограничения скорости API
- Возвращайте правильные коды состояния: Используйте
429 Too Many Requestsи включайте заголовокRetry-After. - Правильно идентифицируйте клиентов: Используйте API-ключи или ID пользователей вместо IP-адресов, когда это возможно, так как IP могут быть общими (NAT) или подделаны.
- Распределяйте состояние: Используйте Redis или аналогичное хранилище для развёртываний с несколькими экземплярами.
- Логируйте и мониторьте: Отслеживайте срабатывания ограничений, чтобы обнаруживать атаки и корректировать пороги. Инструменты вроде Nginx Log Analyzer помогут анализировать ответы 429 и выявлять злоупотребляющие IP.
- Сообщайте об ограничениях: Документируйте ограничения скорости в документации API и включайте заголовки вроде
X-RateLimit-Limit,X-RateLimit-Remaining.
FAQ
В чём разница между ограничением скорости и троттлингом?
Ограничение скорости блокирует запросы сверх порога, тогда как троттлинг замедляет их (например, через очередь или задержку). Ограничение скорости бинарно; троттлинг постепенен.
Какой алгоритм ограничения скорости мне использовать?
Для большинства API маркерное ведро предлагает хороший баланс: позволяет всплески, но обеспечивает среднюю скорость. Если нужно строгое сглаживание, используйте дырявое ведро. Для простоты фиксированное окно подходит для низконагруженных API.
Как обрабатывать ограничение скорости для аутентифицированных и анонимных пользователей?
Применяйте более строгие ограничения к анонимным пользователям (например, по IP) и более щедрые к аутентифицированным (например, по API-ключу). Вы также можете реализовать многоуровневые ограничения на основе планов подписки.
Готовы проанализировать трафик вашего API? Используйте наш Nginx Log Analyzer для парсинга логов, выявления нарушений ограничений и оптимизации порогов.