HardТеория3 min

Service Mesh и паттерны отказоустойчивости

Service Mesh, Service Discovery, Circuit Breaker, Retry, Timeout, Bulkhead и практики обеспечения устойчивости

Проблема сетевого взаимодействия

В микросервисах каждый вызов -- сетевой запрос. Сеть ненадёжна: пакеты теряются, сервисы падают, latency растёт. Нужны паттерны для обработки этих реалий.

8 заблуждений о распределённых системах (Peter Deutsch):

  1. Сеть надёжна               -- НЕТ, пакеты теряются
  2. Latency нулевая            -- НЕТ, ~1ms внутри DC, ~50ms между
  3. Пропускная способность бесконечна -- НЕТ, bandwidth ограничен
  4. Сеть безопасна             -- НЕТ, нужен mTLS
  5. Топология не меняется      -- НЕТ, поды перезапускаются
  6. Один администратор         -- НЕТ, разные команды
  7. Стоимость доставки нулевая -- НЕТ, сериализация стоит CPU
  8. Сеть однородна             -- НЕТ, разные провайдеры

Service Discovery

Проблема

В динамическом окружении (Kubernetes, облако) адреса сервисов меняются: поды перезапускаются, масштабируются, мигрируют между нодами.

Client-Side Discovery

┌──────────┐    1. Запрос адреса     ┌──────────────┐
│  Client  │───────────────────────>│   Service    │
│  Service │    2. Список адресов    │   Registry   │
│          │<───────────────────────│  (Consul,    │
│          │                        │   etcd)      │
│          │    3. Прямой вызов      └──────────────┘
│          │─────────────────────┐         ^
└──────────┘                     │         │ Register
                                 v         │
                          ┌──────────┐     │
                          │  Target  │─────┘
                          │  Service │
                          │ :8001    │
                          │ :8002    │  (multiple instances)
                          │ :8003    │
                          └──────────┘

Примеры: Netflix Eureka, HashiCorp Consul
Client знает адреса и балансирует сам.

Server-Side Discovery

┌──────────┐    1. Запрос      ┌────────────────┐
│  Client  │──────────────────>│  Load Balancer │
│  Service │    4. Ответ       │  / DNS         │
│          │<──────────────────│                │
└──────────┘                   └────────┬───────┘
                                        │
                          2. Роутинг    │
                          к инстансу    │
                     ┌──────────────────┼─────────┐
                     │                  │         │
                     v                  v         v
               ┌──────────┐     ┌──────────┐  ┌──────────┐
               │ Instance │     │ Instance │  │ Instance │
               │    1     │     │    2     │  │    3     │
               └──────────┘     └──────────┘  └──────────┘

Примеры: Kubernetes Services, AWS ALB/NLB
Client не знает адресов -- знает только DNS-имя.

Kubernetes Service Discovery

В Kubernetes Service Discovery встроен:

  Service Name: order-service
  DNS: order-service.default.svc.cluster.local
  ClusterIP: 10.96.0.15

  Pods:
    10.244.1.5:8080 (ready)
    10.244.2.8:8080 (ready)
    10.244.3.2:8080 (not ready -- excluded)

  kube-proxy / iptables / IPVS -- балансировка на уровне L4

Circuit Breaker

Проблема каскадных сбоев

Без Circuit Breaker:

Service A ──> Service B ──> Service C (УПАЛ!)
   │              │
   │  Ждёт 30s   │  Ждёт 30s
   │  timeout     │  timeout
   │              │
   └── Все потоки заняты ожиданием
       Service A тоже УПАЛ!

Каскадный сбой: C упал -> B завис -> A упал -> Всё лежит

Состояния Circuit Breaker

