Представьте: вы написали клиент, который делает запросы к сайту, и всё работает. А потом внезапно сыплются ошибки, воркеры зависают, а сервер отвечает загадочным кодом 429. Знакомо? Тогда этот гайд для вас. Мы разберём, как построить HTTP-клиент, который не паникует при первой проблеме, а ведёт себя вежливо и устойчиво.

Введение: почему 429 - это не ошибка, а сигнал

Многие разработчики видят код 429 и думают: сломалось. На самом деле сервер говорит вам совершенно конкретную вещь: вы шлёте слишком много запросов, притормозите. Это не отказ и не блокировка навсегда. Это просьба сбавить темп. И если вы услышите её правильно, ваш клиент станет надёжным.

Что получит читатель в итоге

К концу этого руководства у вас будет готовый рабочий HTTP-клиент, который умеет несколько важных вещей. Он корректно обрабатывает код 429 и уважает заголовок Retry-After. Он использует экспоненциальный backoff с джиттером, чтобы не устраивать шторм повторов. Он ограничивает параллелизм, чтобы не топить целевой сервер. И он не зависает благодаря грамотным тайм-аутам.

Вы получите готовые фрагменты кода на трёх языках: Python (через библиотеку httpx и через urllib3 Retry), Node.js и Go. Каждый фрагмент вы сможете вставить в свой проект и адаптировать под задачу.

Для кого этот гайд

Гайд рассчитан на начинающих разработчиков, которые уже умеют делать простые HTTP-запросы, но пока не сталкивались с продакшн-нагрузкой. При этом здесь есть блоки для продвинутых: circuit breaker, метрики, управляемая деградация. Если вы пишете парсер, интеграцию с чужим API или сервис, который стучится к внешним ресурсам, этот материал сэкономит вам много бессонных ночей.

Что нужно знать заранее

Вам достаточно понимать, что такое HTTP-запрос и HTTP-ответ. Желательно знать, что такое статус-код (например, 200 - это успех, а 404 - страница не найдена). Пригодится базовое знакомство хотя бы с одним из языков: Python, JavaScript или Go. Глубоких знаний сетей не требуется - всё объясним простыми словами.

Сколько времени потребуется

Прочитать и понять теорию - около 40 минут. Собрать базовый клиент по шагам - примерно час. Полная реализация со всеми защитами, метриками и тестами - около трёх часов. Не спешите: лучше медленно понять каждый шаг, чем быстро скопировать код, который вы не понимаете.

Совет: Читайте гайд с открытым редактором кода. Сразу пробуйте примеры на тестовом endpoint, а не на живом продакшн-сервисе.

Предварительная подготовка

Прежде чем писать код, подготовим рабочее окружение. Это займёт немного времени, но избавит от путаницы дальше.

Необходимые инструменты

  • Один из языков и его среда: Python 3.11 или новее, либо Node.js 20 или новее, либо Go 1.22 или новее.
  • Редактор кода - подойдёт любой, например VS Code.
  • Терминал для запуска скриптов.
  • Доступ в интернет к тестовому HTTP-сервису, который умеет отдавать разные коды ответа.

Что установить для Python

  1. Проверьте версию Python командой в терминале: наберите python --version и нажмите Enter.
  2. Создайте виртуальное окружение командой python -m venv venv.
  3. Активируйте его: на Windows командой venv\Scripts\activate, на macOS и Linux командой source venv/bin/activate.
  4. Установите библиотеки командой pip install httpx urllib3 requests.

Что установить для Node.js

  1. Проверьте версию командой node --version.
  2. Создайте папку проекта и войдите в неё.
  3. Инициализируйте проект командой npm init -y.
  4. Начиная с Node.js 20 встроенный fetch доступен без установки, дополнительных пакетов для базового клиента не нужно.

Что установить для Go

  1. Проверьте версию командой go version.
  2. Создайте папку и инициализируйте модуль командой go mod init myclient.
  3. Стандартной библиотеки net/http достаточно, внешние пакеты не обязательны.

Резервные копии и безопасность

⚠️ Внимание: Никогда не тестируйте новый клиент сразу на важном продакшн-сервисе. Сначала используйте тестовый endpoint или локальный сервер-заглушку, который вы контролируете. Иначе агрессивные ретраи могут навредить чужому сервису и привести к вашей блокировке.

