--- tags: [microservice, circuit-breaker, retry, rate-limiting, bulkhead, health-check] create time: 2026-05-05 --- # 容错模式 ## 概述 微服务最大的挑战是 **网络不可靠**。分布式系统的每个远程调用都可能超时、被拒绝、或部分成功。必须提前设计降级和恢复策略。 ```mermaid graph TB A["🛡️ 超时控制"] --> B["⏱ 重试机制"] B --> C["⚡ 熔断器"] C --> D["🔀 限流"] D --> E["🧱 舱壁隔离"] E --> F["💤 降级策略"] ``` > [!warning] 核心认知 > 不要假设任何网络调用会成功。每个远程调用的代码都应该考虑失败路径。 ## 1. 超时控制 这是所有容错机制的**前提**——没有超时的系统迟早会被拖垮。 ```go // Go context 超时时序图 ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second) defer cancel() resp, err := client.DoRequest(ctx, req) if err == context.DeadlineExceeded { // 上游超时 → 走降级逻辑或快速返回错误 } ``` | 层级 | 推荐超时值 | 说明 | |------|-----------|------| | HTTP 客户端 → 业务服务 | 3~5s | 留有余量给下游 | | gRPC → 内部服务 | 1~2s | 内网延迟低 | | DB 查询 | 500ms~1s | 长查询应在 SQL 层限制 | > [!tip] 超时传递原则 > 如果整体 SLA 要求 P99 < 200ms,子调用的超时时间必须逐层递减。例如:网关 200ms → 订单服务 150ms → 库存服务 80ms。 ## 2. 重试机制 > [!warning] 关键原则 > 只对 **幂等操作** 重试!POST 创建操作直接重试会导致重复数据。GET、PUT(同参数)适合重试。 ### 指数退避 + Jitter ``` 第 1 次重试:等待 1s + random(0~100ms) 第 2 次重试:等待 2s + random(0~100ms) 第 3 次重试:等待 4s + random(0~100ms) ... ``` ```go func retryWithBackoff(ctx context.Context, maxRetries int, fn func() error) error { for attempt := uint(0); attempt <= uint(maxRetries); attempt++ { if err := fn(); err != nil { if attempt == uint(maxRetries) { return err } // jitter: 随机偏移,避免所有客户端同时重试造成二次冲击 jitter := time.Duration(rand.Int63n(int64(time.Millisecond * 100))) wait := time.Duration(math.Pow(2, float64(attempt)))*time.Second + jitter select { case <-ctx.Done(): return ctx.Err() case <-time.After(wait): } } else { return nil } } return nil } ``` **Jitter 为什么重要?** - 如果没有随机偏移,当某个下游服务挂掉时,所有客户端会在同一时刻同时重试 - 这会造成 **retry storm(重试风暴)**,对恢复中的服务施加二次冲击 - 加上 jitter 后,请求均匀分散到时间窗口内 ### 可重试 vs 不可重试的错误类型 | 可以重试 | 不应重试 | |---------|---------| | 连接超时 | 4xx Client Error(除 429) | | 连接重置 | 404 Not Found | | 502/503/504 Gateway Error | 业务错误(如余额不足) | | 并发冲突(乐观锁失败) | 403 Forbidden | ## 3. 熔断器 (Circuit Breaker) 当下游服务频繁失败时,快速失败避免线程堆积雪崩。 ```mermaid flowchart TD CB{{🔘 CIRCUIT BREAKER}} subgraph CL ["CLOSED — 正常"] C["请求放行
⏱ 实时统计成功率
⚠️ 连续失败 → 熔断"] end subgraph OP ["OPEN — 熔断"] O["🚫 请求短路
💥 直接返回降级响应
🛡 不调下游,防雪崩"] end subgraph HO ["HALF-OPEN — 探测"] H["🔍 放行少量探测请求
✅ 成功 → 恢复全量流量
❌ 失败 → 重新熔断"] end CB --> CL CL -.->|失败 > N| OP OP -.->|等待超时| HO HO -->|探测成功| CL HO -->|探测失败| OP style CL fill:#e6f9e6,stroke:#4caf50,stroke-width:3px,color:#000 style OP fill:#ffebee,stroke:#ef5350,stroke-width:3px,color:#000 style HO fill:#fff8e1,stroke:#ffa726,stroke-width:3px,color:#000 ``` ### 状态转换条件 | 状态 | 触发条件 | 行为 | 退出条件 | |------|---------|------|---------| | **Closed** | 初始状态 / 从 Half-Open 恢复 | 正常放行请求 | 连续失败数 > threshold → 打开 | | **Open** | 失败率超过阈值 | 立即短路返回错误 | 等待 recoveryTimeout → 进入半开 | | **Half-Open** | 探测期 | 放少量请求测试 | 探测成功 → Closed;探测失败 → Open | ### 实战示例 ```go // gobreaker 示例 cb := state.NewCB(state.Settings{ Name: "UserService", MaxElems: 10, // 窗口大小 WaitRetry: 5 * time.Second, // Open → Half-Open 等待时间 ReadyToTrip: func(counts state.Counters) bool { return counts.Failures > 5 // 连续 5 次失败熔断 }, }) result := cb.Execute(doRequest) if result.Err != nil { // 熔断打开,立即短路返回错误,不调用下游 return fallbackResponse() } ``` ## 4. 限流 (Rate Limiting) 保护下游服务不被过量请求压垮。 ### 常见算法 ```mermaid flowchart LR subgraph "令牌桶" T1["匀速添加令牌"] T2["请求消耗令牌"] T3["不够则拒绝"] end subgraph "滑动窗口" S1["按时间片切分"] S2["统计每片请求数"] S3["超过阈值则拒绝"] end ``` | 算法 | 特点 | 适用场景 | |------|------|---------| | **固定窗口** | 简单实现,存在边界突发问题 | 低频场景 | | **滑动窗口** | 精确控制,内存占用高 | 需要精细限流的场景 | | **令牌桶** | 允许一定突发,平均速率受限 | API 网关通用方案 | | **漏桶** | 强制匀速输出,不允突发 | 防止下游被打满 | ### 限流层级 ```mermaid graph TB Client["客户端"] --> GW["L1: 网关级限流
防刷 / DDOS"] GW --> Biz["L2: 业务级限流
按用户/接口配额"] Biz --> Downstream["L3: 下游服务限流
保护实例不被打满"] style GW fill:#ffebee style Biz fill:#fff3e0 style Downstream fill:#e8f5e9 ``` ## 5. 舱壁隔离 (Bulkhead) 为不同下游服务分配独立的线程池/连接池,防止一个服务的故障蔓延到其他服务。 ```mermaid graph TB Caller["调用方"] subgraph Bulkhead["舱壁隔离"] Pool1[订单池
maxActive=10] Pool2[用户池
maxActive=5] Pool3[支付池
maxActive=8] end Caller --> Pool1 Caller --> Pool2 Caller --> Pool3 Pool1 --> OrderSvc[[Order Service]] Pool2 --> UserSvc[[User Service]] Pool3 --> PaySvc[[Payment Service]] note[订单服务爆满不会影响用户服务的调用] Pool1 -.-> note ``` ```go // poolgroup 示例:为不同服务隔离连接池 orderPool := pool.New(10, 100) // 活跃10个,上限100个 userPool := pool.New(5, 50) payPool := pool.New(8, 80) // 订单服务占满连接不会影响用户服务的调用 ``` ## 6. 降级策略 当系统部分不可用时,通过降级保证核心功能可用。 ```mermaid flowchart TD Req["用户请求"] Req --> Normal{"正常链路是否可用?"} Normal -- 是 --> Full["完整功能 ✅"] Normal -- 否 --> Degrade{"降级级别?"} Degrade -- "非核心模块" --> Skip["跳过该功能 ⚡"] Degrade -- "核心模块不可用" --> Cache["返回缓存兜底 📦"] Cache -- "无缓存" --> Default["返回默认值/提示页"] Skip --> Partial["部分可用 ✅"] Default --> Minimal["最低可用 ✅"] ``` ### 常见降级场景 | 场景 | 降级方案 | 影响 | |------|---------|------| | 推荐服务挂了 | 展示热门榜单 / 空 | 用户体验略降 | | 评论服务挂了 | 隐藏评论区 | 不影响购买流程 | | 搜索服务超时 | 返回最近浏览记录 | 降低用户感知 | | 库存服务不可用 | 显示"暂时无法确认库存" | 引导用户稍后再试 | ## 7. 健康检查 ```mermaid sequenceDiagram participant LB as 负载均衡器/K8s participant S1 as 服务实例 A participant S2 as 服务实例 B LB->>S1: GET /healthz -> 200 OK LB->>S2: GET /healthz -> 503 ERROR Note over LB: 标记 S2 不健康,剔除出池 LB->>S2: GET /healthz -> 200 OK Note over LB: 逐步恢复,重新加入池 ``` - **存活探针 (Liveness)**:判断"进程是否还活着",挂了则重启 - **就绪探针 (Readiness)**:判断"是否能接收流量",未就绪则剔除负载均衡池 - **Readiness 比 Liveness 更重要**——一个进程活着但数据库连接耗尽时,应该停止接收流量而不是重启 ## 关联笔记 - [[02-服务治理/API网关/README]] — 网关层的限流和熔断插件 - [[02-服务治理/服务发现/README]] — 健康检查是服务发现的基础 - [[04-可观测性/Metrics监控/README]] — 熔断器的指标暴露和告警联动