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-服务治理.md
T

573 lines
18 KiB
Markdown
Raw Normal View History

2026-04-29 20:39:17 +08:00
---
tags: [microservice, service-governance, api-gateway, circuit-breaker, service-discovery, distributed-tracing, canary-deployment]
create time: 2026-04-29 12:02
---
# 服务治理
## 概述
服务治理是微服务架构的"操作系统"——它不直接实现业务逻辑,但决定了系统能否在复杂、高并发、多团队协作的环境中稳定运转。
本文覆盖服务治理的 **七大核心能力**:
```mermaid
quadrantChart
title 服务治理能力矩阵
x-axis Low Impact --> High Impact
y-axis Foundational --> Advanced
"服务发现": [0.25, 0.2]
"负载均衡": [0.45, 0.25]
"API Gateway": [0.35, 0.4]
"熔断降级": [0.55, 0.5]
"限流": [0.65, 0.6]
"链路追踪": [0.5, 0.75]
"配置中心": [0.4, 0.7]
"灰度发布": [0.75, 0.9]
```
> [!question] 开篇思考
> 假设你有 10 个微服务,每个服务有 3 个实例,总共 30 个服务实例。手动维护它们的地址列表会带来哪些问题?你能想到几种解决方案?
答案藏在下面每一个章节里——从服务发现到流量治理,每一层都在解决特定维度的复杂度。
## 服务发现
服务实例的动态变化(扩容、缩容、故障重启)让硬编码地址成为不可能。服务要回答的核心问题是:**我怎么找到你?**
### 两种模式
```mermaid
graph TB
subgraph Client["客户端发现模式"]
A[Service A] -->|1.查询注册中心| R1[(Registry)]
R1 -->|2.返回实例列表| A
A -->|3.自选实例直连| B[Service B]
end
subgraph Server["服务端发现模式"]
C[Service C] -->|请求| P[LoadBalancer Proxy]
P -->|查询注册中心| R2[(Registry)]
R2 -->|返回实例| P
P -->|转发| D[Service D]
end
```
**对比**:
| 维度 | 客户端发现 | 服务端发现 |
|------|-----------|-----------|
| 典型实现 | Eureka、Consul、Nacos | Nginx、Envoy、K8s Service |
| 耦合度 | 客户端嵌入注册逻辑 | 透明代理,客户端无感知 |
| 性能 | 直连调用,延迟最低 | 多一跳代理开销 |
| 运维复杂度 | 每门语言需独立 SDK | 统一部署代理,技术无关 |
> [!tip] 推荐路径
> Kubernetes 环境下直接用 **K8s Service + Ingress**;非容器环境优先考虑 **Nacos**(阿里开源,国内生态好)。
> 如果团队已有 Consul 基础设施,无需迁移——它在中小规模集群中表现优秀。
### 实战:Nacos 服务发现
```go
// Nacos 服务注册 & 拉取
import (
"github.com/nacos-group/nacos-sdk-go/v2/clients"
"github.com/nacos-group/nacos-sdk-go/v2/common/constant"
)
// 创建注册客户端
sc := constant.ServerConfig{
IpAddr: "127.0.0.1",
Port: 8848,
}
// 注册服务实例
client, _ := clients.NewNamingClient(
value_map.NewValueMap(map[string]any{"serverConfig": sc}),
)
client.RegisterInstance(naming_param.RegisterInstanceParam{
Ip: "10.0.0.1",
Port: 8080,
ServiceName: "order-service",
ClusterName: "DEFAULT",
})
// 拉取某服务的可用实例列表
instances, _ := client.SelectInstances(naming_param.SelectInstanceParam{
ServiceName: "order-service",
})
```
> [!warning] 注意
> Nacos 同时支持临时实例(故障自动摘除)和持久实例(基于 etcd)。生产环境中临时实例更常见——节点挂了会自动从列表中剔除。
**关键问题**:服务下线时,如何确保正在处理的请求不会被打断?这引出了健康检查和优雅关闭的概念。
## API Gateway
API Gateway 是所有外部请求的统一入口,承担以下职责:
```mermaid
graph LR
Client["客户端"] --> GW["API Gateway"]
GW --> Auth["鉴权 & 限流"]
GW --> Route["路由转发"]
GW --> Transform["协议转换"]
GW --> Log["日志 & 监控"]
GW -.-> CB[(配置中心)]
```
常见实现:**Kong、APISIX、Spring Cloud Gateway、Nginx**。
### 网关放什么?
> [!summary] 网关职责清单
>
> - ✅ 鉴权与认证
> - ✅ 限流熔断
> - ✅ HTTPS 终结
> - ✅ 请求/响应转换
> - ✅ 路由分发
> - ❌ 业务逻辑 — 网关保持瘦,重逻辑应下沉到业务服务
> [!question] 权衡题
> 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?你怎么设计才能兼顾统一性和灵活性?
2026-04-30 08:58:36 +08:00
> 👉 [[02-服务治理/网关鉴权策略]] — 分层鉴权模型详解
2026-04-29 20:39:17 +08:00
## 服务间通信
### RPC vs RESTful API
| 维度 | REST over HTTP/JSON | gRPC (HTTP/2) |
|------|---------------------|---------------|
| 性能 | 序列化开销大 | Protocol Buffers,二进制高效 |
| 人类可读 | URL + JSON 直观 | 需要 Proto 定义辅助理解 |
| 语言兼容性 | 广泛,任何能发 HTTP 的语言都能用 | 需要代码生成 |
| 适用场景 | 对外 API、跨团队/跨组织调用 | 内部服务间高频强类型调用 |
```go
// Go 示例:gRPC Protobuf 定义
// proto/order/v1/order.proto
syntax = "proto3";
package order.v1;
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
}
message CreateOrderRequest {
string user_id = 1;
repeated Item items = 2;
float total = 3;
}
message Item {
string product_id = 1;
int32 quantity = 2;
}
```
2026-04-30 09:08:09 +08:00
### 典型请求链路
一次下单操作会串联多个服务和两种通信模式:
```mermaid
sequenceDiagram
participant C as Client
participant GW as API Gateway
participant O as Order Service
participant I as Inventory Service
participant P as Payment Service
participant MQ as Message Queue
C->>GW: POST /orders (HTTP/JSON)
GW->>O: CreateOrder (gRPC)
rect rgb(200, 220, 250)
Note over O,P: 同步 RPC 链
O->>I: CheckStock (gRPC)
I-->>O: {ok: true, quantity: 1}
O->>P: Charge (gRPC)
P-->>O: {success: true}
end
O-->>GW: {orderId: "ORD-001"}
GW-->>C: 200 OK
rect rgb(255, 240, 220)
Note over O,MQ: 异步事件解耦
O->>MQ: Publish OrderCreated Event
MQ->>N: Consume → SendSms
Note right of N: 通知类场景,不阻塞主流程
end
```
> [!keypoint] 关键洞察
>
> - 同步链路(蓝色):**gRPC 直连**,延迟低,适合核心事务(查库存 → 扣款)
> - 异步链路(橙色):**消息队列**,解耦非关键路径(发短信、发积分),即使失败也不影响下单主干
> - API Gateway 负责统一鉴权和限流,下游服务之间通过 gRPC 高效通信
>
> 这就是「同步 RPC 保性能,异步 MQ 保解耦」的组合拳。
2026-04-29 20:39:17 +08:00
### 同步 vs 异步
> [!question] 关键时刻的判断
> 用户点击"提交订单"后,系统要做:① 创建订单记录 ② 扣减库存 ③ 扣款 ④ 发送短信通知。哪些步骤必须同步完成?哪些可以异步处理?为什么?
**答案**:①~③需要立即得到结果或保证一致性,走同步流程;④纯粹的通知类场景完全异步,用消息队列解耦。即使扣款失败,短信也没必要发出。
```go
// 异步消息:Go + RabbitMQ 简易示例
producer.Publish("orders", []byte(`{
"orderId": "ORD-2026-001",
"userId": "user-42",
"amount": 199.00
}`))
// 消费者监听
messages := consumer.Consume("order-notifications")
for msg := range messages {
sendSms(msg.OrderId) // 不阻塞主流程
}
```
> [!tip] 选型建议
> 事件驱动架构(Event-driven)适合「最终一致性」场景;强一致事务需求仍依赖 SAGA/TCC 等分布式事务方案。
## 容错模式
微服务最大的挑战是 **网络不可靠**。分布式系统的每个远程调用都可能超时、被拒绝、或部分成功。必须提前设计降级和恢复策略。
### 重试机制
> [!warning] 关键原则
> 只对 **幂等操作** 重试!POST 创建操作直接重试会导致重复数据。GET、PUT(同参数)适合重试。
```go
// exponential backoff + jitter(防雪崩)
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
}
```
### 熔断器 (Circuit Breaker)
当下游服务频繁失败时,快速失败避免线程堆积雪崩:
```mermaid
2026-04-30 09:08:09 +08:00
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
2026-04-29 20:39:17 +08:00
```
```go
// gobreaker 示例
cb := state.NewCB(state.Settings{
Name: "UserService",
MaxElems: 10,
WaitRetry: 5 * time.Second,
ReadyToTrip: func(counts Counters) bool {
return counts.Failures > 5
},
})
result := cb.Execute(doRequest)
if result.Err != nil {
// 熔断打开,立即短路返回错误,不调用下游
}
```
### 健康检查
```mermaid
sequenceDiagram
participant LB as 负载均衡器
participant S1 as 服务实例 A
participant S2 as 服务实例 B
LB->>S1: "GET /health -> 200 OK"
LB->>S2: "GET /health -> 503 ERROR"
Note over LB: "标记 S2 不健康,剔除出池"
LB->>S2: "GET /health -> 200 OK"
Note over LB: "逐步恢复,重新加入池"
```
- **存活探针 (Liveness)**:判断"进程是否还活着",挂了则重启
- **就绪探针 (Readiness)**:判断"是否能接收流量",未就绪则剔除负载均衡池
- ** readiness 比 liveness 更重要**——一个进程活着但数据库连接耗尽时,应该停止接收流量而不是重启
> [!summary] 舱壁隔离 (Bulkhead)
>
> 为不同下游服务分配独立的线程池/连接池:
>
> ```go
> // poolgroup 示例:为不同服务隔离连接池
> orderPool := pool.New(10, 100) // 最多10个活跃,上限100
> userPool := pool.New(5, 50)
> // 订单服务占满连接不会影响用户服务的调用
> ```
>
> 超时 + 熔断 + 舱壁三者组合,构成了经典的稳定性防护三件套。
## 负载均衡
```mermaid
flowchart LR
subgraph "客户端负载均衡 (Client-Side)"
A[Service A] --> RR[轮询]
A --> LH[最少连接]
A --> CH[一致性哈希]
end
subgraph "服务端负载均衡 (Server-Side)"
B[Service B] --> VIP[[Virtual IP]]
VIP --> B1[B-1]
VIP --> B2[B-2]
VIP --> B3[B-3]
end
```
| 策略 | 说明 | 适用场景 |
|------|------|---------|
| Round Robin | 轮询分配 | 各实例负载相近,请求耗时均匀 |
| Least Connections | 最少连接优先 | 长连接场景,请求处理时间差异大 |
| Consistent Hash | 哈希路由,同一 key 始终到同实例 | 有状态缓存、会话绑定场景 |
| Random | 随机选择 | 最简单但不一定最优 |
> [!tip] K8s 中的实践
> K8s Service 默认 Round Robin;如需更精细的控制,可配合 **Istio VirtualService** 实现基于权重/头部的流量分发。
## 配置管理
> [!question] 引出配置中心的必要性
> 假设你的 50 个服务实例都要连同一个数据库。现在需要把数据库密码从 `password1` 改为 `password2`,你会怎么改?登录每台机器改配置文件?滚动重启所有 Pod?还是……有更好的办法?
当服务实例数以百计时,手动管理配置是不可想象的。配置中心提供 **集中化、动态化、版本化** 的配置管理能力。
### 核心价值
```mermaid
flowchart LR
Dev["开发/运维"] --> CC[(配置中心)]
CC -->|热更新推送| S1[Service A]
CC -->|热更新推送| S2[Service B]
CC -->|热更新推送| S3[Service C]
CC -.->|版本管理| V1[v1.0 历史配置]
CC -.->|版本管理| V2[v1.1 当前配置]
```
| 能力 | 说明 |
|------|------|
| 动态刷新 | 修改配置后立即生效,无需重启 |
| 环境隔离 | dev / test / prod 配置分离 |
| 版本管理与回滚 | 每次变更有迹可循,一键回滚 |
| 权限控制 | 敏感配置(密钥、Token)按角色隔离 |
### Nacos Config 示例
```go
// 动态监听配置变更
configClient, _ := clients.NewConfigClient(value_map.NewValueMap(map[string]any{
"serverConfig": sc,
}))
content, _ := configClient.GetConfig(config_param.GetConfigParam{
DataId: "order-service.yaml",
Group: "DEFAULT_GROUP",
})
// 监听配置变化
configClient.ListenChange(config_param.ListenChangeParam{
DataId: "order-service.yaml",
Group: "DEFAULT_GROUP",
Callback: func(content string) {
fmt.Println("配置更新了:", content)
// 重新加载配置...
},
})
```
> [!note] 主流对比
>
> - **Nacos Config**:阿里开源,国内首选,兼顾客户端发现+配置管理
> - **Apollo**:携程开源,配置审核流程完善,适合大型团队
> - **Spring Cloud Config**:与 Spring 生态深度集成,但需额外 Git/DB 存储后端
> - **K8s ConfigMap**:原生方案,适合纯 K8s 环境,不支持热更新需配合 Sidecar
## 分布式链路追踪
> [!question] 定位问题的困难
> 用户反映下单慢,你的系统由订单、支付、库存、会员 4 个服务串联而成。没有工具的情况下,你要怎么知道是哪个服务拖慢了整体响应时间?
单个服务的日志只能告诉你局部信息。分布式链路追踪将一次请求跨越多个服务的完整调用链串起来,形成全局视图。
### 核心概念
```mermaid
flowchart TB
Req["请求 trace_id=abc123"] --> Span1["订单服务 - 20ms"]
Span1 --> Span2["库存服务 - 80ms"]
Span1 --> Span3["会员服务 - 15ms"]
Span2 --> Span4["数据库查询 - 60ms"]
style Span2 fill:#ff9999
```
- **Trace**:一次完整请求的调用链
- **Span**:链路中的一个执行片段(如一次 HTTP 调用、一次 SQL 查询)
- **Context Propagation**:通过 Header 传递 trace_id/span_id,贯穿整条链路
### 主流方案
| 方案 | 协议 | 存储后端 | 特点 |
|------|------|---------|------|
| **OpenTelemetry** | OTLP | Prometheus/Jaeger/Zipkin | CNCF 标准,厂商中立,未来趋势 |
| **Jaeger** | Jaeger native | Cassandra/Elasticsearch | Uber 开源,UI 友好 |
| **SkyWalking** | SkyWalking | MySQL/Elasticsearch | 国产,零侵入 Agent,中文文档完善 |
### OpenTelemetry Go 示例
```go
// 初始化 tracer provider
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcherExporter(exporter), // 异步上报,不阻塞
)
tracer := provider.Tracer("order-service")
// 在 handler 中创建 span
func handleOrder(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleOrder")
defer span.End()
// 传播 context 到下游
resp, err := callInventoryService(ctx) // SDK 自动注入 trace context
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
return
}
w.Write(resp.Body)
}
```
> [!tip] 渐进式落地建议
> 不要试图一开始就采集全部 Span。先从 **核心链路**(下单、支付)入手,采集 rate 设为 100%;稳定后再逐步扩展,日常采样率可降至 5%~10% 以节省存储。
## 灰度发布与流量治理
> [!question] 如何安全地上线?
> 你修复了一个紧急 Bug,但这次改动涉及核心支付链路。直接全量发布一旦出问题损失巨大。有没有办法先小范围验证,确认没问题再放量?
灰度发布(也叫金丝雀发布)是降低变更风险的核心手段。它让你将新版本逐步推向一小部分用户,观察指标正常后再全量。
### 灰度策略
```mermaid
flowchart LR
subgraph "Canary Release"
A["1% 流量"] --> B{"指标正常?"}
B -- 是 --> C["10% 流量"]
B -- 否 --> D["自动回滚"]
C --> E{"指标正常?"}
E -- 是 --> F["50% 流量"]
E -- 否 --> D
F --> G{"指标正常?"}
G -- 是 --> H["100% 全量"]
G -- 否 --> D
end
```
### 灰度路由规则
```yaml
# Istio VirtualService 示例
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: order-route
spec:
hosts: ["order-service"]
http:
# 灰度规则:header 携带 canary=true 的用户走 v2
- match:
- headers:
x-canary:
exact: "true"
route:
- destination:
host: order-service
subset: v2 # 新版本
weight: 100
# 默认:全部走 v1(线上稳定版)
- route:
- destination:
host: order-service
subset: v1 # 旧版本
weight: 100
```
### 蓝绿 vs 灰度
> [!summary] 两种发布策略对比
>
> | 维度 | 蓝绿部署 | 灰度发布 (金丝雀) |
> |------|---------|-----------------|
> | 切换方式 | 一次性全切 | 逐步放量 |
> | 资源消耗 | 双倍(新旧并行) | 初期少量副本 |
> | 回滚速度 | 秒级(切回即可) | 同样秒级 |
> | 风险等级 | 中高 | 低 |
> | 适合场景 | 大型变更、重大版本 | 日常迭代、高风险链路 |
> [!tip] 实战经验
> 无论哪种发布方式,**必须有可量化的观测指标作为决策依据**——错误率、P99 延迟、CPU/内存使用率。不要凭感觉放量,让数据说话。
## 关联笔记
- [[hzh/MS/01-基础概念]] — 微服务的整体架构认知
- [[hzh/MS/05-部署运维]] — K8s Service 天然提供负载均衡和服务发现
- [[hzh/MS/03-数据一致性]] — 微服务下的数据分片与一致性策略
- [[hzh/MS/04-可观测性]] — 日志、指标、链路追踪的完整体系