awesome-everything EN
↑ Обратно к восхождению

Распределённые системы

Retry amplification: чтение кода

Суть Читай реальный retry-код — backoff с jitter, retry budget, вложенный fan-out, half-open breaker — и выбирай фикс с наибольшим рычагом, который senior делает первым.
Высота — путь к senior
НольJuniorMiddleSenior
Ты на senior-высоте — в орбите
◷ 14 min

Баги ретраев прячутся в коде, который выглядит корректным в юнит-тесте и детонирует под реальным outage. Прочитай каждый сниппет, предскажи поведение, когда зависимость лежит, и выбери фикс, который senior делает первым.

Цель

Отработай цикл, который ты прогоняешь на каждом retry-конфиге: прочитай backoff, budget и граф вызовов; предскажи fan-out при сбое; и тянись к фиксу с наибольшим рычагом, прежде чем добавлять ещё ретраи.

Сниппет 1 — backoff без jitter

func callWithRetry(ctx context.Context, fn func() error) error {
    base := 100 * time.Millisecond
    var err error
    for attempt := 0; attempt < 5; attempt++ {
        if err = fn(); err == nil {
            return nil
        }
        // exponential, but no jitter
        sleep := base * time.Duration(1<<attempt) // 100, 200, 400, 800, 1600 ms
        time.Sleep(sleep)
    }
    return err
}
Викторина

10 000 клиентов одновременно вызывают это против зависимости, которая только что блипнула. Что идёт не так и какой однострочный фикс?

Сниппет 2 — retry budget

// token-bucket retry budget: retries may consume at most ~10% of request volume
type RetryBudget struct {
    mu     sync.Mutex
    tokens float64
}

func (b *RetryBudget) OnRequest()      { b.mu.Lock(); b.tokens += 0.1; b.mu.Unlock() } // +0.1 per request
func (b *RetryBudget) TryRetry() bool {
    b.mu.Lock(); defer b.mu.Unlock()
    if b.tokens >= 1 {
        b.tokens -= 1 // each retry costs 1 token
        return true
    }
    return false // budget exhausted: fail fast, do not retry
}
Викторина

Зависимость полностью лежит: каждый запрос сбоит. Какую установившуюся частоту ретраев допускает этот budget и что это даёт?

Сниппет 3 — вложенные ретраи

// data layer
func (d *DataLayer) Read(ctx context.Context, k string) (V, error) {
    return retry(3, func() (V, error) { return d.pool.Read(ctx, k) }) // retries 3x
}
// service layer
func (s *Service) Get(ctx context.Context, k string) (V, error) {
    return retry(3, func() (V, error) { return s.data.Read(ctx, k) }) // retries 3x, calling the above
}
// gateway
func (g *Gateway) Handle(ctx context.Context, k string) (V, error) {
    return retry(3, func() (V, error) { return g.svc.Get(ctx, k) }) // retries 3x, calling the above
}
Викторина

На один запрос, сбоящий на пуле, сколько вызовов придёт к connection pool и какой правильный структурный фикс?

Сниппет 4 — half-open breaker

func (b *Breaker) Call(fn func() error) error {
    switch b.state {
    case Open:
        if time.Since(b.openedAt) < b.cooldown {
            return ErrOpen // fail fast, no network call
        }
        b.state = HalfOpen // cooldown elapsed: allow probes
        fallthrough
    case HalfOpen:
        err := fn()
        if err != nil {
            b.state = Open; b.openedAt = time.Now() // probe failed: re-open
            return err
        }
        b.state = Closed // probe succeeded: resume normal traffic
        return nil
    default: // Closed
        return b.trackFailures(fn)
    }
}
Викторина

В состоянии HalfOpen этот код пропускает всех конкурентных вызывающих сразу. Под нагруженным сервисом почему это опасно и какой фикс?

Итог

Каждый retry-инцидент читается в коде: backoff без jitter ресинхронизирует толпу (full jitter — однострочный фикс); token-bucket retry budget превращает неограниченную амплификацию в потолок ~10%; вложенные ретраи на N слоях множатся до retries^N (ретрай на одном слое, проброс на остальных); а half-open breaker должен пускать одну пробу, а не поток. Предскажи fan-out при сбое, чини структуру, потом перетестируй под той же синхронной нагрузкой.

Продолжить восхождение ↑Retry amplification: разорви metastable storm
хоткеи развернуть
поиск
K
пред. пьеса
k
след. пьеса
j
тиры
t
это меню
?
sources2
expand
  1. 01
  2. 02

Trademarks belong to their respective owners. Editorial reference only.