$ sudo teach|
    $ sudo teach|
    IT school
  • Telegram
  • Партнёрам
  • Все курсы
$ sudo teach IT
OOO "SALEPROFIT"Контакты и реквизитыIT-Park Logo

Школа

  • Блог
  • Проверить сертификат

Сотрудничество

  • Стать учителем
  • Партнёрская программа
  • О проекте

Право

  • Оферта
  • Политика конфиденциальности

© 2023–2026 $ sudo teach IT™. All Rights Reserved. Public user contributions licensed under CC BY-SA 4.0 license with attribution required
TelegramGitHubYouTube
ГлавнаяБлогГайд по асинхронному HTTP-клиенту в Python: таймауты, ретраи, backoff и отмена запросов

Гайд по асинхронному HTTP-клиенту в Python: таймауты, ретраи, backoff и отмена запросов

$ sudo teach IT
·8 августа 2026 г.·12 мин·27
Гайд по асинхронному HTTP-клиенту в Python: таймауты, ретраи, backoff и отмена запросов

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

Содержание
Что ломается в HTTP-клиентах “по умолчанию”Типовые причины проблемБазовые требования к “правильному” асинхронному клиенту1) Единые таймауты2) Ретраи только для безопасных сценариев3) Backoff с джиттером4) Корректная отмена5) Прозрачность логикиВыбор библиотеки: aiohttp как рабочий минимумАрхитектура: разделяем транспорт и политику повторовТаймауты: раздельно по стадиям и единый “контракт”Идемпотентность: ключ к безопасным повторамПочему это критичноЧто делать на практикеРетраи: классификация ошибок и условия для повторной попыткиРетраимые ситуацииНеретраимые (или редко ретраимые)Пример классификатораBackoff: экспонента + джиттер + границыКорректная отмена: критически важный момент в asyncПаттернРеализация: асинхронный HTTP-клиент с таймаутами, ретраями и отменойКод: класс клиентаКак использоватьВажные детали, которые обычно пропускают1) Не путайте таймаут “всего запроса” и “чтения”2) Retry loop должен учитывать отмену3) Ретрай на 4xx — почти всегда ошибка проектирования4) Ретрай на 5xx — не всегда “безопасен” по бизнес-логике5) Следите за количеством попыток6) Лимитируйте конкурентностьПодводные камни идемпотентности: что важно согласовать с APIПроверка корректности: как тестировать retry и отменуЮнит-тесты на retry-условияТест отменыПрактические паттерны для интеграцийПаттерн A: “GET всегда ретраим, POST — только с идемпотентностью”Паттерн B: ретраить не “всё”, а только когда таймаут вероятно транзитныйПаттерн C: журналировать “попытка / delay / причина”Итог: как собрать устойчивый async HTTP-клиент

Интеграции, в которых Python общается по HTTP с внешними сервисами, почти всегда упираются не в “как отправить запрос”, а в операционные детали: таймауты, повторные попытки, корректная отмена, идемпотентность и устойчивость к сетевым сбоям. Эти нюансы особенно болезненны в асинхронных системах, где одна “неудачно отменённая” корутина или неправильно настроенный retry может накапливать нагрузку и превращать деградацию в инцидент.

Ниже — практический гайд по построению асинхронного HTTP-клиента в Python, ориентированный на реальные интеграции: единые таймауты, ретраи только для безопасных сценариев, backoff с джиттером, идемпотентные операции и корректная отмена запросов через отмену задач.


Что ломается в HTTP-клиентах “по умолчанию”

Перед тем как писать клиент, полезно понять, какие сбои типичны:

