Разработка программного обеспечения
Объяснение ограничения скорости API: Алгоритмы, заголовки и обработка ошибок 429
Ограничения скорости защищают API от перегрузки и злоупотреблений. Узнайте, как ведут себя основные алгоритмы, как выглядит хороший ответ 429 и как клиентам следует отступить, с помощью проведенного нами моделирования.
- Ограничение скорости ограничивает количество запросов, которые клиент может сделать за определенный период времени.
- Корзина токенов допускает короткие пакеты, сохраняя при этом среднюю скорость.
- Простое фиксированное окно может пропускать двойное ограничение на границе окна.
- Верните 429 Too Many Requests с заголовком Retry-After и заставьте клиентов отступить из-за дрожания.
Один ошибочный скрипт вызывает ваш API в тесном цикле. Без ограничений это может замедлить работу службы для любого другого пользователя или привести к увеличению счета за облако. Ограничение скорости API — это ограждение, которое удерживает одного клиента от получения большего, чем его справедливая доля.
Чтобы показать, как ведут себя общие алгоритмы, мы написали небольшие версии Python и передали им моделируемое время выполнения запросов. Результаты, приведенные ниже, получены в результате этого прогона. Они обучают моделям, а не производственному коду, а настоящие шлюзы имеют больше возможностей.
Что такое ограничение скорости?
Ограничение скорости учитывает количество запросов на каждого клиента и отклоняет или задерживает запросы, превышающие согласованный лимит, например 100 запросов в минуту. Связанная с этим идея, регулирование, часто означает замедление запросов, а не их отклонение. Квота обычно представляет собой большую норму на более длительный период, например 10 000 вызовов в месяц по тарифному плану.
Зачем это нужно API
- Стабильность: скачки трафика не могут перегрузить базы данных или нижестоящие службы.
- Справедливость: один тяжелый клиент не может морить голодом остальных.
- Безопасность: ограничения замедляют попытки грубого входа в систему и парсинг.
- Контроль затрат: дорогостоящие операции, такие как вызовы моделей ИИ, остаются в рамках бюджета.
Ограничение скорости — это основной элемент управления в топ-10 безопасности API OWASP при неограниченном потреблении ресурсов. В нашем руководстве по тестированию API с помощью Postman показано, как отправлять повторяющиеся запросы, необходимые для его проверки.
Корзина токенов
Представьте себе ведро, вмещающее до 5 жетонов. Каждый запрос принимает токен. Токены пополняются со скоростью 1 в секунду, а в ведре никогда не бывает больше 5. Если ведро пусто, запрос отклоняется.
class TokenBucket:
def __init__(self, capacity, refill_per_sec):
self.capacity = capacity
self.rate = refill_per_sec
self.tokens = capacity
self.last = 0.0
def allow(self, now):
elapsed = now - self.last
self.tokens = min(self.capacity, self.tokens + elapsed * self.rate)
self.last = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False
Мы отправили сразу 7 запросов, потом еще:
burst of 7 at t=0: [True, True, True, True, True, False, False]
t=2.0: [True, True, False]
t=10: 5 allowed of 10
Первые 5 прошли как взрыв, затем ведро опустело. Через две секунды 2 токена были пополнены, поэтому прошло еще 2 запроса. После долгой паузы корзина снова была заполнена, но ограничена значением 5. Такое периодическое, а затем стабильное поведение соответствует реальному трафику, поэтому корзины токенов часто встречаются в шлюзах API. Дырявое ведро — это близкий родственник, который обрабатывает запросы с фиксированной скоростью, сглаживая выходные данные, а не допуская всплески.
Фиксированное окно против раздвижного окна
Счетчик фиксированного окна — это самый простой подход: разрешайте 10 запросов в минуту и сбрасывайте счетчик при изменении минуты. У него есть известный дефект на границе. Мы отправили 10 запросов за 59,9 секунды и еще 10 за 60,0 секунды с ограничением 10 на 60-секундное окно:
fixed window allowed around boundary: 20 of 20
sliding log allowed around boundary: 10 of 20
Фиксированное окно позволяло обрабатывать все 20 запросов за десятую долю секунды, что вдвое превышает запланированную скорость, поскольку они попадали в два разных окна. скользящий журнал запоминает время каждого недавнего запроса и подсчитывает запросы за последние 60 секунд, поэтому правильно разрешено только 10.
Компромисс — память. В скользящем журнале хранится временная метка каждого запроса. Счетчик с раздвижным окном — популярный компромиссный вариант. Он оценивает скорость путем смешивания текущего и предыдущего счетчиков окон, используя небольшой объем памяти с хорошей точностью.
Сравнение алгоритмов
| Алгоритм | всплески | Точность | Память |
|---|---|---|---|
| Фиксированное окно | До 2х на границах | Низкий | Очень низкий |
| Скользящий журнал | Нет | Точный | Высокий |
| Счетчик раздвижного окна | Маленький | Хорошо | Низкий |
| Корзина токенов | Разрешено до вместимости | Хорошо | Низкий |
| Дырявое ведро | Сглаженный | Хорошо | Низкий |
Что ограничить
- Ключ API или идентификатор пользователя: самый справедливый вариант для аутентифицированных API.
- IP-адрес: полезен для общедоступных конечных точек, но многие пользователи могут использовать один IP-адрес, и злоумышленники могут менять их.
- Конечная точка: дорогостоящие операции, такие как поиск, экспорт или вход в систему, заслуживают более жестких ограничений.
- Уровень плана: бесплатные и платные клиенты часто получают разные лимиты.
Конечные точки входа требуют особого внимания. Ограничьте как учетную запись, так и IP-адрес, чтобы злоумышленники не могли просто распространять предположения по множеству IP-адресов или множеству учетных записей.
Ответ 429
Когда клиент превышает лимит, возвращайте HTTP 429 Too Many Requests, а не 500 или 403. Включите заголовки, которые сообщают клиенту, что произошло и когда повторить попытку. Наше моделирование создало этот набор для клиента, у которого не осталось запросов:
HTTP/1.1 429 Too Many Requests
RateLimit-Limit: 100
RateLimit-Remaining: 0
RateLimit-Reset: 30
Retry-After: 30
Retry-After — стандартный HTTP-заголовок. Заголовки RateLimit-* взяты из проекта IETF, и многие API используют свои собственные варианты X-RateLimit-*. Что бы вы ни выбрали, задокументируйте это. Дополнительную информацию о кодах состояния см. в нашей шпаргалке по кодам состояния HTTP.
Обработка лимитов в качестве клиента
Повторная попытка сразу после ошибки 429 усугубляет ситуацию. Если тысячи клиентов делают это одновременно, они поступают на сервер синхронизированными волнами. Исправление — экспоненциальная задержка с джиттером: ждите дольше после каждого сбоя и добавьте случайность.
def backoff(attempt, base=0.5, cap=30, rng=random.Random(4)):
return round(min(cap, base * 2 ** attempt) * rng.random(), 2)
[backoff(a) for a in range(6)]
# [0.12, 0.1, 0.79, 0.62, 0.53, 6.43]
Это «полный джиттер»: случайное ожидание между нулем и экспоненциально растущим потолком. Ожидания выглядят нерегулярными, и в этом вся суть. Клиенты расходятся вместо того, чтобы повторять попытки вместе. Мы задали генератор случайных чисел, чтобы можно было воспроизвести выходные данные.
Контрольный список клиента: соблюдайте Retry-After, когда он присутствует, ограничивайте количество повторов, повторяйте только те запросы, которые безопасно повторить, и следите за заголовком оставшихся запросов, чтобы вы могли замедлиться, прежде чем достигнете предела.
Распределенное ограничение скорости
Благодаря нескольким серверам API за балансировщиком нагрузки каждый сервер, рассчитывающий самостоятельно, позволяет клиенту превысить лимит, распределяя запросы между ними. Команды обычно хранят счетчики в общем хранилище, таком как Redis, с атомарными операциями или коротким скриптом, чтобы два сервера не могли одновременно получить последний токен. Такие шлюзы, как Kong, NGINX, Envoy и облачные API-шлюзы, предоставляют это в качестве конфигурации. Для этой статьи мы не запускали распределенную установку.
Ограничения скорости тестирования
- Отправьте запросы до лимита и подтвердите их успешность.
- Отправьте еще один и подтвердите код 429 с правильными заголовками.
- Подождите времени сброса и снова подтвердите успешность запросов.
- Проверьте лимиты на ключ: использование одного клиента не должно блокировать использование другого.
- Проверьте границу между окнами на предмет проблемы двойного пакета.
- Используйте инструмент загрузки, описанный в разделе нагрузочное тестирование и стресс-тестирование, чтобы проверить поведение в условиях интенсивного параллелизма.
В модульные тесты вводите текущее время, как это делает наша симуляция с now, поэтому тестам никогда не придется переходить в режим сна.
Проектирование ограничений, с которыми пользователи могут жить
Выбор чисел имеет такое же значение, как и выбор алгоритма. Начните с реального трафика: посмотрите, сколько запросов в минуту делают ваши самые активные легитимные клиенты, и установите комфортные лимиты выше этого значения. Ограничение, блокирующее нормальное использование, создает заявки в службу поддержки, а не безопасности.
- Опубликуйте ограничения в документации по API для каждой конечной точки и для каждого плана.
- Предупреждать перед блокировкой. Заголовки, показывающие оставшиеся запросы, позволяют хорошо себя замедлить работу клиентов.
- Предлагайте альтернативы интенсивному опросу, например веб-перехватчики, массовые конечные точки или страницы большего размера.
- Отслеживайте показатели 429. Внезапный рост может означать атаку, выпуск клиента с ошибками или слишком жесткие ограничения.
- Намеренно предоставляйте исключения, например повышенные ограничения для партнера, записанные в конфигурации, а не жестко закодированные.
Также решите, что произойдет с запросами, количество которых превышает лимит. Некоторые API ненадолго ставят в очередь небольшое количество дополнительных запросов вместо того, чтобы отклонять их, что сглаживает короткие всплески. Другие отдают приоритет важным конечным точкам, таким как платежи, над менее важными, такими как аналитика, чтобы важный трафик продолжал течь, когда система находится под нагрузкой.
Итоги
Хорошее ограничение скорости — это договор между API и клиентом. Сервер выбирает справедливый алгоритм, обычно сегмент токенов или скользящее окно, и четко объясняет его ограничения с помощью 429 и Retry-After. Клиент уважает эти сигналы и отступает с дрожью. Начните с щедрых ограничений на ключ API, наблюдайте за реальным использованием и увеличивайте их только там, где это необходимо.
Часто задаваемые вопросы
Что такое ограничение скорости API?
Это ограничение на количество запросов, которые клиент может сделать к API за заданное время, например 100 в минуту. Запросы, превышающие лимит, отклоняются или задерживаются.
Что означает HTTP 429?
429 Too Many Requests означает, что клиент превысил лимит скорости. Ответ часто включает заголовок Retry-After, указывающий, как долго ждать.
Каков алгоритм сегмента токенов?
Каждый запрос использует токен из корзины, которая пополняется с постоянной скоростью до максимальной. Это позволяет делать короткие очереди, сохраняя при этом среднюю скорость.
В чем разница между ограничением скорости и регулированием?
Ограничение скорости обычно отклоняет запросы, превышающие лимит. Регулирование часто вместо этого замедляет или ставит их в очередь. Эти термины часто используются как взаимозаменяемые.
Как клиенту следует обращаться с ограничениями скорости?
Соблюдайте режим повторных попыток, повторите попытку с экспоненциальной задержкой и джиттером, ограничьте количество повторных попыток и замедлите работу, когда количество оставшихся запросов заканчивается.
Продолжайте учиться
Нужна помощь с тестированием, WordPress или SEO?
Мы помогаем командам выпускать лучшее программное обеспечение и развивать свои сайты. Расскажите нам, над чем вы работаете, и мы предложим четкий следующий шаг.
Больше руководств по тестирование, искусственный интеллект и современные разработки
Понравилась эта статья? Выберите тему и сразу приступайте к нашим новейшим руководствам.
Объяснение галлюцинаций искусственного интеллекта: почему языковые модели все выдумывают и как их уменьшить
Галлюцинации ИИ объяснили: что это такое, почему языковые модели их создают, распространенные типы, как уменьшить их с помощью заземления и подсказо…
Читать статью →DevOpsОбъяснение переменных среды: файлы .env, секреты и правильно выполненная конфигурация
Переменные среды объясняются на примерах Python, которые мы запускали: их чтение и установка, почему каждое значение является строкой, файлы .env, н…
Читать статью →Разработка ПООбъяснение ограничения скорости API: алгоритмы, заголовки и обработка ошибок 429
Ограничение скорости API объясняется с помощью проведенного нами моделирования: сегмент токенов, фиксированное окно, скользящее окно, 429 ответов, з…
Читать статью →Автоматизация тестированияИздевательство в Python: unittest.mock, патч и ошибки, из-за которых тесты лгут
Издевательство на Python с помощью unittest.mock, протестировано: Mock и return_value, Side_effect, исправление использования имени, спецификация и …
Читать статью →Тестирование ПООбъяснение покрытия кода: покрытие линий или ветвей и что на самом деле означает 100%
Покрытие кода объяснено с помощью реального запуска покрытия.py: покрытие строк, ветвей и условий, чтение отчета, почему 100%-ное покрытие все еще м…
Читать статью →Базы данных и SQLDELETE, TRUNCATE и DROP в SQL: различия, откат и когда использовать каждый из них
Объяснение DELETE, TRUNCATE и DROP на примере протестированного SQL: что каждый из них удаляет, откат, триггеры, сброс идентификаторов, скорость и ч…
Читать статью →Базы данных и SQLОбъяснение представлений SQL: создавайте, используйте и избегайте ловушек, с примерами
Представления SQL, объясненные с помощью запросов, которые мы выполняли: создание представления, результаты в реальном времени, скрытие столбцов, пр…
Читать статью →Разработка ПОИнтерфейс TypeScript и тип: реальные различия, протестированные с помощью компилятора
Интерфейс TypeScript против типа, проверенный реальным компилятором: что могут делать оба, что может делать только один, слияние объявлений, расшире…
Читать статью →Разработка ПООбъяснение Python *args и **kwargs: гибкие функции с понятными примерами
Python *args и **kwargs, объясненные с помощью кода, который мы запускали: упаковка и распаковка, порядок параметров, аргументы только по ключевым с…
Читать статью →Разработка ПОЛямбда-функции Python: синтаксис, реальное использование и когда их не следует использовать
Лямбда-функции Python, объясненные с помощью кода, который мы запускали: синтаксис, сортировка с помощью ключа, карты и фильтра, замыкания, ловушка …
Читать статью →Искусственный интеллектЧто такое ИИ-агенты? Как они работают: можно запустить простой цикл агента
Что такое агенты ИИ? Простое руководство по циклу агента, инструментам, памяти и ограничениям, с небольшим циклом Python, который мы запустили, реал…
Читать статью →DevOpsМониторинг и наблюдаемость: реальная разница с журналами, метриками и трассировками
Объяснение мониторинга и наблюдаемости: известные и неизвестные сбои, журналы, метрики и трассировки, пошаговое руководство по инцидентам при оформл…
Читать статью →DevOpsDocker Volumes против Bind Mounts: правильное сохранение данных контейнера
Объяснение томов Docker и монтирования привязки: почему исчезают данные контейнера, именованные тома, монтирование привязки, tmpfs, примеры создания…
Читать статью →Автоматизация тестированияTDD против BDD: ключевые различия, одна функция, созданная для обоих способов, и когда использовать каждый
TDD и BDD объясняются с помощью одной функции скидки, созданной в обоих направлениях: красно-зеленый рефакторинг с кодом, который мы запускали, сцен…
Читать статью →Разработка ПОGit merge vs rebase: отличия, примеры и когда что использовать
Git merge vs rebase с реальным выводом log, сравнительной таблицей, золотым правилом rebase и безопасным рабочим процессом для команды.
Читать статью →Разработка ПОГенераторы списков в Python: синтаксис, примеры и когда их избегать
Генераторы списков в Python: синтаксис, фильтры, if-else, вложенные циклы, измеренное время и случаи, когда обычный цикл for лучше.
Читать статью →Базы данных и SQLSQL JOIN: объяснение с примерами (Inner, Left, Right, Full)
Разберите SQL JOIN шаг за шагом на примерах таблиц и запросов: INNER, LEFT, RIGHT, FULL, CROSS и SELF, а также частые ошибки и советы по производите…
Читать статью →