Если вы дорабатываете существующий проект, сделайте копию файла или создайте отдельную ветку в системе контроля версий. Тогда вы всегда сможете откатить изменения.

✅ Проверка: Вы установили выбранный язык, создали проект и убедились, что тестовый скрипт запускается без ошибок. Теперь можно двигаться к теории.

Базовые понятия простым языком

Чтобы уверенно строить клиент, нужно понимать несколько ключевых терминов. Разберём их без сложных слов.

Что означают коды 403, 407, 429 и 503

Эти четыре кода легко перепутать, но ведут они себя по-разному, и лечатся тоже по-разному.

  • Код 429 Too Many Requests - сервер говорит, что вы превысили лимит запросов. Это временно. Нужно притормозить и повторить позже.
  • Код 403 Forbidden - доступ запрещён. Часто это не про скорость, а про права: неверный ключ, отсутствие авторизации, ограничение по региону. Повторять запрос без изменений обычно бесполезно.
  • Код 503 Service Unavailable - сервер временно перегружен или на обслуживании. Как и 429, это временно, и повтор позже может помочь.
  • Код 407 Proxy Authentication Required - и вот здесь важный нюанс. Этот код приходит не от целевого сайта, а от прокси-сервера. Он означает, что прокси требует авторизацию, а вы её не передали или передали неправильно.

⚠️ Внимание: Код 407 нельзя лечить ротацией IP или backoff. Это ошибка настройки вашего клиента, а именно неправильные учётные данные для прокси. Проверьте логин, пароль и формат строки подключения. Никакие повторы здесь не помогут, пока вы не исправите авторизацию.

Разница между 429 и 403

Запомните простое правило. 429 - это про количество: вы делаете слишком часто. 403 - это про право: вам вообще нельзя. При 429 повтор после паузы решает проблему. При 403 повтор без изменения условий её не решит - нужно менять ключ, заголовки или подход.

Заголовки Retry-After и X-RateLimit

Вежливые серверы подсказывают, когда можно возвращаться. Заголовок Retry-After говорит, через сколько секунд стоит повторить запрос. Иногда там число секунд, иногда конкретная дата. Ваш клиент обязан уважать этот заголовок: если сервер сказал ждать 10 секунд, повтор через 1 секунду только ухудшит ситуацию.

Группа заголовков X-RateLimit сообщает лимиты: сколько запросов вам разрешено, сколько осталось и когда счётчик обнулится. Например, X-RateLimit-Remaining показывает остаток. Если он близок к нулю, стоит заранее сбавить темп, не дожидаясь 429.

Как устроены лимиты: token bucket и скользящее окно

Серверы считают ваши запросы двумя популярными способами.

Token bucket (ведро с токенами) работает так. Представьте ведро, в которое постоянно капают токены с фиксированной скоростью. Каждый запрос забирает один токен. Если токенов нет - запрос отклоняется с кодом 429. Такая схема допускает короткие всплески: если вы долго молчали, ведро наполнилось, и можно сделать пачку запросов сразу.

Скользящее окно (sliding window) считает количество запросов за последний промежуток, например за минуту. Как только вы превысили лимит в этом окне - получаете 429. Здесь всплески наказываются жёстче.

Почему параллелизм - это тоже лимит

Многие забывают: лимит бывает не только на частоту, но и на число одновременных соединений. Если вы открываете 500 параллельных запросов, сервер может воспринять это как атаку, даже если общее число за минуту невелико. Параллелизм нужно ограничивать так же строго, как и частоту.

Совет: Прежде чем строить клиент, узнайте лимиты целевого сервиса из его документации. Знание точных цифр избавит вас от догадок и лишних 429.

✅ Проверка: Вы понимаете разницу между 429, 403, 407 и 503, знаете про Retry-After и представляете, как сервер считает ваши запросы. Отлично, переходим к практике.

Шаг 1: настраиваем тайм-ауты правильно

Цель этапа: сделать так, чтобы ни один запрос не мог зависнуть навсегда и заблокировать воркер.

Почему клиент без тайм-аута опасен

Клиент без тайм-аута - это бомба замедленного действия. Если сервер перестал отвечать, ваш запрос будет ждать бесконечно. Один зависший запрос держит один воркер. Десять зависших запросов - и весь ваш пул воркеров занят, новые задачи не обрабатываются, сервис фактически стоит. Тайм-аут - это ваша первая линия обороны.

