При всплеске запросов к дорогой операции публичного API какой механизм ограничит нагрузку на одного клиента?
Используйте rate limiting — ограничение частоты запросов для каждого клиента за заданный интервал. Для плавного пропуска кратковременных всплесков обычно подходит алгоритм token bucket: клиент расходует токены, а они постепенно восстанавливаются; при пустом бакете запрос отклоняется или откладывается.
Ограничение защищает систему от одного чрезмерно активного клиента, но не заменяет глобальные лимиты, контроль параллелизма и защиту от распределённой нагрузки.
Ограничение частоты появилось как способ справедливо разделять ограниченный ресурс между конкурентными потребителями. Без него один поток запросов мог занять все вычислительные, сетевые или дисковые ресурсы и сделать систему недоступной для остальных.
В прикладных API этот подход решает ту же проблему: сервер заранее задаёт допустимую скорость потребления ресурса вместо реакции на уже возникшую перегрузку.
Дорогая операция может потреблять CPU, обращаться к базе данных или запускать длительную фоновую работу. Если клиент отправляет запросы быстрее, чем система успевает их завершать, растут очереди, задержки и число ошибок у всех клиентов.
Лимит только на уровне всего API недостаточен: один активный клиент способен занять весь общий бюджет. Лимит только на IP тоже ненадёжен: несколько пользователей могут находиться за одним NAT, а один злоумышленник — распределять запросы между адресами.
В token bucket для каждого ключа ограничения хранится число токенов. Один запрос потребляет один или несколько токенов, а токены пополняются с фиксированной скоростью до установленной ёмкости. Ёмкость определяет допустимый кратковременный всплеск, а скорость пополнения — устойчивую пропускную способность клиента.
Например, бакет может восстанавливаться со скоростью 10 запросов в секунду и вмещать 20 токенов. Клиент сможет кратковременно выполнить до 20 запросов, но при длительной активности его средняя скорость будет ограничена 10 запросами в секунду.
При исчерпании лимита API обычно быстро отклоняет запрос с признаком временного ограничения. Для HTTP API часто используют статус 429 Too Many Requests; при известном времени восстановления можно передать клиенту заголовок Retry-After. Клиент должен применять задержку и повторную попытку с экспоненциальным backoff, иначе он будет постоянно поддерживать перегрузку.
Ключ ограничения выбирают по модели потребления: идентификатору пользователя, API-ключу, организации или комбинации этих признаков. IP-адрес можно использовать как дополнительный защитный слой, но не как единственный идентификатор для авторизованного доступа.
В распределённой системе состояние лимита должно быть согласовано между экземплярами API. Локальный лимитер на каждом экземпляре прост и быстр, но фактический общий лимит увеличивается пропорционально числу экземпляров. Централизованное хранилище состояния даёт более точный лимит, однако добавляет сетевые задержки, стоимость и отдельную зависимость, отказ которой нужно обработать явно.
Следует различать ограничение скорости и ограничение параллелизма. Rate limiting регулирует число запросов за период, но не гарантирует, что одновременно не выполняются тысячи дорогих операций. Для этого может понадобиться отдельный лимит на число выполняющихся запросов или очередь с ограниченной длиной.
Жёсткий лимит повышает устойчивость, но может ухудшить пользовательский опыт и снизить пропускную способность легитимных клиентов. Поэтому параметры выбирают по измерениям: стоимости операции, целевой задержке, допустимой доле отказов и объёму ресурсов, который система может стабильно обслуживать.
В API генерации отчётов один клиент начал отправлять десятки запросов в секунду, хотя каждый отчёт выполнялся несколько секунд. Вариант с отсутствием лимита быстро исчерпал пул рабочих процессов; глобальный лимит защищал сервер, но при этом блокировал всех клиентов из-за поведения одного потребителя.
Фиксированное окно было простым, но допускало скачок на границе двух соседних окон. Скользящее окно давало более точный контроль, однако требовало хранить больше данных о запросах. Ограничение только числа одновременно выполняющихся задач защищало рабочие процессы, но не препятствовало постоянной генерации новых запросов.
Выбрали token bucket на клиента с отдельным глобальным ограничением параллельных отчётов и ограниченной очередью. Такой вариант разрешил короткие всплески, ограничил устойчивую скорость каждого клиента и не позволил очереди расти бесконечно; при исчерпании ресурсов новые запросы стали предсказуемо отклоняться.
Чем token bucket отличается от leaky bucket и фиксированного окна?
Token bucket ограничивает среднюю скорость, но допускает всплеск размером с ёмкость бакета. Leaky bucket обычно выравнивает поток, обслуживая его с более фиксированной скоростью, поэтому лучше подходит для сглаживания нагрузки, но может сильнее увеличивать задержку. Фиксированное окно проще реализовать, однако на границе окон позволяет непропорционально большой кратковременный всплеск.
Что произойдёт, если лимитер станет недоступен?
Нужно заранее выбрать политику отказа. При режиме fail-open запросы пропускаются, что сохраняет доступность API, но может снять защиту от перегрузки. При fail-closed запросы отклоняются, что защищает ресурсы, но превращает отказ лимитера в отказ API; выбор зависит от критичности операции и наличия локального аварийного лимита.
Почему лимита запросов недостаточно для защиты дорогой операции?
Десять запросов могут иметь совершенно разную стоимость: один завершится быстро, другой запустит тяжёлый запрос к базе или длительную вычислительную задачу. Поэтому для критичных операций лимитируют не только количество запросов, но и вес операции, число одновременных выполнений или общий бюджет ресурсов. Иначе формально соблюдающий rate limit клиент всё равно может перегрузить систему.