Типовые причины проблем

  • Таймауты отсутствуют или “слишком общие”: запрос может зависнуть на соединении/рукопожатии/чтении ответа, а ваше приложение будет держать ресурсы (сокеты, корутины, пул).
  • Ретраи настроены без учёта метода и идемпотентности: повтор POST может привести к двойному созданию сущности.
  • Ретрай-логика без классификации ошибок: повторяют и 4xx, и ошибки протокола, хотя часть из них не может исчезнуть при повторе.
  • Backoff без джиттера: все клиенты синхронно повторяют попытки и перегружают целевой сервис (thundering herd).
  • Отмена запроса реализована некорректно: отмена задачи не отменяет I/O, корутина продолжает выполняться, а ресурсы удерживаются.
  • Отсутствие ограничений на конкурентность: при всплеске ошибок ретраи увеличивают число одновременных запросов.

Именно поэтому “правильный клиент” — это не один параметр, а связка решений.


Базовые требования к “правильному” асинхронному клиенту

Сформулируем требования, которым будем следовать:

1) Единые таймауты

У HTTP есть несколько стадий:

  • установление соединения,
  • TLS/handshake,
  • ожидание “первого байта” (read start),
  • чтение тела (read).

Хороший клиент задаёт таймауты на эти стадии раздельно либо использует библиотечные структуры таймаутов, которые отражают эти этапы.

2) Ретраи только для безопасных сценариев

Повторять запрос имеет смысл:

  • для идемпотентных методов: GET, HEAD, PUT, а также DELETE (в REST обычно идемпотентен),
  • для неидемпотентных сценариев только если:
    • API явно поддерживает идемпотентность через Idempotency-Key,
    • вы можете гарантировать, что сервер обработает повтор безопасно.

3) Backoff с джиттером

Схема backoff должна:

  • расти экспоненциально,
  • иметь случайный разброс (джиттер),
  • быть ограниченной сверху (max delay),
  • учитывать Retry-After при наличии.

4) Корректная отмена

Если вызывающий код отменил задачу (например, тайм-аутом верхнего уровня, shutdown’ом или отменой в orchestrator’е), клиент должен:

  • прекратить ожидание ответа,
  • не продолжать retry-петлю,
  • корректно выйти без утечек.

5) Прозрачность логики

В продакшене важно понимать: какой статус/исключение привели к ретраю, сколько попыток сделано, какой delay применён.


Выбор библиотеки: aiohttp как рабочий минимум

Для асинхронного HTTP-клиента в Python часто выбирают aiohttp. Он даёт:

  • управляемые таймауты,
  • поддержку отмены,
  • понятную модель сессии и соединений,
  • удобные хуки/обработчики.

Ниже примеры будут на aiohttp, потому что их легко перенести в реальные интеграции. Если вы используете httpx, идеи остаются теми же (timouts/retries/cancellation), а API формы будут другими.


Архитектура: разделяем транспорт и политику повторов

Удачный подход:

  1. Транспорт: как именно делается запрос (URL, headers, тело, чтение ответа).
  2. Политика: когда ретраить, какой backoff, как считать попытки, какие ошибки считать ретраируемыми.
  3. Отмена: единая точка, где отмена “разрывает” ожидание и прерывает retry loop.

Сформируем каркас.


Таймауты: раздельно по стадиям и единый “контракт”

В aiohttp таймауты задаются через aiohttp.ClientTimeout. Обычно минимум:

  • total — общий (опционально),
  • sock_connect — таймаут на установление соединения,
  • sock_read — таймаут на чтение ответа,
  • иногда connect/sock_connect зависят от версии и типа.

Пример:

code
import aiohttp

def build_timeout(
    *,
    connect_s: float = 3.0,
    read_s: float = 10.0,
    total_s: float | None = 15.0,
) -> aiohttp.ClientTimeout:
    # total_s: общий “лимит жизни” запроса
    # sock_connect: ожидание подключения
    # sock_read: ожидание данных при чтении тела
    return aiohttp.ClientTimeout(
        total=total_s,
        sock_connect=connect_s,
        sock_read=read_s,
    )

Нюанс: если вы ставите слишком большой sock_read, а сервер отвечает медленно или “держит соединение”, ретраи могут усиливать проблему. Практика: 5–15 секунд на чтение для внешних API часто адекватны, но зависит от SLA.


