Короткий ответ
Библиотека должна по умолчанию задавать безопасные таймауты, ограниченные ретраи с backoff и джиттером только для идемпотентных операций, встроенный circuit breaker для защиты как клиентов, так и самого вызываемого сервиса, и обязательную структурированную телеметрию (request ID, латентность, код ответа) из коробки; единственно верной комбинации параметров не существует — конкретные значения таймаутов, число ретраев и пороги circuit breaker нужно обсуждать отдельно для каждого класса зависимостей и явно проговаривать компромиссы с командами-потребителями библиотеки.
Как это работает подробнее
централизованная библиотека даёт единый контроль над устойчивостью (таймауты, ретраи, circuit breaker) и наблюдаемостью (трейсинг, метрики) для всех её пользователей сразу, вместо того чтобы каждая команда реализовывала это по-своему с разным качеством. Circuit breaker временно прекращает попытки вызова при устойчивых сбоях зависимости, защищая и вызывающую сторону от трат ресурсов на заведомо неудачные запросы, и саму зависимость от retry storm в момент восстановления.
более агрессивные ретраи и широкие таймауты повышают отказоустойчивость отдельного вызова, но увеличивают общую нагрузку на зависимость и удлиняют время до сигнала об инциденте; жёсткий circuit breaker быстрее "режет" трафик при проблемах, но может ложно срабатывать при кратковременных всплесках задержки. Выбор конкретных порогов должен обсуждаться с владельцами зависимостей и исходить из их SLO, а не задаваться библиотекой в одностороннем порядке.
Пример кода
import random
import time
import requests
class ApiClient:
def __init__(self, base_url, timeout=5, max_retries=3):
self.base_url = base_url
self.timeout = timeout
self.max_retries = max_retries
def get(self, path):
for attempt in range(self.max_retries):
try:
response = requests.get(self.base_url + path, timeout=self.timeout)
response.raise_for_status()
return response.json()
except (requests.Timeout, requests.ConnectionError):
if attempt + 1 == self.max_retries:
raise
time.sleep(min(8, 2 ** attempt) + random.uniform(0, 0.5))Что использовать на практике
- Рекомендация. нужно явно разделить операции на идемпотентные (безопасно ретраить) и неидемпотентные (ретраить только с идемпотентным ключом или вообще не ретраить автоматически), предусмотреть конфигурируемые профили таймаутов для разных классов зависимостей (внутренний сервис с низкой задержкой vs внешний API), и обязательную прокидку
request_id/trace_idчерез заголовки для сквозной диагностики инцидентов между командами.
Подводные камни
- Ошибка. задавать одни и те же параметры retry/timeout для всех вызовов независимо от характера зависимости; делать библиотеку обязательной "чёрной коробкой" без возможности переопределить поведение для нетипичных случаев, из-за чего команды начинают обходить её самостоятельно; собирать метрики только по успешным запросам, упуская из виду распределение ошибок и таймаутов, которое критично для диагностики деградаций.