┌────────────────────────────────────────────────────┐
│              Circuit Breaker States                │
│                                                    │
│  ┌──────────┐   Failures    ┌──────────┐           │
│  │  CLOSED  │──────────────>│   OPEN   │           │
│  │          │   > threshold │          │           │
│  │ (работает│               │ (всё     │           │
│  │  нормаль)│               │  отклоняет│           │
│  └──────────┘               └────┬─────┘           │
│       ^                          │                 │
│       │                     Timer expires          │
│       │  Success                 │                 │
│       │  rate OK            ┌────┴──────┐          │
│       └─────────────────────│ HALF-OPEN │          │
│                             │           │          │
│                    Failure  │ (пробует  │          │
│               ┌─────────────│  1 запрос)│          │
│               │             └───────────┘          │
│               v                                    │
│          ┌──────────┐                              │
│          │   OPEN   │                              │
│          └──────────┘                              │
└────────────────────────────────────────────────────┘

Параметры Circuit Breaker

Параметр Описание Пример
Failure Threshold Процент ошибок для открытия 50% за 10 секунд
Timeout Время в OPEN до перехода в HALF-OPEN 30 секунд
Success Threshold Количество успехов в HALF-OPEN для закрытия 3 подряд
Sliding Window Окно для подсчёта ошибок 10 запросов или 10 секунд

Fallback стратегии

Circuit Breaker OPEN -- что вернуть клиенту?

1. Default Value:
   Recommendations Service упал -> вернуть "Popular Products"

2. Cache:
   Price Service упал -> вернуть кешированную цену

3. Degraded Response:
   Search Service упал -> вернуть результаты только по заголовкам

4. Error:
   Payment Service упал -> честная ошибка 503

Retry Pattern

Стратегии повтора

1. Immediate Retry:
   Запрос -> Ошибка -> Запрос -> Ошибка -> Запрос -> OK
   Проблема: DDoS собственного сервиса

2. Fixed Interval:
   Запрос -> Ошибка -> 1s -> Запрос -> Ошибка -> 1s -> Запрос -> OK
   Лучше, но все клиенты ретраят одновременно

3. Exponential Backoff:
   Запрос -> Ошибка -> 1s -> Запрос -> Ошибка -> 2s -> Запрос -> 4s
   Экспоненциально увеличиваем интервал

4. Exponential Backoff + Jitter (ЛУЧШИЙ):
   Запрос -> Ошибка -> 1s+random(0-500ms)
          -> Запрос -> Ошибка -> 2s+random(0-1000ms)
   Jitter разбрасывает ретраи по времени

Когда НЕ ретраить

Идемпотентные (безопасно ретраить):
  ✅ GET /orders/123
  ✅ PUT /orders/123 (полная замена)
  ✅ DELETE /orders/123

НЕ идемпотентные (опасно без Idempotency Key):
  ❌ POST /orders (может создать дубликат)
  ❌ POST /payments (может списать дважды)
  ✅ POST /payments + X-Idempotency-Key (безопасно)

Не ретраить при:
  ❌ 400 Bad Request (ошибка клиента, retry не поможет)
  ❌ 401/403 (проблема авторизации)
  ❌ 422 (бизнес-валидация)
  ✅ 500 (ошибка сервера, может быть временная)
  ✅ 502/503/504 (сервис недоступен)
  ✅ Timeout (network issue)

Timeout Pattern

Виды таймаутов

┌──────────┐    Connection     ┌──────────┐
│  Client  │    Timeout (1s)   │  Server  │
│          │──────┬──────────>│          │
│          │      │            │          │
│          │   Read Timeout    │          │
│          │   (5s)            │          │
│          │<─────────────────│ Response │
└──────────┘                   └──────────┘

Connection Timeout: максимальное время на установку TCP-соединения
Read Timeout: максимальное время ожидания ответа
Write Timeout: максимальное время на отправку запроса
Overall Timeout: максимальное время всей операции (включая retries)

Deadline Propagation

В цепочке вызовов таймаут должен уменьшаться:

Client: Overall Timeout = 5s
  │
  ├──> Service A: Timeout = 4s (оставляем 1s на обработку)
  │      │
  │      ├──> Service B: Timeout = 2s
  │      │
  │      └──> Service C: Timeout = 1.5s
  │
  Если Service B отвечает за 3s, Service A уже вернул timeout

  gRPC поддерживает deadline propagation нативно:
  grpc.WithTimeout(4 * time.Second)

Bulkhead Pattern

Идея: изоляция ресурсов

Название от переборок на корабле. Если пробоина в одном отсеке, другие остаются сухими.

Без Bulkhead:
┌────────────────────────────────┐
│  Service A                     │
│  Thread Pool: 100 threads      │
│                                │
│  Calls to B: 90 threads (SLOW!)│
│  Calls to C: 10 threads        │
│                                │
│  B упал -> 90 threads висят    │
│  -> нет threads для C          │
│  -> A тоже упал!               │
└────────────────────────────────┘

С Bulkhead:
┌────────────────────────────────┐
│  Service A                     │
│                                │
│  ┌──────────────┐              │
│  │ Pool for B   │ Max 20      │
│  │ threads      │ threads     │
│  └──────────────┘              │
│  ┌──────────────┐              │
│  │ Pool for C   │ Max 20      │
│  │ threads      │ threads     │
│  └──────────────┘              │
│  ┌──────────────┐              │
│  │ General Pool │ 60 threads  │
│  └──────────────┘              │
│                                │
│  B упал -> 20 threads висят   │
│  -> C работает нормально!      │
└────────────────────────────────┘

Service Mesh

Что такое Service Mesh

Инфраструктурный слой для управления сетевым взаимодействием. Весь трафик проходит через sidecar proxy рядом с каждым сервисом.

┌──────────────────────────────────────────────────┐
│                 Service Mesh                     │
│                                                  │
│  ┌──────────┐           ┌──────────┐             │
│  │ Service  │           │ Service  │             │
│  │    A     │           │    B     │             │
│  └────┬─────┘           └────┬─────┘             │
│       │                      │                   │
│  ┌────┴─────┐           ┌────┴─────┐             │
│  │ Sidecar  │ ════════> │ Sidecar  │  Data Plane │
│  │ Proxy    │ <════════ │ Proxy    │  (Envoy)    │
│  │ (Envoy)  │  mTLS     │ (Envoy)  │             │
│  └──────────┘           └──────────┘             │
│                                                  │
│  ┌──────────────────────────────────────┐        │
│  │         Control Plane                │        │
│  │  (Istio / Linkerd / Consul Connect)  │        │
│  │                                      │        │
│  │  - Traffic management (routing)      │        │
│  │  - Security (mTLS, AuthZ)            │        │
│  │  - Observability (metrics, tracing)  │        │
│  │  - Resilience (retry, circuit break) │        │
│  └──────────────────────────────────────┘        │
└──────────────────────────────────────────────────┘

Что делает Service Mesh

Функция Без Mesh С Mesh
mTLS Реализовать в каждом сервисе Автоматически
Circuit Breaker Библиотека в коде Конфигурация
Retry/Timeout Код в каждом клиенте Конфигурация
Tracing Instrumentation в коде Автоматически
Traffic Splitting Логика маршрутизации Canary % через config
Rate Limiting Middleware в каждом сервисе Конфигурация
Access Control AuthZ в каждом сервисе Policy

Популярные Service Mesh

┌─────────────────────────────────────────────────────┐
│  Istio                                              │
│  - Самый функциональный и сложный                   │
│  - Control plane: istiod                            │
│  - Data plane: Envoy                                │
│  - Подходит для крупных организаций                  │
│                                                     │
│  Linkerd                                            │
│  - Легковесный, проще Istio                         │
│  - Написан на Rust (proxy) и Go (control plane)     │
│  - Быстрый старт, низкое потребление ресурсов       │
│                                                     │
│  Consul Connect                                     │
│  - Service Mesh от HashiCorp                        │
│  - Интеграция с Consul Service Discovery            │
│  - Multi-datacenter из коробки                      │
│                                                     │
│  Cilium (eBPF)                                      │
│  - Service Mesh на уровне ядра Linux (без sidecar!) │
│  - Минимальный overhead                             │
│  - Растущая популярность в 2025+                    │
└─────────────────────────────────────────────────────┘

Когда НЕ нужен Service Mesh

  • Менее 10 сервисов -- overhead не окупается
  • Команда не готова к операционной сложности
  • Простые сценарии -- хватает библиотек (Resilience4j, go-kit)
  • Tight latency requirements -- sidecar добавляет ~1ms per hop

Реальные примеры

Netflix -- Hystrix (исторический)

Netflix создал Hystrix -- первую популярную библиотеку Circuit Breaker (2012). Каждый из 700+ микросервисов Netflix использовал Hystrix для защиты от каскадных сбоев. В 2018 Hystrix перешёл в maintenance mode, заменён на Resilience4j.

Ключевое правило Netflix: "Design for failure". Они намеренно ломают production (Chaos Monkey) чтобы убедиться, что Circuit Breaker работает.

Google -- Istio

Google создал Istio совместно с IBM и Lyft. Google использует Service Mesh внутренне для управления трафиком между тысячами сервисов. Istio стал де-факто стандартом Service Mesh в Kubernetes.

Shopify -- Adaptive Retries

Shopify реализовал "adaptive retries" -- количество повторов зависит от текущей нагрузки. При высокой нагрузке retries уменьшаются, чтобы не усугублять проблему. При низкой -- увеличиваются для максимальной надёжности.

Проверь себя

Какую роль играет Sidecar Proxy в Service Mesh?

Зачем нужен Jitter в стратегии Exponential Backoff?

Когда НЕ нужен Service Mesh?

Что такое Bulkhead Pattern?

Что происходит с Circuit Breaker в состоянии HALF-OPEN?