Идемпотентность: ключ к безопасным повторам

Почему это критично

Для POST большинство операций не обязаны быть идемпотентными. Повтор может создать объект дважды, списать деньги дважды или инициировать два одинаковых вебхука.

Что делать на практике

  • Для GET/HEAD/PUT/DELETE ретраи обычно допустимы (при условии, что вы корректно обрабатываете таймауты и сетевые ошибки).
  • Для POST:
    • либо используйте API-инвариант “идемпотентность по ключу” (header Idempotency-Key),
    • либо вообще не ретрайте, или ретрайте только на чётко определённые сетевые ошибки, когда вы уверены, что сервер не получил запрос (но на практике это почти никогда нельзя гарантировать).

Ретраи: классификация ошибок и условия для повторной попытки

Надёжная retry-политика — это не “поймать Exception и повторить”. Нужно различать:

Ретраимые ситуации

Чаще всего ретраимы:

  • временные сетевые ошибки (например, сбой соединения),
  • таймауты (connect/read/total),
  • 5xx (особенно 502/503/504),
  • проблемы уровня маршрутизации, которые могут быть временными,
  • 429 Too Many Requests — с учётом Retry-After.

Неретраимые (или редко ретраимые)

  • 4xx (кроме 408/429 в некоторых сценариях) — как правило, повтор не поможет,
  • ошибки, связанные с некорректным запросом (400/401/403),
  • нарушения протокола, которые, как правило, не исправятся retry’ем.

Пример классификатора

В aiohttp таймауты и сетевые проблемы представлены исключениями, которые можно ловить точечно. Для этого удобно создать функцию:

code
from __future__ import annotations

import aiohttp

RETRIABLE_STATUSES = {408, 429, 500, 502, 503, 504}

def is_retriable_status(status: int) -> bool:
    return status in RETRIABLE_STATUSES

И отдельно классифицировать исключения:

code
def is_retriable_exception(exc: BaseException) -> bool:
    # Здесь логика под вашу библиотеку и версию aiohttp.
    # Идея: ретраить таймауты и сетевые ошибки, не ретраить “логические” ошибки.
    retriable_types = (
        aiohttp.ClientConnectionError,   # проблемы с соединением
        aiohttp.ClientPayloadError,      # проблемы с телом ответа
        asyncio.TimeoutError,            # если таймаут вы подняли/снаружи
        aiohttp.ServerTimeoutError,     # таймаут на серверной стороне (если есть)
        aiohttp.ClientOSError,           # обёртка над OSError в aiohttp
    )
    return isinstance(exc, retriable_types)

Backoff: экспонента + джиттер + границы

Схема “exponential backoff” выглядит так:

  • попытка №1 — задержка base * 2^0
  • попытка №2 — base * 2^1
  • и так далее
  • с ограничением max_delay

Добавляем джиттер, например равномерный:

code
import random

def compute_backoff_delay(
    attempt: int,                # 0..N-1
    *,
    base_delay: float = 0.3,
    max_delay: float = 10.0,
    jitter_ratio: float = 0.2,  # 20% случайного разброса
) -> float:
    delay = base_delay * (2 ** attempt)
    delay = min(delay, max_delay)

    # Джиттер: +/- jitter_ratio от delay
    jitter = delay * jitter_ratio
    return max(0.0, delay + random.uniform(-jitter, jitter))

Нюанс: если ваш сервис поддерживает Retry-After (секунды или http-date), его следует уважать, обычно это более точный сигнал, чем вычисленный backoff.


Корректная отмена: критически важный момент в async

Отмена в async — это отмена задачи. Если вы используете asyncio, важно:

  • не “проглатывать” asyncio.CancelledError,
  • в retry-цикле выходить сразу при отмене,
  • не делать asyncio.sleep() в retry, не учитывая отмену.

В Python правильная практика: всегда пробрасывать CancelledError, а не оборачивать в общий except Exception.