Четыре вида тайм-аутов

Правильный клиент различает несколько таймаутов, а не ставит один общий на всё.

  1. Connect timeout (подключение) - сколько ждать установления соединения с сервером. Если сервер недоступен, вы узнаете об этом быстро.
  2. Read timeout (чтение) - сколько ждать данных после отправки запроса. Защищает от сервера, который принял запрос, но молчит.
  3. Write timeout (запись) - сколько ждать отправки тела запроса. Актуально для больших загрузок.
  4. Общий timeout (total) - максимальное время на весь запрос целиком, включая все фазы.

Какие значения брать за старт

Универсальных цифр нет, но есть разумные стартовые значения. Для connect берите 3-5 секунд: соединение обычно устанавливается быстро. Для read берите 10-30 секунд в зависимости от того, насколько быстро сервис отдаёт данные. Общий тайм-аут ставьте так, чтобы он покрывал самый долгий разумный запрос, например 30-60 секунд.

⚠️ Внимание: Никогда не ставьте огромные тайм-ауты вроде 300 секунд на все запросы. Это маскирует проблемы и создаёт очередь зависших операций. Лучше быстро упасть и повторить, чем долго ждать впустую.

Пошаговая настройка

  1. Определите, сколько обычно длится успешный запрос к вашему сервису. Замерьте несколько раз.
  2. Установите read timeout примерно вдвое больше среднего времени ответа.
  3. Установите connect timeout в 3-5 секунд.
  4. Установите общий timeout как сумму разумных фаз плюс небольшой запас.
  5. Запустите тестовый запрос и убедитесь, что он завершается, а не висит.

Совет: Если ваш сервис иногда отдаёт большие файлы, а иногда мелкие ответы, сделайте разные профили тайм-аутов для разных типов запросов. Один размер не подходит всем.

Ожидаемый результат: при обращении к заведомо медленному или недоступному адресу ваш клиент завершает попытку через заданное время с понятной ошибкой тайм-аута, а не висит вечно.

✅ Проверка: Направьте запрос на адрес, который не отвечает (например, несуществующий порт). Клиент должен вернуть ошибку тайм-аута примерно в заданное время. Если он висит дольше - тайм-аут настроен неверно.

Шаг 2: строим ретраи с экспоненциальным backoff и джиттером

Цель этапа: научить клиент повторять запросы умно, без вреда для себя и сервера.

Что вообще можно повторять: идемпотентность

Прежде чем повторять запрос, спросите себя: безопасно ли выполнить его дважды? Это свойство называется идемпотентностью. Запрос идемпотентен, если повторное выполнение даёт тот же результат и не вызывает побочных эффектов.

  • GET, HEAD, PUT, DELETE обычно идемпотентны. Повторить их безопасно.
  • POST обычно не идемпотентен. Повтор может создать дубликат заказа, второй платёж, дублирующую запись.

⚠️ Внимание: Никогда не повторяйте POST-запросы вслепую. Повторная отправка неидемпотентного запроса может привести к двойному списанию денег или дублированию данных. Если нужен повтор POST, используйте ключ идемпотентности (Idempotency-Key), который сервер поймёт и не выполнит операцию дважды.

Сколько раз повторять

Бесконечные повторы - зло. Разумный предел - от 3 до 5 попыток. Если после пяти попыток запрос не прошёл, значит проблема серьёзнее, чем временный сбой, и её нужно логировать и обрабатывать отдельно.

Что такое экспоненциальный backoff

Backoff - это пауза между повторами. Экспоненциальный означает, что пауза растёт кратно с каждой попыткой. Например: первая пауза 1 секунда, вторая 2 секунды, третья 4, четвёртая 8. Формула проста: базовая задержка умножается на два в степени номера попытки.

Почему именно так? Если сервер перегружен, короткие частые повторы только добьют его. Растущие паузы дают серверу время прийти в себя.

Почему без джиттера получается шторм повторов

Представьте, что тысяча клиентов одновременно получили 429. Все они ждут ровно 1 секунду, потом ровно 2, потом ровно 4. И все повторяют в один и тот же момент. Получается синхронный шторм: сервер снова получает тысячу запросов разом и снова отдаёт 429. Проблема не решается, а зацикливается.

