Атомарное ограничение частоты запросов API в Redis с помощью Lua: стратегии для разработчиков
Готовое к использованию ограничение частоты API для разработчиков: выбирайте скользящее окно, токен или текучее ведро; используйте атомарные Lua-скрипты Redis и возвращайте заголовки RateLimit.

Для большинства публичных API счетчик скользящего окна является правильным выбором по умолчанию: он обеспечивает почти точную точность с постоянным объемом памяти на клиента. Обратитесь к корзине с токенами, когда вам нужно разрешить контролируемые всплески, или к текучей корзине, когда хрупкий нижестоящий сервис требует строгого и равномерного оттока. Выбирайте, исходя из бюджета памяти, допуска к всплескам и того, насколько хрупки ваши нижестоящие системы, и реализуйте логику принудительного применения с атомарными скриптами Redis и Lua, поддерживаемыми заголовками RateLimit.
Кратко:
- Счетчик скользящего окна балансирует между точностью и эффективностью памяти, что делает его подходящим для общих публичных API с высоким трафиком.
- Реализация ограничителей скорости атомарно с помощью скриптов Redis Lua предотвращает состояния гонки и поддерживает согласованность между несколькими экземплярами приложения.
- Используйте скорость пополнения немного выше обычного трафика P99 и установите емкость корзины для обработки 5–10 секунд всплескового трафика без ложных срабатываний.
- Информируйте клиентов об их ограничениях скорости через стандартизированные заголовки и предоставляйте понятные ответы Retry-After, чтобы помочь им эффективно управлять повторными попытками.
- Применяйте принудительное применение на уровне границы (например, NGINX) и уровня приложения, и выбирайте алгоритмы на основе ограничений памяти, допуска к всплескам и хрупкости нижестоящих систем.
Оглавление
- Как сравниваются основные алгоритмы ограничения скорости
- Примечания по реализации для каждого алгоритма
- Создание ограничителей, устойчивых к масштабированию экземпляров
- Выбор правильного алгоритма для вашей конечной точки
- Передача ограничений клиентам через заголовки
- Настройка скоростей пополнения, емкости и оповещений
- Где эти паттерны встречаются в продакшене
- Справедливость, предотвращение злоупотреблений и этика троттлинга
- Почему разумные умолчания спасают вас от самих себя в будущем
- Где почитать больше о стандартах ограничения скорости
- Источники
- FAQ
Как сравниваются основные алгоритмы ограничения скорости
Пять алгоритмов покрывают почти любой продакшн-кейс; каждый находится в своей точке компромисса между затратами памяти, допуском к всплескам и точностью.
- Фиксированное окно: один счетчик на временной слот, самый дешевый в эксплуатации, но допускает всплеск у границы окна.
- Лог скользящего окна: хранит временную метку каждого запроса, давая точный счет ценой памяти, растущей с объемом запросов.
- Счетчик скользящего окна: объединяет счетчики текущего и предыдущего окон в взвешенную оценку, удерживая память постоянной на клиента.
- Корзина с токенами: отслеживает количество токенов и временную метку пополнения, позволяя клиентам тратить накопленную емкость в коротких всплесках.
- Текучая корзина: обрабатывает запросы с фиксированной выходной скоростью независимо от того, как они поступают, сглаживая трафик для хрупких нижестоящих систем.
Фиксированное окно подходит для внутренних лимитов с низкими ставками, скользящее окно с журналом — для эндпоинтов с низкой нагрузкой и высокими ставками, например, входа в систему, скользящее окно со счетчиком — для общих публичных API, корзина токенов — для API для разработчиков, которым нужен запас на пиковые нагрузки, а текучее ведро — для очередей перед бэкендами, чувствительными к частоте запросов.
Примечания по реализации для каждого алгоритма
Каждый алгоритм требует определенной формы состояния и несет свои затраты на выполнение, поэтому выбор алгоритма — это, по сути, выбор структуры данных.

