Reliability & Failure Handling
В распределённых системах происходят частичные отказы: одна зависимость замедляется, один запрос получает timeout или один регион теряет связь, пока остальная система продолжает работать. Надёжность строится на определённом поведении при сбоях, контролируемом восстановлении и данных мониторинга, а не на предположении, что отказы редки.
Timeouts и retries
У каждого удалённого вызова должен быть timeout, рассчитанный с учётом latency budget вызывающей стороны. Без него зависшие вызовы занимают соединения, потоки, память и время пользователя. Timeout создаёт неопределённость: вызывающая сторона знает, что перестала ждать, но не знает, завершилась ли удалённая операция.
Retries помогают при временных сбоях, но также умножают нагрузку и могут повторить side effects. Используйте retry budget, ограниченное число попыток, exponential backoff и jitter. Координируйте уровни retries, чтобы клиент, API и сервис не создали retry storm.
Для одной логической мобильной операции нужно определить слой, который управляет retry policy. UI callbacks, HTTP interceptors, repositories, API gateways, backend services и durable workers не должны независимо умножать попытки. Интерактивный retry безопасного чтения, повтор mutation с idempotency key и долговечный retry после завершения мобильного процесса имеют разные ограничения; владелец зависит от операции, а не от одной универсальной реализации.
Слепой повтор платежа может списать деньги дважды, если первый запрос завершился, но ответ потерялся. Безопасность retry зависит от семантики операции. Idempotency key позволяет сервису распознать одну логическую операцию:
Сервис должен атомарно связать ключ с результатом операции в подходящей области и на заданный срок; повторное использование ключа с другим содержимым запроса следует отклонять. Один заголовок сам по себе не обеспечивает идемпотентность.
Изоляция и graceful degradation
Circuit breaker прекращает вызовы зависимости после превышения порога ошибок, ждёт cooldown и разрешает ограниченные пробные запросы перед восстановлением обычного трафика:
stateDiagram-v2
[*] --> CLOSED
CLOSED --> OPEN: превышен порог ошибок
OPEN --> HALF_OPEN: cooldown
HALF_OPEN --> CLOSED: проба успешна
HALF_OPEN --> OPEN: проба неуспешна
Он защищает ресурсы и сокращает повторные вызовы отказавшей зависимости, но требует подходящих порогов и нужен не в каждом процессе приложения. Лимиты параллелизма, bulkheads и очереди могут изолировать перегруженную зависимость от несвязанной работы.
Graceful degradation сохраняет основное поведение при отказе необязательной зависимости. Если рекомендации недоступны, продукт может продолжить показывать поиск и сохранённый контент. Fallback должен быть безопасным и честным: устаревшие цены или решения авторизации могут быть хуже явной ошибки.
Redundancy и восстановление
Redundancy устраняет single point of failure, только если реплики не подвержены одному сценарию отказа. Liveness показывает, жив ли процесс настолько, что перезапуск может помочь; readiness определяет, следует ли сейчас направлять трафик на этот экземпляр; startup checks при необходимости защищают медленную инициализацию. Состояние зависимостей тоже важно наблюдать, но readiness check не обязан синхронно проверять каждый downstream service. Если из-за отказа одной общей зависимости все upstream-экземпляры одновременно станут not ready, такая политика может усилить сбой и вызвать каскадный отказ. Replication повышает устойчивость, но может вносить lag и общие ошибки конфигурации.
Учитывайте частичный успех и неопределённый результат. Многошаговому workflow может потребоваться compensating action, reconciliation или видимый статус pending, а не иллюзия атомарности. План восстановления должен охватывать восстановление долговечных данных, обработку backlog, безопасный replay и проверку инвариантов.
Observability как часть надёжности
Logs, metrics, traces и доменные сигналы должны показывать, какая операция завершилась ошибкой, где потрачено время, помогли ли retries и что увидели пользователи. Отслеживайте перцентили latency, error rate, saturation, возраст очереди, состояние зависимостей и бизнес-результаты. Alerts должны вести к конкретному действию, а не сообщать о каждом кратком сбое.
Надёжное поведение является компромиссом между availability, корректностью, latency, стоимостью и операционной сложностью. Сначала определите важный сценарий отказа, затем выберите минимальный механизм, который его покрывает.