Решение - джиттер, то есть случайная добавка к паузе. Вместо ровно 2 секунд один клиент ждёт 1.7, другой 2.3, третий 1.9. Повторы размазываются во времени, и сервер разгружается плавно.

Как уважать Retry-After

Если сервер прислал заголовок Retry-After, он важнее вашей формулы backoff. Правило простое: берите максимум из вашей вычисленной паузы и значения Retry-After. Никогда не повторяйте раньше, чем просил сервер. Это грубое нарушение вежливости, которое приведёт к новым 429.

Пошаговая реализация логики ретраев

  1. Проверьте, идемпотентен ли запрос. Если нет и нет ключа идемпотентности - не повторяйте.
  2. Проверьте код ответа. Повторяйте только на 429, 503 и сетевых ошибках (тайм-аут, обрыв соединения).
  3. Увеличьте счётчик попыток. Если он превысил лимит - прекратите и верните ошибку.
  4. Вычислите базовую паузу по формуле экспоненциального роста.
  5. Добавьте случайный джиттер к паузе.
  6. Если пришёл Retry-After, возьмите большее из двух значений.
  7. Подождите вычисленное время и повторите запрос.

Совет: Ограничивайте максимальную паузу сверху, например 30 или 60 секундами. Иначе на пятой попытке backoff может вырасти до неприлично больших значений, и пользователь будет ждать слишком долго.

Ожидаемый результат: при коде 429 клиент делает паузу, повторяет запрос, и паузы между повторами растут и слегка различаются от раза к разу.

✅ Проверка: Настройте тестовый сервер отдавать 429 несколько раз подряд, а затем 200. Ваш клиент должен успешно получить финальный ответ, а в логах вы увидите растущие паузы с разбросом.

Шаг 3: ограничиваем параллелизм

Цель этапа: не дать клиенту завалить сервер лавиной одновременных запросов.

Что такое семафор простыми словами

Семафор - это счётчик разрешений. Представьте гардероб с ограниченным числом крючков. Пока есть свободный крючок, вы вешаете пальто. Если все заняты - ждёте, пока кто-то освободит. Семафор пропускает ограниченное число задач одновременно, а остальные держит в очереди.

Очередь задач

Все запросы, которые нужно выполнить, складываются в очередь. Воркеры разбирают задачи из очереди по мере освобождения. Это даёт вам полный контроль над темпом: сколько воркеров - столько параллельных запросов максимум.

Лимит на хост

Важный нюанс: лимит стоит держать для каждого хоста отдельно. Если вы работаете с несколькими сервисами, глобальный лимит на всё сразу неоптимален. Один медленный хост не должен блокировать запросы к другому. Ставьте персональный лимит на каждый домен.

Пул соединений и keep-alive

Каждое новое TCP-соединение стоит времени: рукопожатие, установка защищённого канала. Keep-alive позволяет переиспользовать соединение для нескольких запросов подряд. Это экономит время и ресурсы сервера. Пул соединений хранит открытые соединения наготове. Настройте размер пула согласованно с вашим лимитом параллелизма.

⚠️ Внимание: Не путайте размер пула соединений и лимит параллелизма. Пул может быть чуть больше лимита для запаса, но если пул огромный, а лимит маленький - вы держите лишние открытые соединения зря. Держите их в разумном балансе.

Пошаговая настройка ограничения

  1. Определите безопасное число одновременных запросов на хост. Начните с малого, например 5-10.
  2. Создайте семафор с этим числом разрешений.
  3. Перед каждым запросом запрашивайте разрешение у семафора.
  4. После завершения запроса, успешного или нет, обязательно освобождайте разрешение.
  5. Настройте пул соединений с keep-alive под тот же порядок значений.
  6. Постепенно повышайте лимит, наблюдая за долей 429. Как только она растёт - остановитесь.

Совет: Освобождайте разрешение семафора в блоке finally или его аналоге. Иначе при ошибке разрешение не вернётся, счётчик утечёт, и со временем клиент встанет намертво.

Ожидаемый результат: сколько бы задач вы ни поставили в очередь, число одновременных запросов к хосту не превышает заданный лимит.

✅ Проверка: Поставьте в очередь 100 задач с лимитом 5. В логах или мониторе соединений вы должны видеть не более 5 активных запросов в любой момент времени.

