Представьте: ваш API падает под нагрузкой, а в логах — тысячи запросов от одного IP. На одном из наших проектов партнёрский сервис зациклился и отправил 10 000 запросов в минуту. Без rate limiting это привело к падению базы данных и простою на 4 часа. Мы внедрили ограничения на уровне Nginx и приложения — проблема исчезла. Такие кейсы — наша ежедневная работа.
Мы реализуем rate limiting для API веб-приложений на любом стеке — от Laravel до NestJS, с Redis-бэкендом, разными лимитами по тарифам и правильными заголовками ответа. Ниже — проверенные подходы, которые помогут избежать простоев и защитить инфраструктуру.
Как выбрать алгоритм rate limiting?
Выбор алгоритма зависит от характера нагрузки. Sliding Window усредняет запросы по скользящему окну, избегая уязвимости Fixed Window: когда счётчик сбрасывается каждые N секунд, клиент может отправить 100 запросов в конце окна и 100 в начале следующего — получаем 200 за секунду. Sliding Window этого не допускает. Token Bucket накапливает токены со скоростью refill rate, позволяя burst до bucket size — подходит для интеграций с нерегулярным трафиком. Leaky Bucket ставит запросы в очередь с фиксированным drain rate, давая максимально ровную нагрузку, но возможны задержки.
| Алгоритм | Точность учёта | Burst-защита | Сложность реализации | Рекомендуемый сценарий |
|---|---|---|---|---|
| Fixed Window | Низкая | Нет | Низкая | Простые тарифы |
| Sliding Window | Высокая | Да | Средняя | API общего назначения |
| Token Bucket | Средняя | Да | Средняя | Burst-трафик партнёров |
| Leaky Bucket | Высокая | Нет | Высокая | Real-time системы |
Для большинства веб-приложений оптимален Sliding Window с Redis. Он даёт равномерное ограничение без всплесков на границах окна. Если API обрабатывает burst-трафик (например, от партнёрских интеграций), выбирайте Token Bucket. Для real-time систем со стабильным потоком — Leaky Bucket.
Почему важно комбинировать rate limiting на Nginx и в приложении?
Nginx выступает первой линией защиты: он может отсечь явные аномалии (например, более 1000 запросов в секунду с одного IP) на уровне сервера, не нагружая приложение. Приложение же обеспечивает гибкие лимиты по пользователям и тарифам. Такая двухуровневая защита эффективнее любой однослойной реализации. Вы можете настроить Nginx с модулем limit_req для общей защиты, а в приложении задать более тонкие правила.
Реализация в Laravel и NestJS
Laravel — используем Throttle middleware и RateLimiter:
// app/Providers/RouteServiceProvider.php RateLimiter::for('api', function (Request $request) { $user = $request->user(); if (!$user) return Limit::perMinute(30)->by($request->ip()); return match($user->plan) { 'enterprise' => Limit::perMinute(1000)->by($user->id), 'pro' => Limit::perMinute(300)->by($user->id), default => Limit::perMinute(60)->by($user->id), }; }); Route::middleware(['auth:sanctum', 'throttle:api'])->group(function () { Route::apiResource('articles', ArticleController::class); }); NestJS — модуль @nestjs/throttler с Redis-хранилищем:
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'; import { ThrottlerStorageRedisService } from 'nestjs-throttler-storage-redis'; ThrottlerModule.forRoot({ throttlers: [ { name: 'short', ttl: 1000, limit: 10 }, { name: 'medium', ttl: 60000, limit: 300 }, { name: 'long', ttl: 3600000, limit: 5000 }, ], storage: new ThrottlerStorageRedisService(redisClient), }); | Аспект | Laravel | NestJS | Nginx |
|---|---|---|---|
| Гибкость лимитов | Высокая (по пользователям, тарифам) | Высокая (по группам маршрутов) | Низкая (только по IP) |
| Скорость работы | Средняя (PHP) | Высокая (Node.js) | Максимальная (C) |
| Централизованное хранение | Redis | Redis | Нет (либо внешний модуль) |
| Заголовки Retry-After | Автоматически | Автоматически | Вручную |
Дистрибутированный rate limiting с Lua-скриптом
Для нескольких серверов приложения — централизованный Redis и Lua-скрипт для атомарного счётчика:
-- sliding_window.lua local key = KEYS[1] local now = tonumber(ARGV[1]) local window = tonumber(ARGV[2]) local limit = tonumber(ARGV[3]) redis.call('ZREMRANGEBYSCORE', key, 0, now - window) local count = redis.call('ZCARD', key) if count < limit then redis.call('ZADD', key, now, now .. math.random()) redis.call('EXPIRE', key, window / 1000) return 1 end return 0 Этот скрипт гарантирует атомарность и подходит для распределённой архитектуры. Мы используем его в проектах с высокой нагрузкой.
Bypass-стратегии: что не ограничивать
Некоторые запросы должны обходить лимиты: внутренние сервисы (IP-whitelist), webhook-эндоинты, health check /health. Реализуем через условие в RateLimiter:
RateLimiter::for('api', function (Request $request) { if ($request->ip() === config('services.internal_ip')) { return Limit::none(); } // ... }); Типичные ошибки при внедрении rate limiting
В 70% проектов, которые мы аудировали, использовали Fixed Window без учёта burst — это позволяет клиенту обойти лимит. Ещё 20% игнорируют заголовки Retry-After, из-за чего клиенты не знают, когда повторять запрос. Ограничение health check приводит к ложным срабатываниям мониторинга. Игнорирование распределённого хранения — при нескольких серверах счётчики расходятся.
Что входит в реализацию под ключ
- Анализ текущей архитектуры и профиля нагрузки.
- Выбор алгоритма (рекомендуем Sliding Window + Redis).
- Настройка лимитов по эндпоинтам и тарифам.
- Интеграция с Nginx как первая линия защиты.
- Добавление заголовков
X-RateLimit-*иRetry-After. - Мониторинг 429-ответов (Grafana + алерты).
- Документация и обучение команды.
Сроки
Базовая реализация с Redis и Sliding Window — от 1 до 2 дней. Расширенная с Nginx, Lua-скриптами и мониторингом — от 3 до 4 дней. Стоимость рассчитывается индивидуально после анализа проекта. Оценим ваш проект за 1 день — свяжитесь с нами для консультации. Закажите внедрение rate limiting, и ваш API выдержит любую нагрузку.
Мы реализовали rate limiting для 20+ проектов разной сложности. Гарантируем, что после нашей реализации вы забудете о проблемах с перегрузками.







