Senior Go Interview Prep - Core Go: https://go.vbloher.org/docs/01-core-go/ - Механика defer в Go: https://go.vbloher.org/docs/01-core-go/defer/ - Встраивание структур и интерфейсов (Embedding): https://go.vbloher.org/docs/01-core-go/embedding/ - Ошибки в Go: error, wrapping, errors.Is/As/Join: https://go.vbloher.org/docs/01-core-go/errors/ - Дженерики в Go (1.18+): https://go.vbloher.org/docs/01-core-go/generics/ - Интерфейсы в Go: https://go.vbloher.org/docs/01-core-go/interfaces/ - Устройство map в Go: https://go.vbloher.org/docs/01-core-go/maps/ - panic / recover: механика, раскрутка стека и runtime-паники: https://go.vbloher.org/docs/01-core-go/panic-recover/ - Указатели в Go: https://go.vbloher.org/docs/01-core-go/pointers/ - Рефлексия в Go (reflect): https://go.vbloher.org/docs/01-core-go/reflection/ - Внутреннее устройство слайсов в Go: https://go.vbloher.org/docs/01-core-go/slices/ - Строки, руны и байты в Go: https://go.vbloher.org/docs/01-core-go/strings-runes-bytes/ - Система типов Go: defined types, alignment, memory layout: https://go.vbloher.org/docs/01-core-go/type-system/ - Concurrency: https://go.vbloher.org/docs/02-concurrency/ - sync/atomic: https://go.vbloher.org/docs/02-concurrency/atomic/ - Буферизованные vs небуферизованные каналы: https://go.vbloher.org/docs/02-concurrency/buffered-unbuffered/ - Канал vs Mutex: когда что выбрать: https://go.vbloher.org/docs/02-concurrency/channel-vs-mutex/ - Каналы: устройство hchan: https://go.vbloher.org/docs/02-concurrency/channels/ - Утечки горутин, дедлоки, livelock, starvation: https://go.vbloher.org/docs/02-concurrency/common-leaks-deadlocks/ - sync.Cond: https://go.vbloher.org/docs/02-concurrency/cond/ - context: https://go.vbloher.org/docs/02-concurrency/context/ - Горутины: жизненный цикл, стоимость, стек: https://go.vbloher.org/docs/02-concurrency/goroutines-lifecycle/ - sync.Mutex и sync.RWMutex: https://go.vbloher.org/docs/02-concurrency/mutex-rwmutex/ - sync.Once: https://go.vbloher.org/docs/02-concurrency/once/ - Паттерны конкурентности: https://go.vbloher.org/docs/02-concurrency/patterns/ - Race Detector (гонки данных и -race): https://go.vbloher.org/docs/02-concurrency/race-detector/ - Планировщик GMP: https://go.vbloher.org/docs/02-concurrency/scheduler-gmp/ - select: https://go.vbloher.org/docs/02-concurrency/select/ - sync.WaitGroup: https://go.vbloher.org/docs/02-concurrency/waitgroup/ - Runtime и память: https://go.vbloher.org/docs/03-runtime-memory/ - Паттерны аллокаций и снижение давления на GC: https://go.vbloher.org/docs/03-runtime-memory/allocation-patterns/ - Escape Analysis: когда переменная убегает в кучу: https://go.vbloher.org/docs/03-runtime-memory/escape-analysis/ - Сборщик мусора Go: concurrent tri-color mark-sweep: https://go.vbloher.org/docs/03-runtime-memory/gc/ - Тюнинг GC: GOGC и GOMEMLIMIT: https://go.vbloher.org/docs/03-runtime-memory/gogc-gomemlimit/ - GOMAXPROCS: параллелизм планировщика и проблема контейнеров: https://go.vbloher.org/docs/03-runtime-memory/gomaxprocs/ - Утечки горутин (goroutine leaks): https://go.vbloher.org/docs/03-runtime-memory/goroutine-leaks/ - Утечки памяти в Go (несмотря на GC): https://go.vbloher.org/docs/03-runtime-memory/memory-leaks/ - Модель памяти Go (Go Memory Model): happens-before и синхронизация: https://go.vbloher.org/docs/03-runtime-memory/memory-model/ - pprof: профилирование CPU, памяти и блокировок в Go: https://go.vbloher.org/docs/03-runtime-memory/pprof/ - Execution Tracer и runtime/trace: тайминги вместо агрегатов: https://go.vbloher.org/docs/03-runtime-memory/runtime-tracing/ - Стек vs Куча: где живут данные в Go: https://go.vbloher.org/docs/03-runtime-memory/stack-vs-heap/ - Тестирование: https://go.vbloher.org/docs/04-testing/ - testify, assert/require и golden files: https://go.vbloher.org/docs/04-testing/assertions-testify/ - Бенчмарки в Go: https://go.vbloher.org/docs/04-testing/benchmarks/ - Покрытие, -race и флаки-тесты: https://go.vbloher.org/docs/04-testing/coverage-race/ - Нативный fuzzing в Go (1.18+): https://go.vbloher.org/docs/04-testing/fuzzing/ - Интеграционные тесты, testcontainers-go, TestMain: https://go.vbloher.org/docs/04-testing/integration-testcontainers/ - Моки, стабы и тестируемость: https://go.vbloher.org/docs/04-testing/mocks/ - Table-driven тесты, subtests и параллельность: https://go.vbloher.org/docs/04-testing/table-driven/ - Backend: https://go.vbloher.org/docs/05-backend/ - Аутентификация и авторизация: AuthN/AuthZ, сессии vs токены, RBAC/ABAC, API keys, mTLS, секреты: https://go.vbloher.org/docs/05-backend/auth-authz/ - Graceful Shutdown HTTP/gRPC сервера в Go: https://go.vbloher.org/docs/05-backend/graceful-shutdown/ - gRPC: типы RPC, интерсепторы, контекст, метаданные, error model: https://go.vbloher.org/docs/05-backend/grpc/ - JWT (JSON Web Token): https://go.vbloher.org/docs/05-backend/jwt/ - Middleware-паттерн в Go: https://go.vbloher.org/docs/05-backend/middleware/ - net/http: Server, Handler, ServeMux, таймауты, Client и контекст: https://go.vbloher.org/docs/05-backend/net-http/ - OAuth2: роли, grant types, OIDC, токены и типовые ошибки: https://go.vbloher.org/docs/05-backend/oauth2/ - OpenAPI/Swagger, code generation, contract-first vs code-first, валидация: https://go.vbloher.org/docs/05-backend/openapi/ - Protocol Buffers: схемы, wire format, эволюция и совместимость: https://go.vbloher.org/docs/05-backend/protobuf/ - REST: принципы, версионирование, идемпотентность, статусы, пагинация, ошибки: https://go.vbloher.org/docs/05-backend/rest/ - Сети и протоколы: https://go.vbloher.org/docs/06-networking/ - Пулы соединений: http.Transport, БД, утечки: https://go.vbloher.org/docs/06-networking/connection-pooling/ - DNS: записи, резолвинг, кэширование, DNS в Go: https://go.vbloher.org/docs/06-networking/dns/ - Версии HTTP: 1.1, 2, 3: https://go.vbloher.org/docs/06-networking/http-versions/ - TCP/IP: модель, транспорт и что важно бэкендеру: https://go.vbloher.org/docs/06-networking/tcp-ip/ - TLS: handshake, сертификаты, mTLS, производительность: https://go.vbloher.org/docs/06-networking/tls/ - UDP и надёжность поверх UDP: https://go.vbloher.org/docs/06-networking/udp/ - WebSocket: upgrade, фреймы, масштабирование: https://go.vbloher.org/docs/06-networking/websocket/ - Базы данных: https://go.vbloher.org/docs/07-databases/ - Пул соединений к PostgreSQL в Go: database/sql, pgx, pgxpool, PgBouncer: https://go.vbloher.org/docs/07-databases/connection-pooling-pgx/ - Взаимоблокировки (Deadlocks) в PostgreSQL: https://go.vbloher.org/docs/07-databases/deadlocks/ - Индексы в PostgreSQL: https://go.vbloher.org/docs/07-databases/indexes/ - Уровни изоляции транзакций в PostgreSQL: https://go.vbloher.org/docs/07-databases/isolation-levels/ - MVCC в PostgreSQL: версии строк, видимость, VACUUM и bloat: https://go.vbloher.org/docs/07-databases/mvcc/ - Обзор NoSQL и Redis: https://go.vbloher.org/docs/07-databases/nosql-redis/ - Партиционирование таблиц в PostgreSQL: https://go.vbloher.org/docs/07-databases/partitioning/ - Архитектура PostgreSQL: https://go.vbloher.org/docs/07-databases/postgresql-architecture/ - Планирование и оптимизация запросов в PostgreSQL: https://go.vbloher.org/docs/07-databases/query-planning/ - Репликация в PostgreSQL: https://go.vbloher.org/docs/07-databases/replication/ - Шардирование (горизонтальное масштабирование): https://go.vbloher.org/docs/07-databases/sharding/ - Транзакции в PostgreSQL и Go (database/sql, pgx): https://go.vbloher.org/docs/07-databases/transactions/ - Распределённые системы: https://go.vbloher.org/docs/08-distributed-systems/ - CAP теорема: https://go.vbloher.org/docs/08-distributed-systems/cap-theorem/ - Circuit Breaker: https://go.vbloher.org/docs/08-distributed-systems/circuit-breaker/ - Консенсус и Raft: репликация состояния в присутствии отказов: https://go.vbloher.org/docs/08-distributed-systems/consensus-raft/ - Модели согласованности: https://go.vbloher.org/docs/08-distributed-systems/consistency/ - Гарантии доставки сообщений: at-most-once / at-least-once / exactly-once: https://go.vbloher.org/docs/08-distributed-systems/delivery-guarantees/ - Eventual Consistency: https://go.vbloher.org/docs/08-distributed-systems/eventual-consistency/ - Идемпотентность в распределённых системах: https://go.vbloher.org/docs/08-distributed-systems/idempotency/ - Apache Kafka: https://go.vbloher.org/docs/08-distributed-systems/kafka/ - Transactional Outbox: https://go.vbloher.org/docs/08-distributed-systems/outbox/ - RabbitMQ: AMQP 0-9-1, маршрутизация, надёжность доставки и сравнение с Kafka: https://go.vbloher.org/docs/08-distributed-systems/rabbitmq/ - Ретраи: backoff, jitter, budgets и идемпотентность: https://go.vbloher.org/docs/08-distributed-systems/retries/ - Saga Pattern: https://go.vbloher.org/docs/08-distributed-systems/saga/ - Observability: https://go.vbloher.org/docs/09-observability/ - Grafana: https://go.vbloher.org/docs/09-observability/grafana/ - Метрики: RED, USE, Golden Signals: https://go.vbloher.org/docs/09-observability/metrics/ - OpenTelemetry: https://go.vbloher.org/docs/09-observability/opentelemetry/ - Prometheus: https://go.vbloher.org/docs/09-observability/prometheus/ - SLI / SLO / SLA: https://go.vbloher.org/docs/09-observability/slo-sli/ - Структурированное логирование (slog): https://go.vbloher.org/docs/09-observability/structured-logging/ - Distributed Tracing: https://go.vbloher.org/docs/09-observability/tracing/ - System Design: https://go.vbloher.org/docs/10-system-design/ - Analytics Pipeline: https://go.vbloher.org/docs/10-system-design/analytics-pipeline/ - Chat System: https://go.vbloher.org/docs/10-system-design/chat/ - Фреймворк System Design интервью: https://go.vbloher.org/docs/10-system-design/framework/ - Notification Service: https://go.vbloher.org/docs/10-system-design/notification-service/ - Order Service: https://go.vbloher.org/docs/10-system-design/order-service/ - Payment Service: https://go.vbloher.org/docs/10-system-design/payment-service/ - Rate Limiter: https://go.vbloher.org/docs/10-system-design/rate-limiter/ - URL Shortener: https://go.vbloher.org/docs/10-system-design/url-shortener/ - DevOps: https://go.vbloher.org/docs/11-devops/ - CI/CD: пайплайны, стадии, стратегии деплоя: https://go.vbloher.org/docs/11-devops/cicd/ - Облака (AWS / GCP) для бэкендера: https://go.vbloher.org/docs/11-devops/cloud-aws-gcp/ - Docker для Go-разработчика: https://go.vbloher.org/docs/11-devops/docker/ - GitHub Actions и GitLab CI: https://go.vbloher.org/docs/11-devops/github-gitlab-ci/ - Kubernetes для Go-разработчика: https://go.vbloher.org/docs/11-devops/kubernetes/ - Terraform / Infrastructure as Code: https://go.vbloher.org/docs/11-devops/terraform/ - Алгоритмы: https://go.vbloher.org/docs/12-algorithms/ - Типовые алгоритмические задачи и паттерны: https://go.vbloher.org/docs/12-algorithms/common-problems/ - Асимптотическая сложность (Big-O): https://go.vbloher.org/docs/12-algorithms/complexity/ - Структуры данных в Go: https://go.vbloher.org/docs/12-algorithms/data-structures/ - Специфика live-coding на Go: https://go.vbloher.org/docs/12-algorithms/go-specifics/ - Behavioral: https://go.vbloher.org/docs/13-behavioral/ - Конфликты, разногласия и работа со стейкхолдерами: https://go.vbloher.org/docs/13-behavioral/conflicts/ - Как проходит senior-интервью: этапы, оценка, оффер: https://go.vbloher.org/docs/13-behavioral/interview-flow/ - Лидерство и менторство: https://go.vbloher.org/docs/13-behavioral/leadership-mentoring/ - Типовые поведенческие вопросы для Senior: https://go.vbloher.org/docs/13-behavioral/senior-questions/ > Модуль: Распределённые системы · Уровень: Senior+ ## TL;DR - **Circuit breaker** («предохранитель») оборачивает вызов нестабильной зависимости и при росте ошибок **размыкает цепь** — перестаёт слать запросы, мгновенно возвращая ошибку (**fail fast**). - Три состояния: **Closed** (запросы идут, считаем ошибки) → при превышении порога → **Open** (запросы блокируются, fail fast) → по таймауту → **Half-Open** (пропускаем пробные запросы) → успех → Closed, провал → снова Open. - Зачем: **fail fast** (не ждать таймаутов на мёртвый сервис), **защита downstream** от добивания нагрузкой во время деградации, **дать сервису время восстановиться**, предотвратить **каскадные сбои**. - **Bulkhead** — изоляция ресурсов (отдельные пулы соединений/семафоры на каждую зависимость), чтобы один тормозящий downstream не выел все воркеры/горутины. - В Go стандарт де-факто — **`github.com/sony/gobreaker`**: `Settings{ReadyToTrip, OnStateChange, Interval, Timeout, MaxRequests}`, `cb.Execute(fn)`, `Counts`. - **Fallback**: вернуть кэш, дефолт или деградированный ответ вместо ошибки. - Circuit breaker ≠ retry. Retry борется с *транзиентными* сбоями повтором; breaker борется с *устойчивыми* сбоями прекращением запросов. **Комбинация retry+breaker опасна** — ретраи накручивают счётчик ошибок и раздувают нагрузку; порядок и настройка критичны. ## Теория ### Проблема: каскадный сбой Сервис A зависит от B. B начинает тормозить (latency 5s вместо 50ms). Без предохранителя: - Запросы к B копятся, каждый держит горутину/соединение 5 секунд до таймаута. - Пул соединений и воркеры A исчерпываются ожиданием B. - A перестаёт обслуживать **все** запросы, даже не зависящие от B. - Сервисы, зависящие от A, начинают падать. **Каскад** распространяется вверх. Circuit breaker разрывает эту цепочку: обнаружив, что B нездоров, A немедленно отвечает ошибкой на запросы к B (fail fast), освобождая ресурсы и не добивая B. ### Три состояния и переходы ``` ошибок >= порог (ReadyToTrip) ┌──────────────────────────────────────────┐ │ ▼ ┌─────────┐ ┌────────┐ │ CLOSED │ │ OPEN │ │ запросы │ │ fail │ │ проходят│◄──────────┐ │ fast │ └─────────┘ │ проба УСПЕШНА └────────┘ ▲ │ (>= MaxRequests) │ │ │ │ прошёл Timeout │ ┌──────────┐ │ │ проба │HALF-OPEN │◄────────────────────┘ │ ПРОВАЛ ───│ пробные │ └───────────│ запросы │ └──────────┘ ``` **Closed** — нормальная работа. Все запросы проходят к downstream. Breaker считает успехи/провалы. Когда условие `ReadyToTrip` выполнено (например, доля ошибок > 50% или N подряд) — переход в **Open**. **Open** — цепь разомкнута. Запросы **не идут** к downstream, breaker сразу возвращает ошибку (`ErrOpenState`). Это и есть fail fast. По истечении `Timeout` → **Half-Open**. **Half-Open** — пробное состояние. Пропускается ограниченное число «разведочных» запросов (`MaxRequests`). Если они успешны — downstream восстановился, переход в **Closed**, счётчики сброшены. Если хоть один провалился — обратно в **Open**, таймер заново. В half-open параллелизм ограничен, чтобы не обрушить только что вставший сервис лавиной. ### Зачем это нужно (что именно даёт) 1. **Fail fast.** Вместо ожидания таймаута на заведомо мёртвый сервис — мгновенная ошибка. Освобождает ресурсы вызывающего, держит хвост latency низким. 2. **Защита downstream.** Перегруженный сервис не добивается потоком запросов — ему дают передышку для восстановления (сброс очередей, GC, переподключение к БД). 3. **Предотвращение каскада.** Локализует сбой одной зависимости, не давая ему «съесть» весь сервис и распространиться выше. 4. **Управляемая деградация.** В связке с fallback — пользователь видит деградированный, но рабочий ответ вместо 500. ### Bulkhead pattern (переборки) Метафора из судостроения: корпус делят на изолированные отсеки (bulkheads), пробоина в одном не топит весь корабль. В софте — **изоляция ресурсов по зависимостям**. Без bulkhead все вызовы делят один пул горутин/соединений. Одна тормозящая зависимость занимает весь пул → голодают остальные. С bulkhead каждая зависимость получает **свой ограниченный пул** (семафор / отдельный thread pool / отдельный connection pool): ``` Без bulkhead: С bulkhead: ┌─────────────────┐ ┌────────┬────────┬────────┐ │ общий пул 100 │ │ B: 30 │ C: 30 │ D: 40 │ │ весь занят B │ │ занят │ свободн│ свободн│ │ C, D голодают │ │ C, D работают │ └─────────────────┘ └────────┴────────┴────────┘ ``` В Go bulkhead просто реализуется **буферизованным каналом-семафором**: ```go type Bulkhead struct{ sem chan struct{} } func NewBulkhead(max int) *Bulkhead { return &Bulkhead{sem: make(chan struct{}, max)} } 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() // или ErrBulkheadFull при неблокирующем варианте } } ``` Bulkhead и circuit breaker дополняют друг друга: bulkhead ограничивает *количество одновременных* вызовов (изоляция), breaker — *вообще пускать ли* вызовы (по здоровью). Часто применяются вместе. ### sony/gobreaker `github.com/sony/gobreaker` — порт Hystrix-идей, де-факто стандарт в Go. Конфигурируется через `Settings`: ```go type Settings struct { Name string MaxRequests uint32 // макс. запросов в Half-Open (по умолч. 1) Interval time.Duration // период сброса Counts в Closed (0 = не сбрасывать по времени) Timeout time.Duration // как долго breaker в Open до перехода в Half-Open (по умолч. 60s) ReadyToTrip func(counts Counts) bool // когда размыкать OnStateChange func(name string, from, to State) // колбэк смены состояния IsSuccessful func(err error) bool // считать ли ошибку «провалом» breaker'а } type Counts struct { Requests uint32 TotalSuccesses uint32 TotalFailures uint32 ConsecutiveSuccesses uint32 ConsecutiveFailures uint32 } ``` Ключевое: - **`ReadyToTrip(Counts) bool`** — предикат размыкания. Вызывается после каждого провала в Closed. Типичные политики: `ConsecutiveFailures > N` или доля ошибок `TotalFailures / Requests > 0.5` при достаточном объёме. - **`Counts`** — счётчики окна. `Interval` определяет, как часто они сбрасываются в Closed (скользящее окно по времени; в Open сбрасываются при входе). - **`OnStateChange`** — для метрик/алертов (логировать переходы Closed↔Open). - **`IsSuccessful`** — позволяет не считать ожидаемые ошибки (например 404, `context.Canceled`) за провал предохранителя. Очень важно: иначе бизнес-ошибки откроют breaker. - **`Execute(fn) (any, error)`** — обёртка вызова. В Open сразу вернёт `gobreaker.ErrOpenState`; в Half-Open при превышении `MaxRequests` — `ErrTooManyRequests`. ```go package main import ( "errors" "fmt" "log" "net/http" "time" "github.com/sony/gobreaker" ) func newBreaker() *gobreaker.CircuitBreaker { st := gobreaker.Settings{ Name: "payments-api", MaxRequests: 3, // пробных запросов в Half-Open Interval: 10 * time.Second, // окно подсчёта в Closed Timeout: 30 * time.Second, // сидим в Open 30s, потом Half-Open ReadyToTrip: func(c gobreaker.Counts) bool { // размыкаем при >= 5 запросах и доле ошибок > 60% failRatio := float64(c.TotalFailures) / float64(c.Requests) return c.Requests >= 5 && failRatio >= 0.6 }, IsSuccessful: func(err error) bool { if err == nil { return true } // 4xx-ошибки — это «успех» для breaker'а (downstream жив, виноват запрос) var he *httpError if errors.As(err, &he) && he.code < 500 { return true } return false }, OnStateChange: func(name string, from, to gobreaker.State) { log.Printf("breaker %s: %s -> %s", name, from, to) // здесь — метрика/алерт }, } return gobreaker.NewCircuitBreaker(st) } type httpError struct{ code int } func (e *httpError) Error() string { return fmt.Sprintf("http %d", e.code) } func callDownstream(cb *gobreaker.CircuitBreaker, url string) ([]byte, error) { body, err := cb.Execute(func() (interface{}, error) { resp, err := http.Get(url) // в проде — клиент с таймаутом! if err != nil { return nil, err } defer resp.Body.Close() if resp.StatusCode >= 400 { return nil, &httpError{code: resp.StatusCode} } return readAll(resp), nil }) if err != nil { switch { case errors.Is(err, gobreaker.ErrOpenState): return fallback() // цепь разомкнута — fail fast + fallback case errors.Is(err, gobreaker.ErrTooManyRequests): return fallback() // half-open перегружен пробами default: return nil, err // реальная ошибка вызова } } return body.([]byte), nil } ``` ### Fallback-стратегии Размыкание breaker'а само по себе лишь меняет 5-секундный таймаут на мгновенную ошибку. Чтобы пользователь не увидел 500, нужен **fallback**: | Стратегия | Когда | Пример | |-----------|-------|--------| | **Cached value** | данные допускают устаревание | отдать последний успешный ответ из кэша (stale-while-error) | | **Default value** | есть разумный дефолт | рекомендации недоступны → показать топ-популярное | | **Graceful degradation** | часть функциональности можно отключить | лента без блока «персонализация», страница без виджета отзывов | | **Queue / async** | операция терпит отложенность | положить в очередь, обработать когда downstream встанет | | **Fail fast (явная ошибка)** | fallback невозможен/опасен (платёж) | честный 503 с Retry-After, лучше чем неверный ответ | Принцип: fallback должен быть **дешевле и надёжнее** защищаемого вызова, иначе он сам станет точкой отказа. ### Circuit breaker vs Retry Это **разные инструменты для разных сбоев**: | | Retry | Circuit Breaker | |--|-------|-----------------| | Цель | пережить **транзиентный** сбой (мелькнул и прошёл) | защититься от **устойчивого** сбоя | | Действие | повторить запрос | прекратить запросы | | Когда хорош | редкая сетевая ошибка, 503 на долю секунды | downstream упал/перегружен надолго | | Риск | при массовом сбое **усиливает** нагрузку | может «зарубить» уже выздоровевший сервис | Они комплементарны: retry для случайных глюков, breaker — чтобы не ретраить в стену. ### Опасность retry + circuit breaker вместе Наивная комбинация создаёт проблемы: 1. **Retry накручивает счётчик breaker'а.** Если retry **внутри** Execute — каждая попытка считается отдельным запросом, breaker размыкается раньше/позже, чем ожидаешь, на искажённой статистике. Если retry **снаружи** breaker'а — после размыкания каждый retry мгновенно получает `ErrOpenState`, что бессмысленно тратит попытки. **Правильно: breaker оборачивает один вызов, retry — снаружи, с проверкой `ErrOpenState` (не ретраить при открытом breaker'е).** 2. **Усиление нагрузки (retry storm).** При деградации downstream все клиенты одновременно ретраят → нагрузка ×N в худший момент → добивают сервис. Обязательны **exponential backoff + jitter** и ограничение числа попыток. Breaker здесь — последняя линия: когда retry не помогает, он размыкается и гасит шторм. 3. **Retry amplification по слоям.** Если ретраит и клиент (3×), и mesh (3×), и сервис своему downstream (3×) — итого 27× нагрузки на нижний слой. **Правило: ретраить только на одном уровне** (обычно ближе к краю), глубже — только breaker + fail fast. Рекомендуемый порядок обёрток (снаружи внутрь): `retry(backoff+jitter) → circuit breaker → bulkhead → timeout → call`. Retry проверяет: если ошибка = `ErrOpenState`, не ретраить (или ретраить с большим backoff), т.к. повтор всё равно мгновенно отлетит. ## Подводные камни / gotchas - **Нет таймаута на сам вызов.** Breaker не отменяет зависший запрос — без `context`/таймаута в HTTP-клиенте горутины всё равно зависнут, breaker не успеет «увидеть» провалы. Таймаут обязателен под breaker'ом. - **Бизнес-ошибки открывают breaker.** Если `IsSuccessful` не настроен, 404/401/валидационные ошибки считаются провалами и размыкают цепь, хотя downstream здоров. Считать провалом только 5xx/сетевые/таймауты. - **Слишком чувствительный `ReadyToTrip`.** Порог по абсолютному числу без учёта объёма (`ConsecutiveFailures > 3`) флапает на низком трафике. Лучше доля ошибок при минимальном `Requests`. - **Один общий breaker на много хостов/эндпоинтов.** Сбой одного инстанса размыкает цепь ко всем. Breaker должен быть на гранулярность зависимости (per-host / per-endpoint). - **Half-Open лавина.** Большой `MaxRequests` в Half-Open запустит много проб разом и снова уронит едва вставший сервис. Держать малым (1–3). - **Retry внутри Execute.** Искажает статистику breaker'а и множит нагрузку. Retry — снаружи, с учётом `ErrOpenState`. - **Retry amplification по слоям.** Ретраи на нескольких уровнях перемножаются. Ретраить на одном уровне. - **Breaker без fallback.** Размыкание просто меняет вид ошибки. Без fallback пользователь всё равно получает сбой — продумать деградацию. - **Глобальное состояние при горизонтальном масштабировании.** Каждый под имеет свой in-process breaker; они не синхронизированы. Это нормально (и даже желательно), но мониторинг должен агрегировать состояния, а не смотреть на один под. - **`OnStateChange` не подключён к метрикам.** Без алертов на переход в Open сбой downstream остаётся незамеченным до жалоб. ## Вопросы на собеседовании **В:** Опишите три состояния circuit breaker и переходы. **О:** Closed — запросы проходят, считаем ошибки; при превышении порога (ReadyToTrip) → Open. Open — fail fast, запросы блокируются; по истечении Timeout → Half-Open. Half-Open — пропускаем ограниченное число проб; все успешны → Closed (счётчики сброшены), любой провал → снова Open. **В:** Зачем нужно состояние Half-Open, почему не переходить из Open сразу в Closed? **О:** Чтобы безопасно проверить восстановление, не обрушив downstream лавиной. Half-Open пропускает лишь несколько пробных запросов; если они успешны — сервис здоров, закрываем цепь, иначе возвращаемся в Open. Прямой переход Open→Closed послал бы весь трафик на возможно ещё нездоровый сервис. **В:** Чем circuit breaker отличается от retry? **О:** Retry борется с транзиентными сбоями повтором запроса; breaker — с устойчивыми сбоями прекращением запросов (fail fast). Retry при массовом сбое усиливает нагрузку, breaker — гасит её. Они комплементарны: retry для редких глюков, breaker — чтобы не долбить в мёртвый сервис. **В:** Почему опасно комбинировать retry и circuit breaker и как делать правильно? **О:** Retry внутри breaker искажает его счётчики и множит нагрузку; ретраи разных слоёв перемножаются (retry amplification), создавая шторм при деградации. Правильно: breaker оборачивает одиночный вызов, retry — снаружи с backoff+jitter и проверкой ErrOpenState (не ретраить при открытом breaker'е); ретраить только на одном уровне стека. **В:** Что такое bulkhead и как он соотносится с circuit breaker? **О:** Bulkhead — изоляция ресурсов: каждой зависимости выделяется свой ограниченный пул (семафор/пул соединений), чтобы одна тормозящая зависимость не выела все воркеры. Breaker решает «пускать ли вызов вообще» (по здоровью), bulkhead — «сколько одновременных вызовов разрешено» (изоляция). Применяются вместе. **В:** Как настроить gobreaker, чтобы 404-ответы не размыкали цепь? **О:** Через `Settings.IsSuccessful`: вернуть true для ошибок с кодом < 500 (и для context.Canceled). Тогда breaker считает провалами только 5xx/сетевые/таймауты, а клиентские ошибки не влияют на его состояние. **В:** Какие fallback-стратегии вы знаете? **О:** Cached/stale value (отдать последний успешный ответ), default value (разумный дефолт), graceful degradation (отключить часть функциональности), async/queue (отложить операцию), либо явный fail fast с 503+Retry-After, если корректный fallback невозможен (например, платёж). Fallback должен быть надёжнее защищаемого вызова. **В:** Как выбрать условие размыкания (ReadyToTrip)? **О:** Предпочтительно доля ошибок при достаточном объёме запросов (`Requests >= N && failures/requests >= threshold`), а не абсолютное число подряд — последнее флапает на низком трафике. Учитывать `Interval` (окно сброса счётчиков) и характер зависимости. **В:** Breaker в каждом поде свой — это проблема при масштабировании? **О:** Нет, in-process breaker на под — норма и даже желательно (локальное решение, без сетевого оверхеда на координацию). Каждый под защищает себя независимо. Важно лишь агрегировать состояния в мониторинге, а не смотреть на один под, и не делать общий распределённый breaker без необходимости. ## На что копают на senior+ - **Понимание каскадных сбоев.** Кандидат должен объяснить механику истощения пулов и распространения сбоя вверх по графу зависимостей, а не просто «breaker отключает плохой сервис». - **Взаимодействие паттернов.** Грамотная композиция timeout + bulkhead + breaker + retry + fallback и правильный порядок обёрток; понимание, что breaker без таймаута почти бесполезен. - **Retry amplification.** Глубокое осознание опасности перемножения ретраев по слоям и retry-storm, требование backoff+jitter и ретраев на одном уровне. - **Настройка под нагрузку.** Осмысленный выбор ReadyToTrip (доля vs абсолют), Timeout, MaxRequests, Interval, IsSuccessful — и понимание, что бизнес-ошибки не должны открывать breaker. - **Гранулярность.** Per-dependency / per-host breaker'ы вместо одного глобального; обсуждение partial outage одного инстанса. - **Fallback-мышление.** Что breaker без продуманной деградации лишь меняет вид ошибки; trade-offs stale-данных vs честного отказа (особенно для денег/консистентности). - **Наблюдаемость.** OnStateChange → метрики/алерты; SLO на долю fail-fast ответов; дашборды состояний по подам. - **Знание экосистемы.** sony/gobreaker, наследие Hystrix (и почему он в maintenance), аналоги (resilience4j, Polly), реализация в service mesh (Envoy outlier detection) — и когда лучше mesh, а когда in-process.