Rate limiting в C++: полное руководство по ограничению запросов
Rate limiting (ограничение скорости запросов) — это критически важный механизм для защиты серверов, API и распределённых систем от перегрузок, DDoS-атак и случайных всплесков трафика. В C++ реализация rate limiting требует эффективных алгоритмов и правильного выбора библиотек. В этой статье мы разберём основные подходы, популярные библиотеки и приведём готовые примеры кода.
Что такое rate limiting и зачем он нужен?
Rate limiting устанавливает максимальное количество запросов, которое клиент или система может выполнить за определённый промежуток времени. Основные цели:
- Защита от злоупотреблений и перегрузки сервера
- Обеспечение равномерного распределения ресурсов между пользователями
- Предотвращение DDoS-атак и брутфорс-атак
- Контроль потребления API-квот
Основные алгоритмы rate limiting
В C++ чаще всего используются три классических алгоритма:
1. Token Bucket (Ведро токенов)
Самый популярный алгоритм. Представляет собой "ведро" с токенами, которые пополняются с постоянной скоростью. Каждый запрос забирает один токен. Если токенов нет — запрос отклоняется.
#include <chrono>
#include <mutex>
class TokenBucket {
private:
double tokens;
double maxTokens;
double refillRate; // токенов в секунду
std::chrono::steady_clock::time_point lastRefill;
std::mutex mtx;
public:
TokenBucket(double maxTokens, double refillRate)
: tokens(maxTokens), maxTokens(maxTokens), refillRate(refillRate) {
lastRefill = std::chrono::steady_clock::now();
}
bool tryConsume() {
std::lock_guard<std::mutex> lock(mtx);
refill();
if (tokens >= 1.0) {
tokens -= 1.0;
return true;
}
return false;
}
private:
void refill() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastRefill).count() / 1000.0;
tokens = std::min(maxTokens, tokens + elapsed * refillRate);
lastRefill = now;
}
};
2. Leaky Bucket (Дырявое ведро)
Запросы поступают в очередь (ведро), которая обрабатывается с фиксированной скоростью. Если ведро переполняется — запросы отбрасываются.
#include <queue>
#include <chrono>
class LeakyBucket {
private:
std::queue<int> bucket;
size_t capacity;
double leakRate; // запросов в секунду
std::chrono::steady_clock::time_point lastLeak;
std::mutex mtx;
public:
LeakyBucket(size_t capacity, double leakRate)
: capacity(capacity), leakRate(leakRate) {
lastLeak = std::chrono::steady_clock::now();
}
bool addRequest(int request) {
std::lock_guard<std::mutex> lock(mtx);
leak();
if (bucket.size() < capacity) {
bucket.push(request);
return true;
}
return false;
}
private:
void leak() {
auto now = std::chrono::steady_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - lastLeak).count() / 1000.0;
size_t toRemove = static_cast<size_t>(elapsed * leakRate);
while (toRemove > 0 && !bucket.empty()) {
bucket.pop();
toRemove--;
}
lastLeak = now;
}
};
3. Sliding Window Log (Скользящее окно с логом)
Хранит временные метки запросов и отбрасывает те, что выходят за пределы окна. Более точный, но требует больше памяти.
Популярные библиотеки C++ для rate limiting
Хотя стандартная библиотека C++ не содержит готовых решений, существуют проверенные библиотеки:
- Boost.TokenBucket — часть библиотеки Boost.Utilities. Предоставляет готовую реализацию Token Bucket. Требует Boost 1.75+.
- libcurl с опцией CURLOPT_MAX_RECV_SPEED_LARGE — для ограничения скорости загрузки/отправки данных по сети.
- Abseil (Google) — содержит
absl::synchronization::BlockingCounterдля синхронизации, но не имеет готового rate limiter — потребуется реализация поверх. - Poco::Util::RateLimiter — в библиотеке Poco (начиная с версии 1.12) есть встроенная поддержка ограничения скорости.
Пример использования Boost.TokenBucket
<