Rate Limiting в PHP: как защитить API от перегрузок и ботов

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

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

Начать курс

Rate Limiting и Throttling в PHP: Обуздайте поток запросов

Представьте: вы запустили крутой API для своего сервиса. Всё работает идеально, пока один «шальной» клиент не начинает долбить ваш сервер тысячами запросов в секунду. Или, что ещё хуже, бот-парсер решает выкачать всю вашу базу данных. Знакомо? Если нет — повезло. Но лучше быть готовым. Именно здесь на сцену выходят rate limiting и throttling — два рыцаря, которые защитят ваш бэкенд от перегрузок.

В этой статье мы разберёмся, как реализовать ограничение запросов в PHP, рассмотрим реальные примеры кода и поймём, какой метод выбрать для вашего проекта. Спойлер: писать свой велосипед не придётся, но базовые алгоритмы знать полезно.

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

Часто эти термины используют как синонимы, но между ними есть тонкая грань.

  • Rate Limiting — это жёсткий лимит. Вы говорите: «Максимум 100 запросов в минуту». Как только лимит исчерпан — клиент получает HTTP 429 Too Many Requests и идёт пить чай.
  • Throttling — это более мягкий контроль. Вместо блокировки вы просто замедляете обработку запросов сверх лимита, ставите их в очередь или снижаете приоритет.

На практике throttling в PHP часто реализуют через задержки (sleep) или очереди, а rate limiting — через подсчёт запросов в единицу времени. Для большинства API-шлюзов оптимальна комбинация обоих подходов.

Почему это критически важно для вашего проекта?

Причин внедрить ограничение запросов PHP как минимум три:

  • Защита от DDoS и ботов — даже простой скрипт может положить сервер, если не контролировать частоту вызовов.
  • Честное распределение ресурсов — один «жадный» клиент не должен лишать сервиса остальных пользователей.
  • Стабильность и предсказуемость — вы знаете, какой пиковой нагрузки ждать, и можете правильно настроить инфраструктуру.

Алгоритмы Rate Limiting: выбираем стратегию

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

1. Fixed Window (Фиксированное окно)

Самый простой подход. Делим время на отрезки (например, по минутам). Запоминаем, сколько запросов пришло в текущем окне. Достиг лимита — блокируем до следующего окна.

Минус: на стыке окон возможен всплеск — клиент может отправить 100 запросов в конце минуты и ещё 100 в начале следующей, что даст 200 за 2 секунды.

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

Более точный алгоритм. Вместо фиксированных отрезков мы смотрим на последние N секунд. Запросы «стареют» и выпадают из окна. Это исключает всплески на границах.

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

Элегантный алгоритм из мира сетей. В «ведре» лежат токены. Каждый запрос забирает один токен. Токены пополняются с заданной скоростью. Если ведро пусто — запрос отклоняется. Отлично подходит для throttling php, так как позволяет небольшие всплески трафика.

Практическая реализация: от простого к сложному

Давайте перейдём к коду. Начнём с базового примера на файлах, а затем посмотрим на промышленные решения.

Базовый Rate Limiter на файлах (для обучения)

Этот пример использует файлы для хранения счётчиков. Не годится для продакшена, но отлично демонстрирует принцип.

<?php
class SimpleRateLimiter {
    private string $storageDir;
    private int $maxRequests;
    private int $windowSeconds;

    public function __construct(string $storageDir, int $maxRequests = 100, int $windowSeconds = 60) {
        $this->storageDir = rtrim($storageDir, '/') . '/';
        $this->maxRequests = $maxRequests;
        $this->windowSeconds = $windowSeconds;
        if (!is_dir($this->storageDir)) {
            mkdir($this->storageDir, 0755, true);
        }
    }

    public function check(string $identifier): bool {
        $file = $this->storageDir . md5($identifier) . '.lock';
        $now = time();
        $data = ['count' => 0, 'window_start' => $now];

        if (file_exists($file)) {
            $data = json_decode(file_get_contents($file), true);
            // Если окно истекло — сбрасываем
            if ($data['window_start'] + $this->windowSeconds < $now) {
                $data = ['count' => 0, 'window_start' => $now];
            }
        }

        $data['count']++;
        file_put_contents($file, json_encode($data), LOCK_EX);

        return $data['count'] <= $this->maxRequests;
    }
}

// Использование
$limiter = new SimpleRateLimiter('/tmp/ratelimit/', 5, 10); // 5 запросов за 10 секунд
$userId = 'user_123';

if ($limiter->check($userId)) {
    echo "Запрос разрешён";
} else {
    http_response_code(429);
    echo "Слишком много запросов. Подождите.";
}
?>

Этот код — классический fixed window rate limiting php. Он рабочий, но для серьёзных проектов используйте Redis.

Промышленный подход: Rate Limiter на Redis

Redis идеален для rate limiting благодаря атомарным операциям и встроенному TTL. Вот пример реализации алгоритма sliding window log.

<?php
class RedisRateLimiter {
    private \Redis $redis;
    private int $maxRequests;
    private int $windowSeconds;

    public function __construct(\Redis $redis, int $maxRequests = 100, int $windowSeconds = 60) {
        $this->redis = $redis;
        $this->maxRequests = $maxRequests;
        $this->windowSeconds = $windowSeconds;
    }

    public function allow(string $key): bool {
        $now = microtime(true);
        $windowStart = $now - $this->windowSeconds;
        $redisKey = "ratelimit:{$key}";

        // Удаляем старые записи и добавляем текущую
        $this->redis->multi();
        $this->redis->zRemRangeByScore($redisKey, 0, $windowStart);
        $this->redis->zAdd($redisKey, $now, "{$now}");
        $this->redis->expire($redisKey, $this->windowSeconds + 1);
        $this->redis->exec();

        // Считаем количество запросов в окне
        $count = $this->redis->zCard($redisKey                

Блоги

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