Шаг 4: реагируем именно на код 429

Цель этапа: выстроить правильную реакцию на сигнал перегрузки и понять, когда уместна смена IP.

Три действия при 429

Когда приходит 429, у вас есть три инструмента, и применять их нужно в связке.

  1. Притормозить - снизить общий темп запросов, а не только сделать паузу для одного запроса. Это ключевое: 429 - сигнал, что весь ваш темп высоковат.
  2. Сменить IP - если вы работаете через ротацию IP-адресов, смена адреса может помочь, когда лимит привязан к конкретному адресу. Но это не панацея.
  3. Отложить задачу - вернуть запрос в очередь с задержкой, чтобы выполнить его позже, когда лимиты восстановятся.

⚠️ Внимание: Смена IP не отменяет вежливости. Если лимит стоит не на IP, а на аккаунт или ключ, то никакая ротация не поможет - вы всё равно упрётесь в 429. Не превращайте ротацию в способ обойти правила: уважайте лимиты сервиса и Retry-After в любом случае.

Матрица действий по кодам ответа

Держите под рукой простую таблицу решений. Вот что делать при каждом коде.

  • 200-299 Успех - обработать ответ, освободить ресурсы, взять следующую задачу.
  • 429 Too Many Requests - притормозить темп, уважать Retry-After, повторить с backoff, при необходимости отложить задачу или сменить IP.
  • 503 Service Unavailable - повторить с backoff, уважать Retry-After, но не менять IP: проблема на стороне сервера.
  • 403 Forbidden - не повторять вслепую. Проверить авторизацию, заголовки, права. Логировать для разбора.
  • 407 Proxy Authentication Required - исправить учётные данные прокси. Не повторять и не ротировать до исправления настроек.
  • 400, 404, 422 клиентские ошибки - не повторять. Это ошибка вашего запроса, повтор ничего не изменит.
  • 500, 502, 504 серверные ошибки - осторожно повторить с backoff небольшое число раз.
  • Сетевые ошибки и тайм-ауты - повторить с backoff, если запрос идемпотентен.

Пошаговая реализация реакции на 429

  1. Получив 429, немедленно прекратите наращивать темп.
  2. Прочитайте заголовок Retry-After, если он есть.
  3. Вычислите паузу как максимум из backoff и Retry-After.
  4. Если лимит вероятно привязан к IP и у вас есть ротация - смените адрес перед повтором.
  5. Если попытки исчерпаны - отложите задачу обратно в очередь с большой задержкой.
  6. Снизьте общий лимит параллелизма на время, чтобы дать серверу передышку.

Совет: Ведите отдельный счётчик доли 429 за последнюю минуту. Если она растёт, автоматически снижайте темп ещё до того, как ситуация станет критической. Это называется адаптивным ограничением.

Ожидаемый результат: при серии 429 клиент плавно снижает темп, уважает Retry-After и в итоге успешно завершает запросы, не устраивая шторм.

✅ Проверка: Симулируйте всплеск 429 на тестовом сервере. Клиент должен снизить активность, а не наращивать повторы. Доля успешных ответов после паузы должна восстановиться.

Шаг 5: добавляем circuit breaker и управляемую деградацию

Цель этапа: дать клиенту предохранитель, который защищает и вас, и сервер при затяжных проблемах.

Что такое circuit breaker

Circuit breaker - это предохранитель, как в электрощитке. Если ошибки идут потоком, он размыкает цепь: перестаёт пропускать запросы к проблемному сервису на какое-то время. Это защищает сервер от добивания и ваш клиент от бессмысленной траты ресурсов.

Три состояния предохранителя

  • Closed (замкнут) - нормальная работа, запросы проходят. Клиент считает ошибки.
  • Open (разомкнут) - слишком много ошибок, запросы блокируются сразу без похода на сервер. Держится заданное время.
  • Half-open (полуоткрыт) - пробный режим. Клиент пропускает несколько запросов, чтобы проверить, восстановился ли сервис. Если да - возвращается в closed, если нет - снова open.

Управляемая деградация вместо полной остановки

Когда сервис недоступен, не обязательно всё ронять. Управляемая деградация - это способность работать хуже, но продолжать работать. Примеры: отдать данные из кэша вместо свежих, показать урезанный результат, отложить необязательные задачи, вернуть понятную заглушку вместо ошибки.

