Какие архитектурные паттерны и подходы используются для обеспечения отказоустойчивости приложения?

«Какие архитектурные паттерны и подходы используются для обеспечения отказоустойчивости приложения?» — вопрос из категории Архитектура, который задают на 10% собеседований Python Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Отказоустойчивость (fault tolerance) — это способность системы продолжать работать корректно даже при возникновении сбоев в её компонентах. Для этого применяются следующие подходы:

1. Повторные попытки (Retry)

Паттерн для автоматического повторения операции, которая завершилась неудачей из-за временной проблемы (например, сбой сети). Часто используется с экспоненциальной задержкой (exponential backoff), чтобы не перегружать отказавший сервис.

  • Пример: Библиотека tenacity.
from tenacity import retry, stop_after_attempt, wait_exponential

@retry(stop=stop_after_attempt(3), wait=wait_exponential(multiplier=1, min=2, max=10))
def call_unstable_external_service():
    # Код вызова внешнего API, который может временно отказать
    response = requests.get("http://example.com/api/data")
    response.raise_for_status() # Вызовет исключение для кодов 4xx/5xx
    return response.json()

2. Автоматический выключатель (Circuit Breaker)

Предотвращает лавинообразные отказы. Если сервис-зависимость постоянно выдает ошибки, "прерыватель" временно перестает отправлять к нему запросы, давая ему время на восстановление и немедленно возвращая ошибку клиенту.

  • Пример: Библиотека pybreaker.
from pybreaker import CircuitBreaker

# Если 5 вызовов подряд неудачны, "размыкается" на 60 секунд
db_breaker = CircuitBreaker(fail_max=5, reset_timeout=60)

@db_breaker
def get_data_from_db():
    # ... код для чтения из базы данных ...
    pass

3. Изоляция компонентов (Bulkhead)

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

  • Реализация: Микросервисная архитектура, контейнеризация (Docker), отдельные пулы соединений к разным базам данных.

4. Резервирование и репликация (Redundancy and Replication)

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

  • Примеры: Репликация баз данных (primary-replica), запуск нескольких экземпляров приложения за балансировщиком нагрузки.

5. Таймауты (Timeouts)

Установка максимального времени ожидания для любой блокирующей операции (сетевой запрос, запрос к БД). Это предотвращает "зависание" потоков в ожидании ответа от медленного или неработающего сервиса.

try:
    response = requests.get(url, timeout=5) # Ждать ответа не более 5 секунд
except requests.Timeout:
    logger.warning("Request timed out")
    return get_fallback_data()