Rate Limiting в PHP: полное руководство по ограничению запросов

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

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

Начать курс

Rate Limiting в PHP: защита API и оптимизация нагрузки

Rate limiting (ограничение скорости запросов) — критически важный механизм для любого веб-приложения на PHP, особенно для публичных API. Он предотвращает злоупотребления, защищает от DDoS-атак, снижает нагрузку на сервер и обеспечивает справедливое распределение ресурсов между пользователями. В этой статье мы подробно разберем популярные библиотеки и инструменты для реализации rate limiting в PHP.

Основные алгоритмы rate limiting

Прежде чем выбирать библиотеку, важно понимать базовые алгоритмы:

  • Token Bucket (Токеновое ведро) — самый гибкий алгоритм. Ведро содержит N токенов, каждый запрос забирает токен. Токены восстанавливаются с заданной скоростью.
  • Leaky Bucket (Дырявое ведро) — запросы обрабатываются с постоянной скоростью. Избыточные запросы отбрасываются или ставятся в очередь.
  • Fixed Window (Фиксированное окно) — запросы считаются за фиксированный интервал (например, 60 секунд). Простой, но может пропускать пиковые нагрузки на границах окон.
  • Sliding Window (Скользящее окно) — более точный алгоритм, учитывающий запросы в плавающем временном окне. Использует логи или счетчики с временными метками.

Популярные библиотеки для rate limiting в PHP

1. Symfony Rate Limiter (symfony/rate-limiter)

Официальный компонент Symfony, но работает и как standalone-библиотека. Поддерживает все основные алгоритмы: Token Bucket, Fixed Window, Sliding Window. Идеально подходит для проектов на Symfony или Laravel.

use Symfony\Component\RateLimiter\RateLimiterFactory;
use Symfony\Component\RateLimiter\Storage\InMemoryStorage;

$factory = new RateLimiterFactory([
    'id' => 'api',
    'policy' => 'token_bucket',
    'limit' => 100,
    'rate' => ['interval' => '1 minute'],
], new InMemoryStorage());

$limiter = $factory->create('user_123');

if ($limiter->consume(1)->isAccepted()) {
    // Обработка запроса
} else {
    // Возврат 429 Too Many Requests
    header('HTTP/1.1 429 Too Many Requests');
    echo json_encode(['error' => 'Rate limit exceeded']);
}

2. nikic/FastRoute с middleware rate limiting

Легковесное решение для проектов без фреймворков. Использует PHP-реализацию Token Bucket с хранением в Redis или Memcached.

class RateLimitMiddleware {
    private $redis;
    private $maxRequests = 100;
    private $window = 60; // секунд

    public function handle($request, $next) {
        $key = 'rate_limit:' . $_SERVER['REMOTE_ADDR'];
        $current = $this->redis->incr($key);
        
        if ($current === 1) {
            $this->redis->expire($key, $this->window);
        }

        if ($current > $this->maxRequests) {
            return new Response('Too Many Requests', 429);
        }

        return $next($request);
    }
}

3. Laravel Rate Limiter (встроенный)

В Laravel rate limiting встроен в ядро. Использует Redis или кэш. Поддерживает именованные лимитеры и сегментирование по пользователям.

// В RouteServiceProvider
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Http\Request;

protected function configureRateLimiting()
{
    RateLimiter::for('api', function (Request $request) {
        return Limit::perMinute(60)->by($request->user()?->id ?: $request->ip());
    });
}

// В контроллере
public function index()
{
    $executed = RateLimiter::attempt(
        'send-message:'.$user->id,
        $perMinute = 5,
        function() {
            // Отправка сообщения
        }
    );
    
    if (! $executed) {
        return response('Too Many Attempts.', 429);
    }
}

4. PHP RateLimit (php-rateLimit/php-rateLimit)

Универсальная библиотека, поддерживающая множество бэкендов: файлы, Redis, PDO, APCu. Реализует алгоритмы Sliding Window и Token Bucket.

use RateLimit\RateLimiter;
use RateLimit\Adapter\RedisAdapter;
use RateLimit\Adapter\FileAdapter;

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$adapter = new RedisAdapter($redis);

$rateLimiter = new RateLimiter($adapter, 100, 3600); // 100 запросов в час

if ($rateLimiter->limit('user:'.$userId)) {
    // Запрос разрешен
} else {
    // Превышен лимит
}

Практические рекомендации

  • Выбирайте алгоритм

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