Перейти к содержимому

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.

  1. Концепция: service discovery основы
  2. Resilience patterns: circuit breaker, retry, timeout, bulkhead
  3. Gotchas
  4. Real cases
  5. Вопросы (25)
  6. Practice
  7. Источники

В статической инфраструктуре (один сервер, фиксированный IP) клиент конфигурируется хардкодом: payment.internal:8080. В микросервисной среде это не работает:

  • Pod может перезапуститься на другой ноде.
  • Auto-scaling добавляет/убирает replicas динамически.
  • Blue-green deployment меняет endpoints.
  • Multi-region — клиент должен знать ближайший instance.

Service discovery = механизм, чтобы клиент находил адреса актуальных healthy instances.

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.

Простейший подход: DNS A/SRV records.

Kubernetes Service:

apiVersion: v1
kind: Service
metadata:
name: payment
namespace: prod
spec:
selector:
app: payment
ports:
- port: 8080
targetPort: 8080

DNS 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.

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 healthy
for _, 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.

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 через keepalive
ch, _ := cli.KeepAlive(ctx, lease.ID)
go func() { for range ch {} }() // потребляем
// Discovery с watch
resp, _ := cli.Get(ctx, "/services/payment/", clientv3.WithPrefix())
// watch для real-time updates
watchCh := cli.Watch(ctx, "/services/payment/", clientv3.WithPrefix())
for w := range watchCh { /* refresh local list */ }

Netflix OSS service registry. Был популярен в Java/Spring ecosystem. В Go менее популярен. Netflix постепенно мигрировал на Envoy + xDS.

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.

Discovery без health checks бесполезно. Типы:

  • Liveness: жив ли процесс? (HTTP 200 на /healthz)
  • Readiness: готов принимать трафик? (DB connection, cache warmed)
  • Startup: ещё стартует? (длинный grace period)

В k8s — три probes:

livenessProbe:
httpGet: { path: /healthz, port: 8080 }
initialDelaySeconds: 15
readinessProbe:
httpGet: { path: /ready, port: 8080 }
startupProbe:
httpGet: { path: /startup, port: 8080 }
failureThreshold: 30 # дать 30*10s = 5min на старт

Гадость: /ready не должен возвращать 503 на временные проблемы downstream. Иначе при проблеме одной зависимости весь сервис уходит из rotation → cascade outage.


Идея: если 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.

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 в одной библиотеке).

Без 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.Millisecond
b.MaxInterval = 30 * time.Second
b.MaxElapsedTime = 2 * time.Minute
b.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 обычно).

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 (клиент уже отказался)

Без 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.

Идея: изолировать ресурсы между потребителями. Если одна группа исчерпает свой пул, другие продолжат работать.

Аналогия: корабль с водонепроницаемыми отсеками. Пробоина в одном отсеке не топит корабль.

Реализация:

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 concurrent
notificationBulkhead := 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,
}

Чтобы не убить 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 downstream

Adaptive rate limiting: замедляться при росте latency.

Reference: Netflix’s adaptive concurrency limits (https://github.com/Netflix/concurrency-limits).

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 — не показывай “ОК”, показывай ошибку.

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.

Без 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 систем.


⚠️ 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.


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.

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 должен замедлиться.


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.


  1. Реализуй circuit breaker на gobreaker. Симулируй downstream с 50% failures. Покажи трип CB, recovery.

  2. Сравни retry strategies. Constant, linear, exponential, exp + jitter. Запусти 1000 clients, downstream с 30% errors. Замерь: тайл лагающие, total time, нагрузка на downstream.

  3. Реализуй bulkhead с semaphore. Покажи изоляцию: один пул исчерпан, другой работает.

  4. Service discovery через Consul. Зарегистрируй 3 instances, запусти client, потуши 1 instance — client переключается.

  5. DNS discovery в k8s. Запусти Deployment с 3 replicas + Service. Сделай curl в loop из другого pod’а. Покажи round-robin.

  6. Test cascade timeouts. Клиент 3s, сервис 10s. Покажи, что после клиентского таймаута сервис продолжает работать. Затем правильно: client 15s, сервис 8s.

  7. Adaptive rate limiter. Реализуй: при росте latency p99 снижай rate. Сравни с фиксированным.

  8. Health check endpoint. Реализуй /healthz (liveness) и /ready (readiness с проверкой DB). Симулируй DB down — pod уходит из rotation.

  9. Combine CB + retry + timeout. Robust client. Покажи разницу с naive client при 30% failure rate.

  10. gRPC retry policy. Настрой retryPolicy в gRPC service config. Сравни с client-side retry.


  1. Michael Nygard. “Release It! Design and Deploy Production-Ready Software.” 2nd ed., Pragmatic Bookshelf, 2018. — must read, библия resilience.
  2. Martin Fowler. “CircuitBreaker.” https://martinfowler.com/bliki/CircuitBreaker.html
  3. AWS Architecture Blog. “Exponential Backoff And Jitter.” https://aws.amazon.com/blogs/architecture/exponential-backoff-and-jitter/
  4. Netflix Tech Blog. “Performance Under Load.” (adaptive concurrency).
  5. sony/gobreaker docs. https://github.com/sony/gobreaker
  6. cenkalti/backoff. https://github.com/cenkalti/backoff
  7. Kubernetes docs. “Services, Load Balancing, and Networking.” https://kubernetes.io/docs/concepts/services-networking/
  8. Istio Documentation. “Traffic Management.” https://istio.io/docs/concepts/traffic-management/
  9. Linkerd Documentation. https://linkerd.io/2/overview/
  10. HashiCorp Consul docs. https://www.consul.io/docs
  11. Discord Engineering. “How Discord scaled Elixir to 5,000,000 concurrent users.” (mentions retry storms).
  12. Sam Newman. “Building Microservices.” 2nd ed., O’Reilly, 2021.