Паттерн

  • Ловите исключения для ретраев
  • Отдельно ловите asyncio.CancelledError и сразу raise

Реализация: асинхронный HTTP-клиент с таймаутами, ретраями и отменой

Ниже — компактный, но практичный пример “client wrapper” поверх aiohttp.ClientSession.

Код: класс клиента

code
from __future__ import annotations

import asyncio
import random
from dataclasses import dataclass
from typing import Any, Mapping, Optional

import aiohttp


@dataclass(frozen=True)
class RetryPolicy:
    max_attempts: int = 4
    base_delay: float = 0.3
    max_delay: float = 10.0
    jitter_ratio: float = 0.2


def compute_backoff_delay(
    attempt: int,
    *,
    base_delay: float,
    max_delay: float,
    jitter_ratio: float,
) -> float:
    delay = base_delay * (2 ** attempt)
    delay = min(delay, max_delay)
    jitter = delay * jitter_ratio
    return max(0.0, delay + random.uniform(-jitter, jitter))


def is_retriable_status(status: int) -> bool:
    return status in {408, 429, 500, 502, 503, 504}


def is_retriable_exception(exc: BaseException) -> bool:
    retriable_types = (
        aiohttp.ClientConnectionError,
        aiohttp.ClientPayloadError,
        aiohttp.ClientOSError,
        aiohttp.ServerTimeoutError,
        asyncio.TimeoutError,
    )
    return isinstance(exc, retriable_types)


def parse_retry_after(headers: Mapping[str, str]) -> Optional[float]:
    """
    Очень упрощённый парсер Retry-After:
    - поддерживаем только формат "seconds"
    - для http-date можно расширить при необходимости
    """
    value = headers.get("Retry-After")
    if not value:
        return None
    try:
        return float(value)
    except ValueError:
        return None


class HttpClient:
    def __init__(
        self,
        session: aiohttp.ClientSession,
        *,
        timeout: aiohttp.ClientTimeout,
        retry_policy: RetryPolicy = RetryPolicy(),
    ):
        self._session = session
        self._timeout = timeout
        self._retry_policy = retry_policy

    async def request_json(
        self,
        method: str,
        url: str,
        *,
        params: Optional[Mapping[str, Any]] = None,
        json: Any = None,
        headers: Optional[Mapping[str, str]] = None,
        idempotency_key: Optional[str] = None,
    ) -> Any:
        # Для POST/PUT с идемпотентностью можно передавать ключ.
        # Если API поддерживает Idempotency-Key — ретраи становятся намного безопаснее.
        method_upper = method.upper()

        req_headers = dict(headers or {})
        if idempotency_key:
            req_headers.setdefault("Idempotency-Key", idempotency_key)

        # Идемпотентные методы без специальных ключей обычно безопасны для ретраев
        safe_methods = {"GET", "HEAD", "PUT", "DELETE"}

        # Считаем, можно ли ретраить метод в текущей конфигурации
        can_retry = (method_upper in safe_methods) or bool(idempotency_key)

        if not can_retry and self._retry_policy.max_attempts > 1:
            # Мягко ограничиваем: если метод не безопасен и идемпотентности нет — ретраев не будет
            max_attempts = 1
        else:
            max_attempts = self._retry_policy.max_attempts

        last_exc: BaseException | None = None
        for attempt in range(max_attempts):
            try:
                async with self._session.request(
                    method_upper,
                    url,
                    params=params,
                    json=json,
                    headers=req_headers,
                    timeout=self._timeout,
                ) as resp:
                    # Если вы хотите читать не весь body — лучше сначала проверить статус.
                    status = resp.status

                    if status >= 400:
                        # Для ретраев статус-коды важны
                        if is_retriable_status(status) and attempt < max_attempts - 1 and can_retry:
                            retry_after = parse_retry_after(resp.headers)
                            if retry_after is not None:
                                delay = retry_after
                            else:
                                delay = compute_backoff_delay(
                                    attempt,
                                    base_delay=self._retry_policy.base_delay,
                                    max_delay=self._retry_policy.max_delay,
                                    jitter_ratio=self._retry_policy.jitter_ratio,
                                )
                            # Важно: sleep должен реагировать на отмену
                            await asyncio.sleep(delay)
                            continue

                        # Неретраимая ошибка или исчерпаны попытки — пробрасываем с контекстом
                        text = await resp.text()
                        raise aiohttp.ClientResponseError(
                            resp.request_info,
                            resp.history,
                            status=status,
                            message=text[:500],
                            headers=resp.headers,
                        )

                    # Успех: парсим JSON
                    return await resp.json()

            except asyncio.CancelledError:
                # Ключевое: отмену нельзя “глотать” — иначе отмена верхнего уровня не сработает
                raise
            except Exception as exc:
                last_exc = exc
                if not can_retry or attempt >= max_attempts - 1:
                    raise

                if not is_retriable_exception(exc):
                    raise

                delay = compute_backoff_delay(
                    attempt,
                    base_delay=self._retry_policy.base_delay,
                    max_delay=self._retry_policy.max_delay,
                    jitter_ratio=self._retry_policy.jitter_ratio,
                )
                await asyncio.sleep(delay)

        # Теоретически сюда не дойдём, но пусть будет на случай логической ошибки.
        if last_exc:
            raise last_exc
        raise RuntimeError("Request failed without exception")

