Service Discovery и Resilience Patterns
Зачем знать: В микросервисной архитектуре адреса сервисов меняются динамически — pod’ы переезжают, replicas масштабируются, ноды падают. Service discovery — это механизм, который позволяет клиентам находить актуальные backends. Resilience patterns (circuit breaker, retry, timeout, bulkhead) — это арсенал защиты от каскадных отказов. Middle Go-разработчик должен уметь настраивать gobreaker, понимать exponential backoff с jitter, knowing когда retry помогает, а когда добивает упавший downstream.
Содержание
Заголовок раздела «Содержание»- Концепция: service discovery основы
- Resilience patterns: circuit breaker, retry, timeout, bulkhead
- Gotchas
- Real cases
- Вопросы (25)
- Practice
- Источники
1. Концепция
Заголовок раздела «1. Концепция»1.1 Service discovery: проблема
Заголовок раздела «1.1 Service discovery: проблема»В статической инфраструктуре (один сервер, фиксированный IP) клиент конфигурируется хардкодом: payment.internal:8080. В микросервисной среде это не работает:
- Pod может перезапуститься на другой ноде.
- Auto-scaling добавляет/убирает replicas динамически.
- Blue-green deployment меняет endpoints.
- Multi-region — клиент должен знать ближайший instance.
Service discovery = механизм, чтобы клиент находил адреса актуальных healthy instances.
1.2 Client-side vs Server-side discovery
Заголовок раздела «1.2 Client-side vs Server-side discovery»Client-side discovery:
[Client] -> [Service Registry] (get list)[Client] -> [Service instance] (direct call)- Клиент сам выбирает instance (load balance).
- Примеры: Netflix Ribbon, Eureka, Consul.
- Pros: точный контроль балансировки.
- Cons: discovery logic в каждом клиенте.
Server-side discovery:
[Client] -> [Load Balancer / Proxy] -> [Service instance] ↑ [Service Registry]- Клиент бьёт LB, LB резолвит и балансирует.
- Примеры: Kubernetes Service + kube-proxy, AWS ALB, Envoy.
- Pros: client прост, не знает о discovery.
- Cons: extra hop, LB — точка отказа (если не HA).
В современной k8s-инфраструктуре чаще server-side через Service + iptables/IPVS.
1.3 DNS-based discovery
Заголовок раздела «1.3 DNS-based discovery»Простейший подход: DNS A/SRV records.
Kubernetes Service:
apiVersion: v1kind: Servicemetadata: name: payment namespace: prodspec: selector: app: payment ports: - port: 8080 targetPort: 8080DNS resolution: payment.prod.svc.cluster.local → ClusterIP. ClusterIP → kube-proxy → Pod IP. Round-robin.
SRV records (для портов):
_grpc._tcp.payment.prod.svc.cluster.local → priority weight port targetИспользуется gRPC client’ами для discovery.
Headless Service (clusterIP: None): DNS возвращает все IP подов напрямую. Клиент сам балансирует (Go gRPC, например).
Gotchas:
- DNS TTL: если 30s, при scaling client будет видеть старый список 30s.
- DNS не делает health checks (Pod в Service’е, но pod fails health — DNS вернёт).
- DNS caching внутри клиента (Go net/http resolver) может удерживать дольше TTL.
1.4 Consul
Заголовок раздела «1.4 Consul»HashiCorp Consul — popular service registry + health checks + KV.
Регистрация:
import "github.com/hashicorp/consul/api"
client, _ := api.NewClient(api.DefaultConfig())registration := &api.AgentServiceRegistration{ ID: "payment-1", Name: "payment", Port: 8080, Address: "10.0.0.15", Check: &api.AgentServiceCheck{ HTTP: "http://10.0.0.15:8080/health", Interval: "10s", Timeout: "1s", },}client.Agent().ServiceRegister(registration)Discovery:
services, _, _ := client.Health().Service("payment", "", true, nil) // passing=true → only healthyfor _, s := range services { fmt.Printf("%s:%d\n", s.Service.Address, s.Service.Port)}Features:
- Health checks (HTTP, TCP, gRPC, script).
- Multi-datacenter.
- KV store (config).
- DNS interface (можно использовать как DNS).
Used by: HashiCorp stack, multi-cloud setups.
1.5 etcd
Заголовок раздела «1.5 etcd»etcd — generic KV, не purpose-built для service discovery, но используется (через Kubernetes, через прямую запись).
cli, _ := clientv3.New(clientv3.Config{Endpoints: []string{"etcd:2379"}})// Регистрация с lease (TTL)lease, _ := cli.Grant(ctx, 10)cli.Put(ctx, "/services/payment/instance-1", "10.0.0.15:8080", clientv3.WithLease(lease.ID))// Renew через keepalivech, _ := cli.KeepAlive(ctx, lease.ID)go func() { for range ch {} }() // потребляем
// Discovery с watchresp, _ := cli.Get(ctx, "/services/payment/", clientv3.WithPrefix())// watch для real-time updateswatchCh := cli.Watch(ctx, "/services/payment/", clientv3.WithPrefix())for w := range watchCh { /* refresh local list */ }1.6 Eureka (Netflix)
Заголовок раздела «1.6 Eureka (Netflix)»Netflix OSS service registry. Был популярен в Java/Spring ecosystem. В Go менее популярен. Netflix постепенно мигрировал на Envoy + xDS.
1.7 Service Mesh (краткий обзор, глубже в файле 23)
Заголовок раздела «1.7 Service Mesh (краткий обзор, глубже в файле 23)»Service Mesh = инфраструктурный layer для service-to-service связи. Sidecar proxy на каждом pod’е (Envoy). Control plane (Istio, Linkerd) программирует sidecars.
Service discovery становится частью mesh: каждый pod бьёт в localhost sidecar, sidecar знает routes.
Pros: language-agnostic, observability, mTLS, traffic management. Cons: complexity, resource overhead.
1.8 Health checks
Заголовок раздела «1.8 Health checks»Discovery без health checks бесполезно. Типы:
- Liveness: жив ли процесс? (HTTP 200 на /healthz)
- Readiness: готов принимать трафик? (DB connection, cache warmed)
- Startup: ещё стартует? (длинный grace period)
В k8s — три probes:
livenessProbe: httpGet: { path: /healthz, port: 8080 } initialDelaySeconds: 15readinessProbe: httpGet: { path: /ready, port: 8080 }startupProbe: httpGet: { path: /startup, port: 8080 } failureThreshold: 30 # дать 30*10s = 5min на стартГадость: /ready не должен возвращать 503 на временные проблемы downstream. Иначе при проблеме одной зависимости весь сервис уходит из rotation → cascade outage.
2. Resilience Patterns
Заголовок раздела «2. Resilience Patterns»2.1 Circuit Breaker
Заголовок раздела «2.1 Circuit Breaker»Идея: если downstream сервис фейлится, перестать к нему обращаться на время. Дать ему “выздороветь”, не добивая нагрузкой. Клиенты получают быстрый fail вместо timeout.
State machine:
┌─── failures > threshold ───┐ │ ▼ [Closed] [Open] ▲ │ │ │ cooldown elapsed │ success ▼ └──── [Half-Open] ◀──────────┘ trial requests │ ├─ success → Closed └─ failure → Open- Closed: трафик проходит. Считаем failures. Если errors > threshold → Open.
- Open: все запросы фейлятся мгновенно (fail-fast). После cooldown → Half-Open.
- Half-Open: пропускаем ограниченное число trial requests. Если они успешны → Closed. Если фейлятся → Open.
Trip threshold:
- Consecutive failures:
5 failures in a row→ trip. - Error rate:
>50% errors in last 100 requests→ trip. - Adaptive: учитывая latency, throughput.
Cooldown period: обычно 30s-60s.
2.2 Go libraries для circuit breaker
Заголовок раздела «2.2 Go libraries для circuit breaker»sony/gobreaker (популярная, активно поддерживается):
import "github.com/sony/gobreaker"
cb := gobreaker.NewCircuitBreaker(gobreaker.Settings{ Name: "payment-service", MaxRequests: 3, // half-open: max requests Interval: 10 * time.Second, // reset counter в Closed state Timeout: 30 * time.Second, // Open → Half-Open ReadyToTrip: func(c gobreaker.Counts) bool { failureRatio := float64(c.TotalFailures) / float64(c.Requests) return c.Requests >= 10 && failureRatio >= 0.5 }, OnStateChange: func(name string, from, to gobreaker.State) { log.Printf("CB %s: %s -> %s", name, from, to) },})
result, err := cb.Execute(func() (any, error) { return httpCall(ctx)})afex/hystrix-go (deprecated, не используйте новых проектах). Hystrix как библиотека от Netflix decommissioned в 2018.
sentinel-golang (Alibaba) — adaptive: учитывает QPS, latency, не только error rate. Полезен для high-load scenarios.
failsafe-go — общий фреймворк (CB + retry + timeout в одной библиотеке).
2.3 Retry с exponential backoff
Заголовок раздела «2.3 Retry с exponential backoff»Без backoff: retry сразу → DDoS upstream. С linear: 1s, 2s, 3s… — мало гибко.
Exponential backoff:
attempt N: delay = base * 2^(N-1)1, 2, 4, 8, 16, ...С capping: min(base * 2^N, max_delay).
Jitter: добавить случайное возмущение, чтобы избежать thundering herd при синхронных retries.
Типы jitter (AWS Architecture Blog):
- Full jitter:
delay = random(0, base * 2^N). Лучший по distribution. - Equal jitter:
delay = base * 2^(N-1) + random(0, base * 2^(N-1)). - Decorrelated jitter:
delay = random(base, prev_delay * 3). Хорош для длинных retry.
Go library: cenkalti/backoff/v4:
import "github.com/cenkalti/backoff/v4"
b := backoff.NewExponentialBackOff()b.InitialInterval = 100 * time.Millisecondb.MaxInterval = 30 * time.Secondb.MaxElapsedTime = 2 * time.Minuteb.RandomizationFactor = 0.5
operation := func() error { resp, err := http.Get(url) if err != nil { return err // retried } if resp.StatusCode == 503 { return errors.New("service unavailable") // retried } if resp.StatusCode >= 400 && resp.StatusCode < 500 { return backoff.Permanent(errors.New("client error")) // not retried } return nil}
if err := backoff.Retry(operation, b); err != nil { return err}backoff.Permanent — обёртка для non-retriable error (4xx обычно).
2.4 Что retry’ить, что нет
Заголовок раздела «2.4 Что retry’ить, что нет»Retry:
- 502, 503, 504 (transient server errors)
- Network errors (connection refused, timeout)
- 429 (rate limited) — но с уважением
Retry-After - gRPC: UNAVAILABLE, DEADLINE_EXCEEDED (с осторожностью)
Не retry:
- 4xx client errors (400, 401, 403, 404, 422)
- Operation already attempted with side effect (без idempotency-key)
- Context cancelled (клиент уже отказался)
2.5 Timeouts
Заголовок раздела «2.5 Timeouts»Без timeout: запрос может висеть бесконечно → исчерпание goroutines / connections.
Layered timeouts:
Client (3s) → API Gateway (5s) → Service A (10s) → DB (15s)⚠️ Это анти-паттерн! Timeout снаружи должен быть больше, чем все вложенные. Иначе:
- Клиент таймаутится на 3s.
- Сервис продолжает работать ещё 12s, потребляя ресурсы.
- Cascade: DB запрос продолжается даже после смерти клиента.
Правильно:
Client (15s) → API Gateway (12s) → Service A (8s) → DB (5s)Каждый внутренний timeout < внешнего, оставляя время на retry/processing.
Context propagation в Go:
func handler(w http.ResponseWriter, r *http.Request) { ctx, cancel := context.WithTimeout(r.Context(), 5*time.Second) defer cancel() result, err := backend.Call(ctx) // backend увидит deadline и сам прервётся ...}Per-attempt vs total:
- Per-attempt: каждая попытка retry имеет свой timeout (например, 2s).
- Total: общее время на всю операцию (например, 30s включая все retries).
Лучше комбинировать: per-attempt 2s + total 10s + max 5 retries.
2.6 Bulkhead pattern
Заголовок раздела «2.6 Bulkhead pattern»Идея: изолировать ресурсы между потребителями. Если одна группа исчерпает свой пул, другие продолжат работать.
Аналогия: корабль с водонепроницаемыми отсеками. Пробоина в одном отсеке не топит корабль.
Реализация:
Separate goroutine pools:
type Bulkhead struct { sem chan struct{}}
func New(maxConcurrent int) *Bulkhead { return &Bulkhead{sem: make(chan struct{}, maxConcurrent)}}
func (b *Bulkhead) Do(ctx context.Context, fn func() error) error { select { case b.sem <- struct{}{}: defer func() { <-b.sem }() return fn() case <-ctx.Done(): return ctx.Err() }}
// Использование:paymentBulkhead := New(10) // max 10 concurrentnotificationBulkhead := New(50)
paymentBulkhead.Do(ctx, func() error { return paymentCall() })notificationBulkhead.Do(ctx, func() error { return notifyCall() })Separate connection pools per dependency:
paymentDB, _ := sql.Open("postgres", ...)paymentDB.SetMaxOpenConns(10)
reportingDB, _ := sql.Open("postgres", ...)reportingDB.SetMaxOpenConns(5)Reporting запросы (тяжёлые) не съедят connections для payment (критичных).
HTTP client per dependency:
paymentClient := &http.Client{ Transport: &http.Transport{ MaxIdleConns: 20, MaxIdleConnsPerHost: 10, }, Timeout: 3 * time.Second,}2.7 Rate limiting (client-side)
Заголовок раздела «2.7 Rate limiting (client-side)»Чтобы не убить downstream — ограничить свой исходящий QPS.
Token bucket:
import "golang.org/x/time/rate"
limiter := rate.NewLimiter(100, 20) // 100 req/s, burst 20
if err := limiter.Wait(ctx); err != nil { return err}// call downstreamAdaptive rate limiting: замедляться при росте latency.
Reference: Netflix’s adaptive concurrency limits (https://github.com/Netflix/concurrency-limits).
2.8 Fallback
Заголовок раздела «2.8 Fallback»Default value:
result, err := cb.Execute(func() (any, error) { return fetchRecommendations(ctx, userID)})if err != nil { result = popularItemsFallback() // top-10 без персонализации}Cache fallback:
data, err := fetchFromAPI(ctx)if err != nil { data = cache.GetStale(key) // stale data lieux of error metrics.IncrementCacheStale()}Degraded service: временно отключить feature.
⚠️ Fallback должен быть safe: не должен скрывать критические ошибки. Если payment fails — не показывай “ОК”, показывай ошибку.
2.9 Combining patterns: robust client
Заголовок раздела «2.9 Combining patterns: robust client»type Client struct { cb *gobreaker.CircuitBreaker backoff backoff.BackOff bulkhead *Bulkhead timeout time.Duration}
func (c *Client) Call(ctx context.Context, req Request) (Response, error) { return c.bulkhead.Do(ctx, func() (Response, error) { var resp Response op := func() error { attemptCtx, cancel := context.WithTimeout(ctx, c.timeout) defer cancel() r, err := c.cb.Execute(func() (any, error) { return c.httpCall(attemptCtx, req) }) if err != nil { return err } resp = r.(Response) return nil } if err := backoff.Retry(op, c.backoff); err != nil { return Response{}, err } return resp, nil })}Порядок (изнутри наружу): timeout → CB → retry → bulkhead.
2.10 Observability
Заголовок раздела «2.10 Observability»Без metrics resilience patterns слепы. Метрики:
circuit_breaker_state{name}(gauge: 0=closed, 1=open, 2=half-open)circuit_breaker_trips_total{name}(counter)retry_attempts_total{name}(counter)bulkhead_rejected_total{name}(counter)request_duration_seconds{name, status}(histogram)client_inflight_requests{name}(gauge)
Используя Prometheus + Grafana, можно увидеть, когда CB разомкнут, когда retries растут — это early warning систем.
3. Gotchas
Заголовок раздела «3. Gotchas»⚠️ Retry без идемпотентности → дубли. Если POST /charge упал на network — retry создаст второй платёж. Решение: Idempotency-Key.
⚠️ Retry storms. Все клиенты ретраят одновременно после ошибки → upstream под двойной нагрузкой. Решение: jitter, circuit breaker.
⚠️ Cascade timeout. Внешний timeout < внутренний → ресурсы тратятся после смерти client. Всегда: timeout внутри < timeout снаружи.
⚠️ Circuit breaker per-method, не per-service. Если “GET /users” фейлится из-за бага в этом endpoint, а “GET /orders” работает — нельзя глобально открыть CB. Раздельные CB по endpoint.
⚠️ CB и health checks конфликтуют. Если health check считает downstream down → readiness 503 → pod уходит из rotation → cascade. Лучше: CB локально, health check глобально.
⚠️ DNS caching в Go. net.LookupHost кэширует только если используется Resolver. http.Client может держать connection до закрытия Keep-Alive, не реагируя на изменение DNS.
⚠️ HTTP Keep-Alive vs scaling. При scale-down старые connections могут продолжать идти на удалённый pod до Connection: close. Решение: graceful shutdown с draining (см. файл по shutdown).
⚠️ Health check returns 200 при перегрузке. Pod не способен обрабатывать, но healthz отвечает. Решение: readiness учитывает inflight requests, queue depth.
⚠️ Bulkhead не help при shared downstream. Если бекенд DB один, изоляция goroutine pools не поможет — DB всё равно перегружена. Изолируй downstream через шарды.
⚠️ Fallback скрывает баги. Если cache fallback всегда срабатывает, а fresh data никогда не приходит — никто не заметит. Метрика fallback rate критична.
⚠️ backoff.Permanent не работает с context. Если context cancelled, backoff остановится только после следующей попытки. Используй WithContext.
⚠️ DNS A record и SRV record разные. A → IP. SRV → host+port+weight. gRPC по умолчанию использует A. Для weighted routing нужен SRV или xDS.
⚠️ Eureka и k8s — не очень совместимы. Eureka client регистрируется сам, k8s регистрирует через kubelet+API. Двойная регистрация → путаница.
⚠️ Service mesh + retry → multiplication. Если retry на client side И в Envoy — total retries умножаются. 3 × 3 = 9 попыток на каждый запрос.
⚠️ Idempotency для gRPC streaming. Retry stream сложен: какая часть была обработана? Лучше — designinger для idempotency без retry.
4. Real cases
Заголовок раздела «4. Real cases»Netflix Hystrix. Java библиотека для circuit breaker (decomm. 2018). Идеи живы в Sentinel, Resilience4j. Hystrix-Go — порт, тоже устарел. Netflix мигрировал на adaptive concurrency limits (https://github.com/Netflix/concurrency-limits).
Alibaba Sentinel. Adaptive flow control: учитывает QPS, latency, system load. Sentinel-Golang — Go версия. Используется в Alibaba ecosystem, набирает популярность вне.
Envoy / Istio outlier detection. Service mesh CB: Envoy исключает upstream host из load balancing на основе consecutive failures / error rate. Конфигурируется через DestinationRule в Istio.
AWS SDK retry.
AWS Go SDK v2 по умолчанию использует exponential backoff с jitter, 3 retries для 5xx и throttling errors. Кастомизируется через RetryMaxAttempts, RetryMode (standard, adaptive).
Stripe API retries. Stripe рекомендует 5 retries с jittered exponential backoff. После 5 — alert. Обязательно с Idempotency-Key для POST.
GitHub API rate limit.
При hit rate limit, GitHub возвращает 403 + X-RateLimit-Reset header. Client должен ждать до reset, не ретраить immediately.
Cloudflare retry on 525/526 (SSL errors). Cloudflare ретраит origin до 3 раз при timeout. Если ваш origin упал — CF не сделает worse.
Yandex.Cloud Load Balancer health checks. HTTP/TCP/gRPC checks. Если 3 fails подряд → unhealthy. Endpoint удаляется из rotation. Возврат в rotation после 3 успехов.
Kubernetes kube-proxy и iptables/IPVS. Server-side discovery: ClusterIP — виртуальный, kube-proxy создаёт iptables rules. При DNAT — выбирает pod. Health check не делается kube-proxy сам — он смотрит на Endpoints/EndpointSlice, который обновляется на основе readiness probe.
Consul Connect + Envoy. Consul как service registry + auto-generate Envoy sidecar config. mTLS, observability, routing.
Linkerd vs Istio. Linkerd — простой service mesh (Rust data plane). Istio — feature-rich (Envoy data plane). Linkerd проще, Istio мощнее. Linkerd 2.x — light-weight, рекомендуется для большинства команд.
Discord — gRPC retries problem. Discord описывал в blog’е как избыточные retries усиливали outage: каждый сервис ретраил, каскадный rate увеличивался 3-9x. Решение: budget retries (max ratio), CB, jitter.
Yandex MeBro (внутренний) — adaptive timeouts. Yandex использует адаптивные таймауты на основе p99 latency downstream. При росте p99 — timeout увеличивается, но total deadline ограничен.
Datadog StatsD — UDP fail-open. Datadog Agent на UDP — если упал, application не падает (UDP fire-and-forget). Это fail-open design: monitoring не критично для бизнеса.
Twitter Finagle — Service interface. Finagle (Scala) предоставила unified Service interface с composable filters: retry, timeout, CB. Inspirede многих в Go.
Slack — health checks для WebSocket. WebSocket pings каждые 30s. Если 2 pings missed — reconnect. Без этого reverse-proxy timeouts закрывают idle connections.
Stripe — adaptive rate limiting.
Stripe API возвращает 429 с Retry-After. Customers следуют. Внутри Stripe — adaptive rate limit per API key based on error rate.
OpenTelemetry в Go — semantic conventions.
OTel standardizes attribute names для traces, metrics. http.method, http.status_code, db.system, messaging.system. Цель: cross-tool compatibility.
Cloudflare — graceful degradation. При DDoS атаке Cloudflare degrades to “Under Attack Mode”: challenge pages для unknown clients, allow known. Бизнес-логика не падает.
Kubernetes EndpointSlices. Раньше — Endpoints (один resource per service). При больших scale — slow updates. EndpointSlices (1.21+) — sharded, scales до 100K endpoints.
gRPC service config / xDS. gRPC поддерживает xDS protocol (Envoy data plane API). Control plane (Istio) программирует service discovery, load balancing, retries в gRPC client прямо. Без service mesh sidecar.
Дополнительные patterns
Заголовок раздела «Дополнительные patterns»Hedged requests. Отправить 2 запроса параллельно после короткого timeout. Использовать ответ от первого вернувшегося. Снижает tail latency (p99) ценой удвоенной нагрузки.
Speculative execution. Похоже на hedged. Использует на batch jobs (MapReduce): запустить duplicate task если оригинал медленный.
Load shedding. Отбрасывать запросы при перегрузке. По QoS: low-priority → drop первым. По server health (CPU > 80%).
Backpressure. Сигнализировать upstream “слишком много для меня”. HTTP 429, gRPC ResourceExhausted. Upstream должен замедлиться.
5. Вопросы
Заголовок раздела «5. Вопросы»Q1: Что такое service discovery? A: Механизм для динамического resolving адресов сервисов в распределённой системе. Клиент находит healthy instances по logical имени.
Q2: Client-side vs server-side discovery — в чём разница? A: Client-side — клиент знает registry, сам выбирает instance (Eureka, Consul). Server-side — клиент бьёт LB, LB резолвит (k8s Service, AWS ALB).
Q3: Как работает DNS-based discovery в Kubernetes? A: Service создаёт ClusterIP. CoreDNS возвращает ClusterIP для имени. kube-proxy создаёт iptables/IPVS rules: ClusterIP → pod IP. Round-robin.
Q4: Какие проблемы у DNS-based discovery? A: TTL caching (медленное обновление), нет встроенных health checks (доверяется readiness), Go клиент может кэшировать connection и не реагировать на изменения.
Q5: Что такое headless Service в k8s? A: Service с clusterIP: None. DNS возвращает IP всех подов напрямую. Клиент сам балансирует. Используется gRPC, statefulsets.
Q6: Зачем readiness probe помимо liveness? A: Liveness — рестарт pod’а при крахе. Readiness — временный выход из rotation без рестарта (например, DB connection потеряна). Разные lifecycle.
Q7: Что делает circuit breaker? A: Защищает downstream от перегрузки. При росте errors — открывает CB, мгновенно фейлит запросы. После cooldown — half-open, пробные запросы. Защита от cascade failures.
Q8: Опишите states circuit breaker. A: Closed (норма, пропускает), Open (фейлит мгновенно), Half-Open (trial requests). Closed → Open при превышении порога. Open → Half-Open после cooldown. Half-Open → Closed при успехе, → Open при failure.
Q9: Какую Go библиотеку использовать для CB?
A: sony/gobreaker — стандарт. sentinel-golang для adaptive control. afex/hystrix-go deprecated.
Q10: Зачем jitter в retry backoff? A: Чтобы избежать thundering herd: все клиенты ретраят синхронно после ошибки. Jitter (random delay) распределяет атаки во времени.
Q11: Что такое full jitter?
A: delay = random(0, base * 2^N). Полностью случайный delay в диапазоне 0 до экспоненциального base. Лучший по равномерному distribution (AWS recommendation).
Q12: Какие ошибки нужно ретраить? A: 5xx server errors, network errors (connection refused, timeout), 429 с уважением Retry-After. Не ретраить: 4xx client errors, context cancelled, permanent failures.
Q13: Почему опасен retry без idempotency?
A: При POST /charge upstream может уже обработать запрос, но ответ потерялся. Retry создаст второй платёж. Решение: Idempotency-Key.
Q14: Что такое cascade timeout? A: Внешний timeout меньше внутреннего → клиент таймаутится, но сервис продолжает работать, тратя ресурсы. Правильно: timeout снаружи > timeout внутри.
Q15: Per-attempt vs total timeout? A: Per-attempt — timeout на одну попытку. Total — на всю операцию включая retries. Лучше комбинировать: short per-attempt + medium total + max retries.
Q16: Что такое bulkhead pattern? A: Изоляция ресурсов между потребителями. Например, separate goroutine pools для разных downstream. Failure одного не съест ресурсы для других.
Q17: Как реализовать bulkhead в Go? A: Через semaphore (buffered channel) с capacity. Или отдельные http.Client с ограниченным MaxIdleConnsPerHost.
Q18: Что такое rate limiting client-side?
A: Ограничение исходящего QPS, чтобы не убить downstream. Через token bucket (golang.org/x/time/rate).
Q19: Adaptive rate limiting — что это? A: Динамическое снижение QPS при росте latency или error rate downstream. Netflix’s concurrency-limits, Sentinel adaptive flow control.
Q20: Что такое fallback? A: Замещающее поведение при failure. Cached value, default response, degraded service. Должен быть safe — не скрывать critical errors.
Q21: Как комбинировать resilience patterns? A: Снаружи внутрь: bulkhead → retry → CB → timeout → request. Каждый слой защищает по-своему.
Q22: Какие метрики важны для resilience? A: CB state, trips count, retry attempts, bulkhead rejections, request duration histogram, inflight requests, error rate per dependency.
Q23: Что лучше: client-side или server-side load balancing? A: Зависит. Client-side точнее (знает latency к instances), но сложнее. Server-side проще, но extra hop. В k8s — обычно server-side через Service, или mesh.
Q24: Service mesh всегда нужен? A: Нет. Для small system — overkill. Для large microservices с mTLS, observability needs — да. Tradeoff: features vs complexity vs overhead.
Q25: Чем Linkerd отличается от Istio? A: Linkerd — light-weight, Rust data plane, простая установка. Istio — feature-rich, Envoy proxy, более сложная конфигурация. Linkerd для большинства, Istio для enterprise needs.
Q26: Что такое hedged requests? A: Отправить duplicate request после короткого timeout (например, p95). Использовать первый вернувшийся ответ. Снижает tail latency ценой удвоенной нагрузки.
Q27: Load shedding — что и зачем? A: Отбрасывание запросов при перегрузке сервиса. Priority-based: low-priority — drop первыми. Защита от полного отказа.
Q28: Что такое backpressure? A: Сигнал от downstream к upstream “слишком много”. HTTP 429 + Retry-After, gRPC ResourceExhausted. Upstream должен замедлиться, не игнорировать.
Q29: gRPC vs HTTP retry policy — разница? A: gRPC service config поддерживает retry policy в client config (max attempts, backoff). HTTP — обычно через client middleware. gRPC native retry — более integrated.
Q30: Что такое EndpointSlices в k8s? A: Раньше Endpoints — один resource per service. При scale (1000+ pods) — slow updates. EndpointSlices (1.21+) — sharded by readiness/topology, scales до 100K endpoints.
6. Practice
Заголовок раздела «6. Practice»-
Реализуй circuit breaker на gobreaker. Симулируй downstream с 50% failures. Покажи трип CB, recovery.
-
Сравни retry strategies. Constant, linear, exponential, exp + jitter. Запусти 1000 clients, downstream с 30% errors. Замерь: тайл лагающие, total time, нагрузка на downstream.
-
Реализуй bulkhead с semaphore. Покажи изоляцию: один пул исчерпан, другой работает.
-
Service discovery через Consul. Зарегистрируй 3 instances, запусти client, потуши 1 instance — client переключается.
-
DNS discovery в k8s. Запусти Deployment с 3 replicas + Service. Сделай curl в loop из другого pod’а. Покажи round-robin.
-
Test cascade timeouts. Клиент 3s, сервис 10s. Покажи, что после клиентского таймаута сервис продолжает работать. Затем правильно: client 15s, сервис 8s.
-
Adaptive rate limiter. Реализуй: при росте latency p99 снижай rate. Сравни с фиксированным.
-
Health check endpoint. Реализуй /healthz (liveness) и /ready (readiness с проверкой DB). Симулируй DB down — pod уходит из rotation.
-
Combine CB + retry + timeout. Robust client. Покажи разницу с naive client при 30% failure rate.
-
gRPC retry policy. Настрой
retryPolicyв gRPC service config. Сравни с client-side retry.
7. Источники
Заголовок раздела «7. Источники»- Michael Nygard. “Release It! Design and Deploy Production-Ready Software.” 2nd ed., Pragmatic Bookshelf, 2018. — must read, библия resilience.
- Martin Fowler. “CircuitBreaker.” https://martinfowler.com/bliki/CircuitBreaker.html
- AWS Architecture Blog. “Exponential Backoff And Jitter.” https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
- Netflix Tech Blog. “Performance Under Load.” (adaptive concurrency).
- sony/gobreaker docs. https://github.com/sony/gobreaker
- cenkalti/backoff. https://github.com/cenkalti/backoff
- Kubernetes docs. “Services, Load Balancing, and Networking.” https://kubernetes.io/docs/concepts/services-networking/
- Istio Documentation. “Traffic Management.” https://istio.io/docs/concepts/traffic-management/
- Linkerd Documentation. https://linkerd.io/2/overview/
- HashiCorp Consul docs. https://www.consul.io/docs
- Discord Engineering. “How Discord scaled Elixir to 5,000,000 concurrent users.” (mentions retry storms).
- Sam Newman. “Building Microservices.” 2nd ed., O’Reilly, 2021.