Совет: Всегда думайте, что показать пользователю или системе, когда внешний сервис лежит. Заглушка с осмысленным сообщением лучше, чем зависание или стектрейс.

Пошаговая настройка circuit breaker

  1. Задайте порог ошибок, при котором предохранитель размыкается, например 50 процентов неудач за окно из 20 запросов.
  2. Задайте время, на которое цепь размыкается, например 30 секунд.
  3. Считайте успехи и неудачи в скользящем окне.
  4. При превышении порога переведите предохранитель в состояние open.
  5. По истечении времени переведите его в half-open и пропустите несколько пробных запросов.
  6. По результату проб верните в closed или снова в open.

⚠️ Внимание: Не путайте circuit breaker с ретраями. Ретраи повторяют один запрос, а предохранитель управляет всем потоком к сервису. Вместе они мощны, но настраивать их надо согласованно, чтобы предохранитель не открывался слишком рано из-за нормальных единичных сбоев.

Ожидаемый результат: при затяжной недоступности сервиса клиент перестаёт долбить его запросами, быстро отдаёт заглушку и периодически проверяет восстановление.

✅ Проверка: Сделайте тестовый сервер недоступным. Клиент должен после серии неудач перестать слать запросы (open), а после восстановления сервера сам вернуться к нормальной работе через half-open.

Проверка результата: какие метрики считать

Устойчивость нельзя оценить на глаз. Нужны цифры. Вот ключевые метрики, которые покажут, стал ли клиент надёжнее.

Основные показатели

  • Доля успешных ответов (success rate) - процент запросов, завершившихся кодом 2xx. Чем выше, тем лучше. Стремитесь к стабильно высокому значению даже под нагрузкой.
  • p95 задержки - время, в которое укладываются 95 процентов запросов. Этот показатель честнее среднего, потому что показывает, как чувствует себя большинство, а не только удачливые запросы.
  • Доля 429 - процент ответов с кодом 429. Если он высокий, вы шлёте слишком агрессивно. Цель - свести его к минимуму.
  • Число повторов на запрос - показывает, насколько тяжело даётся успех. Рост говорит о проблемах.
  • Число открытий circuit breaker - частые открытия сигнализируют о нестабильности сервиса или слишком агрессивных настройках.

Чек-лист готовности

  1. Тайм-ауты настроены на все фазы, ни один запрос не висит вечно.
  2. Ретраи работают только для идемпотентных запросов и безопасных кодов.
  3. Backoff растёт экспоненциально и содержит джиттер.
  4. Retry-After уважается всегда.
  5. Параллелизм ограничен семафором на каждый хост.
  6. Пул соединений с keep-alive настроен согласованно с лимитом.
  7. Реакция на 429 снижает темп, а не наращивает повторы.
  8. Матрица действий по кодам реализована.
  9. Circuit breaker защищает от затяжных сбоев.
  10. Метрики собираются и доступны для анализа.

Как понять, что клиент стал устойчивее

Сравните метрики до и после доработок под одинаковой нагрузкой. Устойчивый клиент показывает высокую долю успеха, низкую долю 429, стабильный p95 и отсутствие зависших воркеров. Даже когда сервер капризничает, ваш сервис продолжает работать без каскадных падений.

✅ Проверка: Проведите нагрузочный тест на тестовом endpoint. Если под нагрузкой доля успеха держится высокой, а зависаний нет - поздравляю, клиент устойчив.

Типичные ошибки и их решения

Разберём частые грабли, на которые наступают почти все.

Ошибка 1: ретраи усиливают нагрузку

Проблема: сервер перегружен, а ваши агрессивные повторы добивают его окончательно. Причина: повторы без backoff и без снижения темпа. Решение: добавьте экспоненциальный backoff с джиттером, ограничьте число попыток, снижайте общий параллелизм при росте ошибок.

Ошибка 2: повтор неидемпотентных запросов

Проблема: двойные заказы, повторные списания, дубликаты записей. Причина: слепой повтор POST-запросов. Решение: повторяйте только идемпотентные методы. Для POST используйте ключ идемпотентности, который сервер распознает и не выполнит операцию дважды.

Ошибка 3: лечение 429 бесконечной сменой IP