Фиксированное окно хранит один счетчик, ключ которого состоит из ID клиента и временного интервала; он увеличивается при каждом запросе и сбрасывается при смене интервала. Его просто понять, но клиент может отправить полный объем квоты в самом конце одного окна и еще один полный объем в начале следующего, удваивая эффективную частоту на коротком промежутке. Это делает его приемлемым для грубых лимитов с низким риском, но рискованным для чего-либо, связанного с безопасностью.
Скользящее окно с журналом хранит отсортированное множество временных меток запросов для каждого клиента, удаляя всё, что старше окна, при каждой проверке. Оно точное, так как считает реальные запросы, а не оценку, но память растет с объемом запросов, что делает его дорогим для клиентов с высоким трафиком.
Скользящее окно со счетчиком избегает этих затрат, храня всего два счетчика: один для текущего окна и один для предыдущего, и вычисляя взвешенную оценку на основе того, насколько глубоко мы вошли в текущее окно. На этом подходе построен edge rate limiting Cloudflare, сочетающий контролы на уровне PoP с централизованными счетчиками для поддержки трафика большого масштаба при постоянном потреблении памяти.
Корзина токенов хранит количество токенов и временную метку последнего пополнения для каждого клиента. При каждом запросе вы вычисляете заработанные с последней проверки токены, ограничиваете общее количество емкостью корзины и списываете один токен, если он доступен. Пополнение и списание должны происходить атомарно, иначе два одновременных запроса могут прочитать одно и то же количество токенов и оба пройти успешно, хотя должен пройти только один.
Текучее ведро бывает двух видов: полисинговый вариант, который просто отбрасывает запросы, превышающие скорость откачки, и вариант формирования трафика, который ставит их в очередь для последующей обработки. Обращайтесь к нему, когда эндпоинт находится перед базой данных или сторонним сервисом, которые не могут поглотить пики нагрузки.
Совет: Оберните логику пополнения и списания в один Lua-скрипт вместо отдельных вызовов GET и SET; последовательность чтения-записи за два круговых пути — именно то состояние гонки, которое существуют атомарные скрипты, чтобы предотвратить.
Создание лимитеров, устойчивых к работе в кластере
Лимитер частоты, который работает в одном процессе, но ломается при параллелизме, хуже, чем его отсутствие, так как создает ложное чувство защищенности.
- Храните счетчики в Redis, чтобы каждый экземпляр приложения считывал и записывал одно и то же состояние, вместо того чтобы расходиться.
- Используйте Lua-скрипты Redis EVAL для объединения шагов чтения, пополнения и потребления в одну атомарную операцию, как рекомендует руководство Redis по ограничению скорости, поскольку MULTI/EXEC и оптимистичная блокировка оставляют пробелы при высокой конкуренции.
- Для сервисов с высокой нагрузкой агрегируйте счетчики локально на уровне экземпляра или точки присутствия перед синхронизацией с центральным хранилищем, вместо обращения к Redis при каждом отдельном запросе.
- Добавьте шлюзовой уровень «дырявого ведра» (leaky bucket), например в NGINX, в качестве внешней стены против злоупотребительского трафика, а более тонкие лимиты на уровне ключей оставьте на уровне приложения для легитимных, но бурных клиентов.
- Решите вопрос fail-open против fail-closed для каждого эндпоинта до того, как инцидент заставит вас сделать это: используйте fail-open для эндпоинтов с преобладанием чтения, чтобы сбой Redis не отключал весь API, и fail-closed для эндпоинтов аутентификации или платежей, где пропуск неограниченного трафика — больший риск.
Совет: Логируйте каждое событие fail-open отдельно от обычного трафика; сбой Redis, который молча отключает ваши лимиты скорости — именно тот тип сбоя, который проявляется только при разборе инцидента.
Выбор правильного алгоритма для вашего эндпоинта
Пройдитесь по четырём осям перед написанием кода: сколько памяти вы можете потратить на клиента, являются ли всплески легитимными или угрозой, насколько хрупка нижестоящая система и допустимы ли точные счетчики или достаточно оценок.
- Публичные API для разработчиков: скользящее окно с счетчиком (sliding window counter) или корзина токенов (token bucket), так как оба допускают разумные всплески без накладных расходов на точный подсчет.
- Эндпоинты платежей и аутентификации: журнал скользящего окна (sliding window log) для точности или настройка fail-closed по умолчанию, которая склоняется к отклонению запросов, а не к пропуску подозрительного трафика.
- Потоки, ограниченные нижестоящими системами: «дырявое ведро» (leaky bucket), чтобы выходная скорость никогда не превышала того, что может выдержать хрупкая система за ней.
- Внутренний трафик с высокой нагрузкой и низким риском: фиксированное окно (fixed window), жертвуя граничными всплесками ради простейшей реализации.
Если вы не можете ответить, что произойдет, когда Redis станет недоступен, вы не завершили проектирование, независимо от выбранного алгоритма.
Информирование клиентов о лимитах через заголовки
Серверы должны сообщать клиентам их статус до того, как те начнут гадать. Новый черновик IETF определяет заголовки RateLimit и RateLimit-Policy, где RateLimit-Limit и RateLimit-Reset помечены как обязательные, а RateLimit-Remaining — как рекомендуемый, но необязательный. Многие крупные провайдеры всё еще используют старые вендорные заголовки X-RateLimit-*, предшествующие черновику, поэтому поддержка обоих вариантов в переходный период разумна.
- Возвращайте RateLimit-Limit, RateLimit-Remaining и RateLimit-Reset в каждом ответе, а не только при отклонении.
- Отправляйте заголовок Retry-After при каждом отклонении запроса со статусом 429.
- Обращайтесь с этими заголовками как с подсказками, а не гарантиями, так как нагрузка может сместиться между моментом выдачи заголовка и следующим запросом.
- На стороне клиента используйте экспоненциальный бэкофф с полным джиттером (full jitter), а не фиксированную задержку, что размазывает повторные попытки вместо создания синхронизированных штормов ретраев.
Производственная ссылка сообщает, что скользящее оконное счетчик Cloudflare работает с экстремально низкой частотой ошибок при очень больших объемах запросов, что доказывает, что подход на основе оценок удерживается в масштабе без жертвования существенной точностью.
Настройка скоростей пополнения, емкости и оповещений
Получите p95 и p99 запросов в секунду на клиента из вашего существующего конвейера метрик, затем установите скорость пополнения немного выше устойчивого p99, чтобы нормальное использование никогда не срабатывало на лимите. Размер корзины выбирайте так, чтобы она поглощала примерно 5–10 секунд ожидаемого пикового p99, что покроет повторную попытку клиента выполнить пакетную задачу, не наказывая всех остальных.
- Следите за частотой 429 и отношением троттлинга как долей от общего числа запросов, а не просто абсолютными значениями.
- Отслеживайте задержку запросов отдельно для троттлируемого и не троттлируемого трафика, поскольку скачок в первом часто предшествует скачку во втором.
- Настройте оповещения на сбои или таймауты команд Redis, связанные с лимитером, так как тихий сбой там делает всю систему бесполезной.
- Внедряйте изменения лимитов сначала на небольшой процент трафика, затем расширяйте, когда частота 429 и задержка оба выглядят стабильными.
Совет: Когда вы ужесточаете лимит, анонсируйте изменение и новые заголовки потребителям API до развертывания. Неожиданный 429 без контекста генерирует больше тикетов в поддержку, чем злоупотреблений, которых он должен был предотвратить.
Где эти паттерны встречаются в продакшене
Большинство продакшн-стеков разделяют принуждение на два слоя, а не полагаются на один.
- Edge: конфигурация NGINX с leaky-bucket поглощает злоупотребляющий трафик и явный скрейпинг до того, как он достигнет серверов приложений.
- Приложение: Lua-скрипт в Redis, реализующий token bucket или sliding window counter, принуждает лимиты на ключ, обычно работая в режиме fail-open на эндпоинтах только для чтения, чтобы сбой кэша не уронил API.
- Публичные API для разработчиков: sliding window counter или token bucket, настроенные под тарифный план клиента.
- Эндпоинты аутентификации: строгие лимиты с низкой емкостью, часто fail-closed, так как пропуск избыточного трафика здесь — это риск безопасности, а не неудобство.
- Обработчики вебхуков: leaky bucket или лимитер на основе очереди, так как повторные попытки от отправляющего сервиса нужно сглаживать, а не отклонять полностью.
Справедливость, предотвращение злоупотреблений и этика троттлинга
Ограничение частоты — это механизм справедливости не меньше, чем технический: оно решает, какие запросы обслуживать, когда спрос превышает пропускную способность, и это решение влияет на реальных пользователей и реальный бизнес. Слишком агрессивный лимит может заблокировать легитимных клиентов во время пика трафика, а слишком слабый позволяет малому числу злоупотребляющих клиентов деградировать сервис для всех остальных.
Публикуйте свои лимиты и обоснование в документации API, так как недокументированный троттлинг выглядит произвольно и подрывает доверие разработчиков, строящих на вашем API. Применяйте лимиты последовательно к похожим клиентам, а не молча предпочитая некоторые аккаунты, и явно указывайте в условиях обслуживания, что считается злоупотребляющим трафиком, таким как credential stuffing или скрейпинг, в отличие от нормального высокообъемного использования платящего клиента.
Когда вы ограничиваете клиента, сам ответ имеет значение как с этической, так и с технической точки зрения. Код 429 с понятными заголовками и значением Retry-After уважает время клиента и позволяет его системе восстановиться корректно, тогда как молча отброшенный запрос или неопределенная ошибка заставляют их гадать. Для мультитенантных платформ изолируйте лимиты для каждого арендатора, чтобы скачок трафика одного клиента, будь то легитимный или вредоносный, не мог исчерпать квоту другого арендатора, использующего ту же инфраструктуру. Такая изоляция часто становится разграничительной линией между незначительным инцидентом и нарушением доверия платящих клиентов, которые ничего не сделали неправильного.

