This repository has been archived on 2026-05-24. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
all-in-kingsoft/hzh/MS/02-服务治理/容错模式/README.md
T

289 lines
8.8 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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["请求放行<br/>⏱ 实时统计成功率<br/>⚠️ 连续失败 → 熔断"]
end
subgraph OP ["OPEN — 熔断"]
O["🚫 请求短路<br/>💥 直接返回降级响应<br/>🛡 不调下游,防雪崩"]
end
subgraph HO ["HALF-OPEN — 探测"]
H["🔍 放行少量探测请求<br/>✅ 成功 → 恢复全量流量<br/>❌ 失败 → 重新熔断"]
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: 网关级限流<br/>防刷 / DDOS"]
GW --> Biz["L2: 业务级限流<br/>按用户/接口配额"]
Biz --> Downstream["L3: 下游服务限流<br/>保护实例不被打满"]
style GW fill:#ffebee
style Biz fill:#fff3e0
style Downstream fill:#e8f5e9
```
## 5. 舱壁隔离 (Bulkhead)
为不同下游服务分配独立的线程池/连接池,防止一个服务的故障蔓延到其他服务。
```mermaid
graph TB
Caller["调用方"]
subgraph Bulkhead["舱壁隔离"]
Pool1[订单池<br/>maxActive=10]
Pool2[用户池<br/>maxActive=5]
Pool3[支付池<br/>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]] — 熔断器的指标暴露和告警联动