Проблема: вы меняете IP снова и снова, а 429 не исчезает. Причина: лимит привязан не к IP, а к ключу или аккаунту, либо вы просто шлёте слишком много суммарно. Решение: снижайте темп и уважайте Retry-After. Ротация IP - лишь один из инструментов, а не замена вежливости.

Ошибка 4: синхронный шторм повторов

Проблема: все клиенты повторяют в одинаковые моменты, сервер снова падает. Причина: backoff без джиттера. Решение: добавьте случайную составляющую к каждой паузе.

Ошибка 5: зависшие воркеры

Проблема: сервис постепенно перестаёт обрабатывать задачи. Причина: отсутствие тайм-аутов, запросы висят вечно. Решение: настройте connect, read и общий тайм-ауты на все запросы.

Ошибка 6: утечка разрешений семафора

Проблема: со временем клиент перестаёт делать запросы. Причина: разрешение семафора не освобождается при ошибке. Решение: освобождайте разрешение в блоке finally, чтобы это происходило всегда.

Ошибка 7: неверная реакция на 407

Проблема: клиент бесконечно повторяет и ротирует IP, но получает 407. Причина: код 407 приходит от прокси и означает ошибку авторизации прокси, а не проблему сервиса. Решение: проверьте и исправьте учётные данные прокси. Повторы здесь бесполезны.

Готовые фрагменты кода

Ниже описания подходов на трёх стеках. Адаптируйте под свой проект.

Python на httpx

Создайте клиент httpx с явными тайм-аутами через объект Timeout, где отдельно заданы connect и read. Задайте пределы пула через httpx Limits, указав максимум соединений на хост. Оберните вызов в цикл повторов: на 429 и 503 читайте Retry-After, вычисляйте паузу как максимум из экспоненциального backoff с джиттером и значения Retry-After, затем делайте паузу через asyncio sleep. Ограничьте параллелизм через asyncio Semaphore, освобождая его в блоке finally. Повторяйте только идемпотентные методы, ограничьте число попыток пятью.

Python на urllib3 Retry

Библиотека urllib3 предлагает готовый механизм. Создайте объект Retry с параметрами: total задаёт число попыток, backoff_factor включает экспоненциальные паузы, status_forcelist перечисляет коды для повтора, например 429, 500, 502, 503, 504. Параметр respect_retry_after_header включает уважение Retry-After. Передайте этот Retry в PoolManager или в адаптер requests через HTTPAdapter. Это самый быстрый способ получить базовую устойчивость без написания цикла вручную.

Node.js

Используйте встроенный fetch с AbortController для тайм-аута: создайте контроллер, поставьте setTimeout на abort, передайте signal в fetch. Оберните вызов в функцию с циклом повторов. Проверяйте response.status: на 429 и 503 читайте заголовок Retry-After через response.headers.get, вычисляйте паузу с джиттером, ждите через промис с setTimeout. Для ограничения параллелизма используйте простой семафор на промисах или популярную библиотеку-ограничитель. Держите число одновременных промисов под контролем через очередь.

Go

В Go настройте http Client с полем Timeout для общего тайм-аута и настройте Transport с параметрами MaxIdleConnsPerHost и IdleConnTimeout для пула и keep-alive. Для connect-тайм-аута используйте DialContext с net Dialer. Реализуйте цикл повторов: на 429 и 503 читайте заголовок Retry-After, вычисляйте паузу через time Duration с экспоненциальным ростом и случайным джиттером, ждите через time Sleep или select с context. Ограничьте параллелизм буферизированным каналом как семафором: пишите в канал перед запросом, читайте из него в defer после.

Совет: На любом языке выносите настройки (тайм-ауты, число попыток, лимит параллелизма) в конфигурацию, а не хардкодьте. Так вы подстроите поведение под каждый сервис без переписывания кода.

Дополнительные возможности и оптимизация

Когда базовый клиент работает, можно сделать его ещё умнее.

Адаптивное ограничение темпа

Вместо фиксированного лимита сделайте его плавающим. Читайте заголовки X-RateLimit-Remaining и заранее снижайте темп, когда остаток мал. Так вы избегаете 429 ещё до их появления.

Приоритеты задач

Не все запросы равны. Сделайте очередь с приоритетами: важные задачи выполняются раньше, необязательные откладываются первыми при деградации.

Кэширование

