Проблема сетевого взаимодействия
В микросервисах каждый вызов -- сетевой запрос. Сеть ненадёжна: пакеты теряются, сервисы падают, 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 уменьшаются, чтобы не усугублять проблему. При низкой -- увеличиваются для максимальной надёжности.