Почему разумные значения по умолчанию спасают вас от самих себя в будущем
Выбранный алгоритм имеет меньшее значение, чем сам факт его выбора и честный мониторинг. Счетчик скользящего окна и корзина токенов покрывают большинство случаев с минимальными операционными издержками, но наивные повторные попытки клиентов без отката (backoff) или учета заголовков все равно приводят к сбоям. Держите продуктовые, SDK- и инфраструктурные команды в курсе лимитов до того, как клиенты узнают об этом самым сложным путем.
— Bitblade
Где почитать больше о стандартах ограничения частоты запросов
Начните с черновика IETF RateLimit header для формирующегося стандарта, затем ознакомьтесь с руководством Redis по реализации ограничителя частоты запросов для получения кода.
Источники
- Build 5 Rate Limiters with Redis: Fixed Window, Sliding Window, Token Bucket, and Leaky Bucket
- draft-ietf-httpapi-ratelimit-headers-05
- API Rate Limiting Strategies: 2026 Engineering Reference
- Counting things: a lot of different things
Рекомендуем
Часто задаваемые вопросы
Наиболее эффективные стратегии сочетают алгоритм, подходящий под паттерн трафика, например, счетчик скользящего окна для общих API или корзину токенов для клиентов с пиковыми нагрузками, с атомарным применением, чтобы одновременные запросы не могли обойти лимит. Сочетание пограничных (edge-level) контролов с лимитами на уровне приложения для каждого ключа, как показывает подход Cloudflare, добавляет второй слой защиты.
Храните счетчики или состояние токенов в общем хранилище, таком как Redis, и оберните шаги чтения, пополнения и потребления в один атомарный Lua-скрипт, чтобы избежать состояний гонки, этот паттерн подробно описан в руководстве Redis по ограничителю частоты. Возвращайте понятные заголовки RateLimit в каждом ответе и заголовок Retry-After при отклонении, чтобы клиенты знали, как вести себя.
Начните с оценки бюджета памяти, допуска к пиковым нагрузкам и хрупкости ваших нисходящих систем, затем подберите алгоритм под эти ограничения: счетчик скользящего окна или корзина токенов для публичных API, журнал скользящего окна или настройки fail-closed по умолчанию для чувствительных эндпоинтов. Настройте скорость пополнения на основе измеренного p99 трафика и задайте емкость корзины так, чтобы она поглощала несколько секунд ожидаемого пика.
Ограничение частоты API (rate limiting) — это практика установки лимита на количество запросов, которые клиент может выполнить за определённый период. Это защищает сервис от перегрузки и обеспечивает справедливое распределение ресурсов между клиентами. Обычно серверы сообщают лимит и оставшуюся квоту через заголовки ответа, подход, формализованный в черновике IETF RateLimit header.
Готовы собрать это сами? Получите свой API-ключ — 7-дневный пробный период, без карты — или посмотрите API блокчейна для полного справочника по эндпоинтам.