Как использовать

code
import asyncio
import aiohttp

async def main():
    timeout = build_timeout(connect_s=3.0, read_s=10.0, total_s=15.0)
    retry_policy = RetryPolicy(max_attempts=4)

    async with aiohttp.ClientSession() as session:
        client = HttpClient(session, timeout=timeout, retry_policy=retry_policy)

        # Пример GET (ретраи безопасны)
        data = await client.request_json("GET", "https://httpbin.org/json")
        print(data)

asyncio.run(main())

Важные детали, которые обычно пропускают

1) Не путайте таймаут “всего запроса” и “чтения”

Если вы задаёте только total, иногда вы “скрываете” проблему: соединение может зависнуть или сервер может долго не отдавать данные. Раздельные таймауты помогают быстрее идентифицировать тип деградации.

2) Retry loop должен учитывать отмену

Даже если отмена задачи уже произошла, ваш код может находиться в asyncio.sleep(delay) или ожидать I/O.
В приведённом подходе отмена отработает, потому что await asyncio.sleep(...) и I/O через aiohttp прерываются при отмене задачи, а CancelledError пробрасывается.

3) Ретрай на 4xx — почти всегда ошибка проектирования

Даже 404 часто означает “не существует”, повтор бесполезен. Исключения обычно ограничиваются 408 и 429 (и то — с учётом контрактов API).

4) Ретрай на 5xx — не всегда “безопасен” по бизнес-логике

HTTP 500 говорит о проблеме на сервере, но это не гарантия, что запрос не был частично обработан. Для идемпотентности — снова ключевая тема: сервер должен уметь безопасно обрабатывать повтор.

5) Следите за количеством попыток

max_attempts=6 на массовых запросах может быстро превратить проблему сети в нагрузочную бурю. Практический диапазон для внешних API часто 3–4. Увеличивайте только при реальной необходимости и после измерений.

6) Лимитируйте конкурентность

Если у вас 1000 запросов одновременно и каждый ретраится, вы можете получить всплеск исходящего трафика. Для системного решения добавляйте семафор/очередь.

Пример ограничения конкурентности:

code
sem = asyncio.Semaphore(20)

async def safe_call(fn, *args, **kwargs):
    async with sem:
        return await fn(*args, **kwargs)

Подводные камни идемпотентности: что важно согласовать с API

Даже если вы добавили Idempotency-Key, стоит проверить:

  • Сколько времени действует ключ на стороне сервера.
  • Как сервер ведёт себя при истечении (например, ключ сбрасывается через N минут — тогда ретраи спустя время могут снова создать дубликаты).
  • На какие операции распространяется идемпотентность: только на POST? или и на PUT?
  • Что считается “одинаковым запросом”: ключ может защищать от повторов, но если тело отличается, сервер может обработать как отдельную операцию.

