Транспорт бэкенда

Dead Letter Queue, Retry и Circuit Breaker

Black Friday, 11:59 PM. Payment service начинает замедляться из-за нагрузки на БД. Сотни микросервисов шлют retry запросы - и перегружают его ещё больше. Через 2 минуты весь кластер встаёт: thread pools исчерпаны ожиданием платежей. Netflix называет это 'distributed system death spiral'. Circuit Breaker, DLQ и Bulkhead - барьеры против этого сценария.

  • **AWS SQS** имеет встроенный Dead Letter Queue с redrive policy. Каждый SQS consumer в Amazon production настроен с DLQ и алертом на появление сообщений.
  • **Netflix Hystrix** (сейчас Resilience4j) защищает 500+ сервисов Netflix. Circuit Breaker открывается при 50%+ error rate за 10 секунд и закрывается через 5 секунд в HALF-OPEN.
  • **Google SRE** документирует exponential backoff как обязательное требование для всех RPC вызовов. Без jitter и backoff retry storm в 2012 году привёл к нескольким часам даунтайма Google App Engine.

Retry стратегии

Большинство сетевых ошибок в распределённых системах - временные: таймаут из-за GC паузы, overloaded DB, сетевой блип. Retry позволяет пережить кратковременные сбои автоматически. Главный вопрос: когда и как часто повторять попытку, не перегружая упавший сервис.

AWS SDK, gRPC-js, Axios-retry - все реализуют retry из коробки. В Kafka Consumer retry обычно реализуется через отдельные топики: `orders.events.retry-1`, `orders.events.retry-2`, `orders.events.dlq`.

HTTP 400 Bad Request при вызове внешнего API - стоит ли retry?

Exponential Backoff с Jitter

Naive retry с фиксированным интервалом создаёт thundering herd: тысячи клиентов одновременно пробуют снова после того, как сервер упал. Exponential backoff увеличивает задержку экспоненциально, jitter (случайное смещение) предотвращает синхронизацию клиентов.

AWS 2015 статья 'Exponential Backoff And Jitter' показала: decorrelated jitter даёт на 30% меньше нагрузки на восстанавливающийся сервер по сравнению с полным backoff без jitter. Jitter критичен при 1000+ клиентах.

Зачем добавлять jitter (случайный сдвиг) к exponential backoff?

Dead Letter Queue

Dead Letter Queue (DLQ) - специальная очередь для сообщений, которые не удалось обработать после всех retry. Вместо потери сообщения или блокировки очереди - оно перемещается в DLQ для ручного анализа или позднейшей обработки.

DLQ должен быть видимым: алерт при появлении любого сообщения в DLQ. Это не нормальная ситуация - это сигнал о баге в потребителе или некорректных данных. Amazon использует DLQ для каждого SQS consumer в production.

Что должно произойти при появлении сообщений в DLQ?

Circuit Breaker

Circuit Breaker - паттерн защиты от каскадных сбоев. Если зависимость (DB, внешний API) деградирует, постоянные retry исчерпывают ресурсы вызывающего сервиса. Circuit Breaker отслеживает error rate и при превышении порога 'разрывает цепь' - возвращает ошибку без реального вызова.

Netflix ввёл Hystrix (Circuit Breaker библиотека) в 2011 году. Сейчас Hystrix на maintenance, Netflix использует Resilience4j. Envoy Proxy (Istio) реализует Circuit Breaker на уровне service mesh - без изменений в коде.

Зачем Circuit Breaker переходит в состояние HALF-OPEN после таймаута?

Bulkhead Pattern

Bulkhead (переборка корабля) - изоляция ресурсов для разных типов запросов. Если один тип запросов потребляет все thread pool слоты / соединения - другие тоже не могут работать. Bulkhead выделяет отдельный пул ресурсов для каждой критической функциональности.

Netflix применяет bulkhead на уровне Hystrix thread pools: каждый downstream сервис имеет свой thread pool. Если Recommendations API зависает - Payment API продолжает работать с отдельным пулом потоков.

Circuit Breaker и retry - конкурирующие паттерны, нужно выбрать один

Они дополняют друг друга: retry справляется с кратковременными сбоями, circuit breaker защищает при длительной деградации

Правильная комбинация: retry с backoff для первых 3 попыток, circuit breaker открывается если сервис деградирует на протяжении минут. DLQ принимает сообщения когда circuit open. Bulkhead предотвращает распространение на другие компоненты.

Какую проблему решает Bulkhead Pattern?

Ключевые идеи

  • **Retry только для transient ошибок** (5xx, timeout). Permanent ошибки (4xx) не нужно retry-ить - повтор даст тот же результат.
  • **Exponential backoff с jitter** предотвращает thundering herd: клиенты размазывают retry во времени вместо синхронного шторма.
  • **DLQ + Circuit Breaker + Bulkhead** - три уровня защиты: DLQ сохраняет необработанные сообщения, CB останавливает шторм, Bulkhead изолирует деградацию.

Связанные темы

Retry, DLQ и Circuit Breaker образуют паттерн надёжности поверх любого транспорта:

  • RabbitMQ и очереди — DLQ - нативный механизм RabbitMQ через dead-letter-exchange; retry топики - паттерн из Kafka ecosystem
  • Backpressure и Flow Control — Backpressure предотвращает перегрузку проактивно; Circuit Breaker реагирует на уже произошедшую деградацию

Вопросы для размышления

  • Как различить transient ошибку (можно retry) от permanent в контексте ошибок внешнего API без HTTP-кодов?
  • При каком error rate Circuit Breaker должен открываться? Как этот порог зависит от критичности сервиса?
  • Как реализовать replay из DLQ обратно в основную очередь, не нарушив порядок событий?

Связанные уроки

  • db-03-acid
  • alg-22-backtracking
Dead Letter Queue, Retry и Circuit Breaker

0

1

Войти