Rate Limiting и Throttling в C++: Как защитить API от перегрузок

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Когда ваш API начинает задыхаться: Введение в rate limiting

Представьте ситуацию: вы написали идеальный сервер на C++. Он быстрый, эффективный, использует все ядра процессора. Вы запускаете его в продакшн, и... через пять минут он падает. Что случилось? Оказалось, какой-то скрипт-хулиган решил «проверить» ваш API, отправляя тысячи запросов в секунду.

Знакомо? Если нет — вам повезло. Но рано или поздно каждый разработчик сталкивается с необходимостью контролировать нагрузку. Именно здесь на сцену выходят rate limiting и throttling — два мощных механизма, которые спасают серверы от перегрузок и обеспечивают равномерное распределение ресурсов.

В этой статье мы разберёмся, как реализовать эти механизмы на C++. Вы узнаете не только теорию, но и получите рабочий код, который можно сразу использовать в своих проектах. Поехали!

Rate Limiting vs Throttling: В чём разница?

Прежде чем погружаться в код, давайте чётко разделим эти два понятия. Многие путают их, но разница есть, и она критична.

Rate Limiting (Ограничение скорости)

Это «шлагбаум». Вы говорите: «За одну секунду — не больше 10 запросов. Всё, что сверху, — в бан (или очередь)». Rate limiting защищает ваш API от лавинообразных запросов, DDoS-атак и случайных всплесков трафика.

Throttling (Дросселирование)

Это «регулятор громкости». Вы не блокируете запросы полностью, а замедляете их обработку. Например, если клиент отправляет 100 запросов в секунду, вы обрабатываете их, но с задержкой в 10 мс между каждым. Throttling — более «вежливый» способ управления нагрузкой, он не отбрасывает данные, а просто сглаживает пики.

В реальных проектах эти техники часто комбинируют: сначала throttling для сглаживания, а при превышении критического порога — rate limiting с возвратом ошибки 429 (Too Many Requests).

Алгоритмы rate limiting в C++

Существует несколько классических алгоритмов. Мы разберём два самых популярных и эффективных: Token Bucket и Sliding Window. Оба легко реализуются на C++ и дают отличные результаты.

1. Token Bucket (Ведро токенов)

Представьте ведро, в которое каждую секунду капает N токенов. Каждый запрос забирает один токен. Если ведро пустое — запрос отклоняется. Просто и гениально.

Вот реализация на современном C++ с использованием std::chrono:

#include <chrono>
#include <mutex>
#include <atomic>

class TokenBucket {
public:
    TokenBucket(size_t max_tokens, double refill_rate)
        : max_tokens_(max_tokens)
        , refill_rate_(refill_rate)
        , current_tokens_(max_tokens)
        , last_refill_(std::chrono::steady_clock::now()) {}

    bool try_consume() {
        std::lock_guard<std::mutex> lock(mutex_);
        
        // Вычисляем, сколько токенов накопилось с прошлого раза
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(now - last_refill_);
        
        // Пополняем ведро
        double tokens_to_add = elapsed.count() * refill_rate_ / 1000.0;
        current_tokens_ = std::min(max_tokens_, current_tokens_ + tokens_to_add);
        last_refill_ = now;
        
        // Пытаемся взять токен
        if (current_tokens_ > 0) {
            --current_tokens_;
            return true;
        }
        return false;
    }

private:
    std::mutex mutex_;
    size_t max_tokens_;           // Максимальная ёмкость ведра
    double refill_rate_;          // Токенов в секунду
    double current_tokens_;       // Текущее количество токенов
    std::chrono::steady_clock::time_point last_refill_;
};

Использование:

TokenBucket limiter(10, 1.0); // 10 токенов, пополнение 1 токен/сек

// В обработчике запроса:
if (limiter.try_consume()) {
    process_request();
} else {
    send_429_error();
}

Плюсы: Простота, мало памяти, работает для равномерного трафика.
Минусы: Не идеален для burst-трафика (всплесков).

2. Sliding Window (Скользящее окно)

Этот алгоритм более точен. Вы запоминаете временные метки последних N запросов и проверяете, сколько из них попало в последнюю секунду (или любой другой интервал).

Реализация с кольцевым буфером для эффективности:

#include <vector>
#include <chrono>
#include <mutex>

class SlidingWindowLimiter {
public:
    SlidingWindowLimiter(size_t max_requests, std::chrono::milliseconds window_duration)
        : max_requests_(max_requests)
        , window_duration_(window_duration)
        , timestamps_(max_requests, std::chrono::steady_clock::time_point::min())
        , head_(0), count_(0) {}

    bool try_consume() {
        std::lock_guard<std::mutex> lock(mutex_);
        auto now = std::chrono::steady_clock::now();
        
        // Удаляем устаревшие метки
        while (count_ > 0 && (now - timestamps_[(head_ - count_ + max_requests_) % max_requests_]) > window_duration_) {
            --count_;
        }
        
        // Проверяем лимит
        if (count_ < max_requests_) {
            timestamps_[head_] = now;
            head_ = (head_ + 1) % max_requests_;
            ++count_;
            return true;
        }
        return false;
    }

private:
    std::mutex mutex_;
    size_t max_requests_;
    std::chrono::milliseconds window_duration_;
    std::vector<std::chrono::steady_clock::time_point> timestamps_;
    size_t head_;
    size_t count_;
};

Плюсы: Точность, хорошо обрабатывает burst-трафик, легко настраивается.
Минусы: Требует больше памяти (хранит метки).

Throttling в C++: Плавное замедление

А теперь перейдём к throttling. Здесь наша задача — не блокировать, а регулировать скорость обработки. Классический подход — использовать таймеры или std::this_thread::sleep_for.

Пример простого throttler, который ограничивает количество операций в секунду:

#include <chrono>
#include <thread>
#include <atomic>

class SimpleThrottler {
public:
    explicit SimpleThrottler(double operations_per_second)
        : min_interval_(std::chrono::microseconds(static_cast<int>(1'000'000 / operations_per_second)))
        , last_time_(std::chrono::steady_clock::now()) {}

    void throttle() {
        auto now = std::chrono::steady_clock::now();
        auto elapsed = std::chrono::duration_cast<std::chrono::microseconds>(now - last_time_);
        
        if (elapsed < min_interval_) {
            std::this_thread                

Блоги

Рекомендаций по книгам