Для “плавающих” интеграций часто приходится договориться об идемпотентности отдельно: иначе невозможно сделать надежные ретраи на неидемпотентных методах.


Проверка корректности: как тестировать retry и отмену

Юнит-тесты на retry-условия

Идеальный тест проверяет:

  • при 500 выполняется повтор,
  • при 400 ретрая нет,
  • при 429 используется Retry-After,
  • при отмене задачи — нет попыток после отмены.

Для тестов удобнее подменять транспорт. Если используете aiohttp, можно:

  • тестировать на встроенных mock-серверах,
  • или мокать метод session.request.

Тест отмены

Ключевой тест: отмена должна прерывать ожидание и выходить из функции с CancelledError.

Пример идеи (упрощённо):

code
import asyncio
import aiohttp
import pytest

async def test_cancelled_does_not_retry():
    timeout = build_timeout(connect_s=1.0, read_s=1.0, total_s=2.0)
    async with aiohttp.ClientSession() as session:
        client = HttpClient(session, timeout=timeout, retry_policy=RetryPolicy(max_attempts=5))

        task = asyncio.create_task(client.request_json("GET", "http://example.invalid"))
        await asyncio.sleep(0.1)
        task.cancel()

        with pytest.raises(asyncio.CancelledError):
            await task

Практические паттерны для интеграций

Паттерн A: “GET всегда ретраим, POST — только с идемпотентностью”

Это самый частый безопасный базис:

  • GET ретраим по сетевым причинам и по 5xx/408/429,
  • POST ретраим только при наличии Idempotency-Key.

Паттерн B: ретраить не “всё”, а только когда таймаут вероятно транзитный

Если запрос завис на чтении ответа (sock_read), ретрай может быть разумным. Но если сервер успел обработать запрос и ответ уже “в пути”, вы всё равно не узнаете результат. Поэтому:

  • бизнес должен быть устойчив к повторам (идемпотентность),
  • или вы ретраите ограниченно и осознанно.

Паттерн C: журналировать “попытка / delay / причина”

В логах нужны поля:

  • попытка (attempt),
  • метод/endpoint (без секретов),
  • статус/исключение,
  • delay и причина delay (Retry-After vs computed),
  • request-id/correlation-id (если у вас есть).

Иначе расследование инцидентов превращается в гадание.


Итог: как собрать устойчивый async HTTP-клиент

Хороший асинхронный HTTP-клиент — это сочетание нескольких инженерных решений:

  • Таймауты задаются раздельно и соответствуют стадиям запроса.
  • Ретраи происходят только в тех случаях, где есть шанс на восстановление, и только для идемпотентных сценариев либо при поддержке Idempotency-Key.
  • Backoff экспоненциальный, с джиттером, и уважает Retry-After.
  • Отмена корректно пробрасывается через asyncio.CancelledError, прерывая retry loop и I/O.
  • Ограничения (конкурентность, max_attempts) не дают ретраям усилить деградацию.

Если хотите глубже разобраться в проектировании таких клиентов (и не только в “параметрах”, но и в архитектуре устойчивых интеграций), полезно посмотреть учебные материалы по асинхронным паттернам и построению надёжных систем: например, курс по этой теме можно использовать как структурированный путь для практики — /course/.


Войдите, чтобы поставить лайк и оставить комментарий.

Автор

$ sudo teach IT

Продолжите обучение

Все курсы
Python – для начинающих!

Python – для начинающих!

С нуля до профессионального уровня. Подходит для всех. Учитесь каждый день и овладейте самым популярным языком программирования.

Перейти к курсу
Python для аналитиков

Python для аналитиков

Данный курс будет очень полезен и отдельно аналитикам, и специалистам по машинному обучению, и тем, кто объединяет обе эти профессии (специалисты по Data Science).

