--- tags: [http, retry, jitter, backoff, distributed-system] create time: 2026-06-01 10:30 --- # Jitter 抖动与重试策略 ## 概述 当客户端收到 429 响应后,简单地重试是不够的——如果所有客户端在同一个时刻同时重试,会造成"惊群效应"(thundering herd),让刚恢复的服务再次被打垮。**Jitter(随机抖动)** 是让分布式系统优雅退避的关键技术。 对应薄弱点 F4:指数退避 + Jitter 的组合使用。 ## 一、为什么需要 Jitter? ### 问题场景 假设你的 API 限流阈值是 100 QPS,有 100 个客户端共享这个配额: ``` t = 0s: 所有客户端发现超限 → 等待 5 秒 (Retry-After: 5) t = 5s: 100 个客户端同时重试 → 瞬间又达到 100+ QPS → 再次被限流 t = 10s: 再次全部超时... ``` 这就是**惊群效应**——大量客户端在同一时刻涌入,效果相当于人为制造了一次流量脉冲。 ### 解决方案 ```mermaid flowchart TD subgraph "无 Jitter(糟糕)" C1["Client 1"] -->|wait 5s| S1["t=5s
⚡ 100 clients fire at once"] C2["Client 2"] -->|wait 5s| S1 C3["Client N"] -->|wait 5s| S1 S1 --> FAIL["🔴 Server overwhelmed again"] end subgraph "有 Jitter(正常)" D1["Client 1"] -->|"wait 5s, jitter +0.3s"| D4["t=5.3s
😊 gradual arrival"] D2["Client 2"] -->|"wait 5s, jitter -0.7s"| D5["t=4.3s"] D3["Client N"] -->|"wait 5s, jitter +1.2s"| D6["t=6.2s"] D4 --> OK["🟢 Server handles gracefully"] D5 --> OK D6 --> OK end style FAIL fill:#ffebee style OK fill:#e8f5e9 ``` ## 二、指数退避 + Jitter 公式 ### 2.1 基础公式 ```go // 第 attempt 次重试的等待时间(简单加减 Jitter 实现) // wait = base × 2^attempt ± random(0, base),上限不超过 Retry-After wait := min(base * math.Pow(2, float64(attempt)) + (-jitter ~ +jitter), retryAfter) ``` 分解说明: | 部分 | 作用 | 示例值 | |------|------|--------| | `base` | 基础等待时间 | 1 秒 | | `2^attempt` | 指数增长因子 | attempt=0→1x, 1→2x, 2→4x, 3→8x | | `randomJitter` | 随机抖动,防止同步 | ±[0, base] 秒 | | `retryAfter` | 服务端上限,不违反约定 | Retry-After 头部的值 | ### 2.2 具体数字示例 假设 `base = 1s`,`Retry-After = 10s`: | 重试次数 | 指数退避 | +Jitter(±1s) | 实际等待 | capped to Retry-After | |---------|---------|-------------|---------|---------------------| | #1 | 1 × 2⁰ = 1s | 1s ± 1s | [0, 2s] | min([0,2], 10) = [0, 2] | | #2 | 1 × 2¹ = 2s | 2s ± 1s | [1, 3s] | min([1,3], 10) = [1, 3] | | #3 | 1 × 2² = 4s | 4s ± 1s | [3, 5s] | min([3,5], 10) = [3, 5] | | #4 | 1 × 2³ = 8s | 8s ± 1s | [7, 9s] | min([7,9], 10) = [7, 9] | | #5 | 1 × 2⁴ = 16s | — | — | min(16, 10) = **10s** ← 不再增长 | > [!tip]- 为什么要取 min(..., Retry-After)? > > `Retry-After` 是服务器给出的硬性上限,客户端不应超过它。但如果指数退避还没到 Retry-After 的时间,也应该遵循服务器的建议等待。 ## 三、Go 实现 ```go import ( "math/rand" "time" ) // calculateRetryDelay 计算带 Jitter 的指数退避等待时间 func calculateRetryDelay(attempt int, retryAfterSec int64) time.Duration { // 1. 指数退避 base := time.Second wait := base * time.Duration(math.Pow(2, float64(attempt))) // 2. 添加 Jitter:±[0, base] 的随机偏移 jitter := time.Duration(rand.Int63n(int64(base))) if rand.Intn(2) == 0 { jitter = -jitter // 50% 概率减,50% 加 } wait = wait + jitter // 3. 不超过 Retry-After if retryAfterSec > 0 { maxWait := time.Duration(retryAfterSec) * time.Second if wait > maxWait { wait = maxWait } } return wait } // 使用示例 func callWithRetry(ctx context.Context, maxRetries int, callFunc func() error) error { for attempt := 0; attempt <= maxRetries; attempt++ { err := callFunc() if err == nil { return nil } // 检查是否是 429 if isRateLimited(err) { retryAfter := getRetryAfterHeader() // 从响应头读取 wait := calculateRetryDelay(attempt, retryAfter) select { case <-time.After(wait): continue // 继续重试 case <-ctx.Done(): return ctx.Err() } } // 非限流错误,直接返回 return err } return fmt.Errorf("max retries (%d) exceeded", maxRetries) } ``` ## 四、常见 Jitter 算法对比 | 算法 | 公式 | 优点 | 缺点 | |------|------|------|------| | **Full Jitter**(AWS 推荐) | `random(0, min(cap, base × 2^attempt))` | 分布均匀,去抖效果好 | 可能等待很短导致过早重试 | | **Equal Jitter** | `min(cap, base × 2^attempt / 2 + random(0, base × 2^attempt / 2))` | 介于两者之间 | 略复杂 | | **Decorrelated Jitter**(AWS 默认) | `min(cap, random(base, prev_wait × 3))` | 快速分散,适应性强 | 参数调优门槛高 | | **简单加减 Jitter** | `base × 2^attempt ± random(0, base)` | 实现最简单 | 抖动范围小 | > [!tip]- 推荐方案 > > 对于限流场景,**简单加减 Jitter**(上面 Go 代码的实现)已经足够。如果你的系统规模极大(数千客户端),建议用 Full Jitter(AWS 推荐)。 ## 五、完整流程总结 ```mermaid flowchart TD Req["请求发出"] --> Resp{"收到响应?"} Resp -- "200" --> Success["✅ 成功"] Resp -- "429" --> Read["读取 Retry-After 头部"] Read --> Calc["计算 wait = 指数退避 + Jitter"] Calc --> Wait["⏳ 等待"] Wait --> Retry{"还有重试次数?"} Retry -- "是" --> Req Retry -- "否" --> Fail["❌ 最终失败,上报告警"] style Success fill:#c8e6c9 style Fail fill:#ffebee ``` ## 关联笔记 - [[分布式限流]] — 限流架构中的客户端重试策略 - [[多级限流架构]] — 各层超时配置与降级策略设计