Для идемпотентных GET-запросов добавьте кэш с коротким временем жизни. Это снижает нагрузку на сервер и вашу долю 429 без всяких хитростей.

Наблюдаемость

Подключите структурированные логи и метрики. Логируйте каждый повтор, каждое открытие circuit breaker, каждую долгую паузу. Так вы быстро найдёте узкое место при разборе инцидентов.

Совет: Начните с простого клиента и добавляйте продвинутые фичи по мере реальной потребности. Преждевременная сложность так же вредна, как и её отсутствие.

FAQ: частые вопросы

Нужно ли всегда уважать Retry-After, даже если он большой?

Да. Если Retry-After слишком велик для вашего сценария, лучше отложить задачу или вернуть деградированный ответ, чем повторять раньше срока. Игнорирование Retry-After почти всегда приводит к новым 429.

Можно ли повторять POST-запросы?

Только с осторожностью. Если операция не идемпотентна, повтор может создать дубликат. Используйте ключ идемпотентности, чтобы сервер сам защитил вас от двойного выполнения.

Какое число одновременных запросов брать за старт?

Начните с малого, например 5-10 на хост, и повышайте, наблюдая за долей 429 и p95. Как только 429 растёт - вы нашли потолок.

Чем 429 отличается от 503 на практике?

429 - это про ваш темп: вы шлёте слишком часто. 503 - про сервер: он сам перегружен или на обслуживании. При 429 полезно снизить темп и, возможно, сменить IP. При 503 менять IP смысла нет, просто повторите позже.

Почему мой клиент иногда получает 407?

Код 407 приходит от прокси и означает, что не прошла авторизация на прокси. Проверьте логин и пароль прокси. Ротация IP и backoff здесь не помогут - это ошибка настройки.

Сколько попыток повтора считается нормой?

Обычно от трёх до пяти. Больше редко имеет смысл: если не помогло за пять попыток, проблема серьёзнее временного сбоя.

Зачем нужен джиттер, если backoff и так растёт?

Без джиттера множество клиентов повторяют в одинаковые моменты и создают синхронный шторм. Случайный разброс размазывает повторы во времени и разгружает сервер плавно.

Когда открывать circuit breaker?

Когда доля ошибок в скользящем окне превышает заданный порог, например половину запросов. Это защищает и сервер, и вас от бессмысленной траты ресурсов.

Помогает ли смена IP от 429?

Иногда, если лимит привязан к IP. Но если лимит на ключ или аккаунт, смена IP бесполезна. Смена IP не заменяет снижение темпа и уважение Retry-After.

Что показать пользователю, когда сервис лежит?

Понятную заглушку, данные из кэша или урезанный результат. Это лучше, чем зависание или техническая ошибка на экране.

Заключение

Вы прошли большой путь. Давайте вспомним, что вы построили. Вы настроили тайм-ауты на все фазы, чтобы ни один запрос не завис навсегда. Вы добавили умные ретраи с экспоненциальным backoff и джиттером, которые повторяют только безопасные запросы и уважают Retry-After. Вы ограничили параллелизм семафором и настроили пул соединений с keep-alive. Вы выстроили правильную реакцию на 429 и составили матрицу действий по кодам ответа. Наконец, вы добавили circuit breaker и управляемую деградацию.

Главная мысль всего гайда проста. 429 - это не ошибка, а разговор. Сервер говорит вам сбавить темп, и вежливый клиент слушает. Устойчивость рождается не из агрессии, а из умения притормозить в нужный момент.

Что делать дальше

Соберите метрики на реальной нагрузке и посмотрите на доли успеха и 429. Постепенно подстройте лимиты под каждый сервис. Добавьте адаптивное ограничение темпа по заголовкам X-RateLimit. Внедрите кэширование для идемпотентных запросов.

Куда развиваться

Изучите отдельно тему пула IP-адресов и его здоровья - это большая соседняя область, которую мы намеренно не трогали здесь. Погрузитесь в наблюдаемость: трассировки, дашборды, алерты. И обязательно почитайте документацию сервисов, с которыми работаете: точные лимиты всегда лучше догадок.

Вы отлично справились. Теперь у вас есть клиент, который не паникует, а ведёт себя устойчиво и вежливо. Это фундамент, на котором строятся надёжные интеграции. Удачи в ваших проектах.