Перейти к курсу
Ботостроение Telegram

Ботостроение Telegram

Лёгкий, быстрый и доступный способ познакомиться с миром ботостроения в Telegram. Видео, конспекты, практика и помощь – всё у нас на курсе.

Перейти к курсу
Python для машинного обучения. Основы

Python для машинного обучения. Основы

Машинное обучение это популярнейшее и востребованное направление в сфере IT, с использованием языка Python.

Перейти к курсу

Другие статьи

Как устроен сборщик мусора в CPython: трассировка объектов и циклические ссылки
устроен

Как устроен сборщик мусора в CPython: трассировка объектов и циклические ссылки

Погружаемся в механизм reference counting и cyclic garbage collector — смотрим на исходный код CPython, пишем тесты и выясняем, когда объект действительно удаляется из памяти.

17 июля 2026 г.
570
SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI
sqlmodel

SQLModel vs SQLAlchemy: что выбрать для Python-проекта с FastAPI

Сравниваем два подхода к работе с базами данных в Python-экосистеме: где SQLModel упрощает жизнь, а где мощность SQLAlchemy незаменима. С примерами моделей, запросов и миграций.

18 июля 2026 г.
530
Создание Telegram бота в 2026 легко и просто! Полный курсы!
создание

Создание Telegram бота в 2026 легко и просто! Полный курсы!

19 июня 2026 г.
1031
Как выучить программирование самому и бесплатно: с чего начать и в каком порядке
выучить

Как выучить программирование самому и бесплатно: с чего начать и в каком порядке

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

21 сентября 2026 г.
100
FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь
fastapi

FastAPI и фоновые задачи: когда лучше использовать BackgroundTasks, а когда очередь

Поймём различия между синхронной обработкой, BackgroundTasks и внешними очередями. Разберём idempotency, ретраи и мониторинг фоновых процессов.

22 июля 2026 г.
750
cargo

Cargo для продакшена: workspaces, фичи и стратегия версий зависимостей

Разберём, как организовать Rust workspace, управлять optional features, фиксировать версии библиотек и уменьшать “дрейф” зависимостей при командной разработке и CI.

18 сентября 2026 г.
110

Комментарии

Пока нет комментариев

Содержание

Что ломается в HTTP-клиентах “по умолчанию”Типовые причины проблемБазовые требования к “правильному” асинхронному клиенту1) Единые таймауты2) Ретраи только для безопасных сценариев3) Backoff с джиттером4) Корректная отмена5) Прозрачность логикиВыбор библиотеки: aiohttp как рабочий минимумАрхитектура: разделяем транспорт и политику повторовТаймауты: раздельно по стадиям и единый “контракт”Идемпотентность: ключ к безопасным повторамПочему это критичноЧто делать на практикеРетраи: классификация ошибок и условия для повторной попыткиРетраимые ситуацииНеретраимые (или редко ретраимые)Пример классификатораBackoff: экспонента + джиттер + границыКорректная отмена: критически важный момент в asyncПаттернРеализация: асинхронный HTTP-клиент с таймаутами, ретраями и отменойКод: класс клиентаКак использоватьВажные детали, которые обычно пропускают1) Не путайте таймаут “всего запроса” и “чтения”2) Retry loop должен учитывать отмену3) Ретрай на 4xx — почти всегда ошибка проектирования4) Ретрай на 5xx — не всегда “безопасен” по бизнес-логике5) Следите за количеством попыток6) Лимитируйте конкурентностьПодводные камни идемпотентности: что важно согласовать с APIПроверка корректности: как тестировать retry и отменуЮнит-тесты на retry-условияТест отменыПрактические паттерны для интеграцийПаттерн A: “GET всегда ретраим, POST — только с идемпотентностью”Паттерн B: ретраить не “всё”, а только когда таймаут вероятно транзитныйПаттерн C: журналировать “попытка / delay / причина”Итог: как собрать устойчивый async HTTP-клиент