vault backup: 2026-05-05 20:20:15

This commit is contained in:
2026-05-05 20:20:16 +08:00
parent 57d1495dd1
commit 3805434398
28 changed files with 3866 additions and 1643 deletions
+153
View File
@@ -0,0 +1,153 @@
---
tags: [microservice, api-gateway, kong, apisix, spring-cloud-gateway]
create time: 2026-05-05
---
# API 网关
## 概述
API Gateway 是所有外部请求的统一入口,承担以下职责:
```mermaid
graph LR
Client["客户端 App / Web"] --> GW["API Gateway"]
GW --> Auth["鉴权 & 限流"]
GW --> Route["路由转发"]
GW --> Transform["协议转换"]
GW --> Log["日志 & 监控"]
GW -.-> CB[(配置中心)]
```
常见实现:**Kong、APISIX、Spring Cloud Gateway、Nginx + Lua**。
## 网关应该做什么?
> [!summary] 网关职责清单
| ✅ 适合放 | ❌ 不应该放 |
|---------|-----------|
| 鉴权与认证 | 业务逻辑(如订单创建) |
| 限流熔断 | 复杂的数据聚合查询 |
| HTTPS 终结 | 大量 CPU 密集型计算 |
| 请求/响应转换 | 涉及数据库写操作 |
| 路由分发 | 跨服务事务管理 |
| 日志 & 监控 | 邮件/短信发送等异步任务 |
> [!tip] 核心原则
> **网关保持瘦**——它是 traffic cop(交通警察),不是 warehouse manager(仓库管理员)。重逻辑应下沉到业务服务。
### 分层鉴权模型
> [!question] 权衡题
> 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?
**分层鉴权架构**:
```mermaid
graph TB
External["外部用户"] --> GW["API Gateway<br/>JWT 校验 + OAuth2"]
GW --> S1[公开接口<br/>无需额外鉴权]
GW --> S2[内部接口<br/>Service Token]
S1 --> UserSvc[[User Service]]
S2 --> OrderSvc[[Order Service]]
S2 --> PaySvc[[Payment Service]]
OrderSvc -->|"mTLS + JWT"| DB[(DB)]
PaySvc -->|"mTLS + JWT"| DB
```
- **第一层(网关)**:校验外部用户的 JWT/OAuth Token,拦截非法请求
- **第二层(服务间)**:通过 Service Token 或 mTLS 保证调用方身份可信
- **第三层(数据层)**:RBAC / ABAC 细粒度权限控制
## 路由策略
### 基础路由规则
```yaml
# APISIX 路由示例
routes:
- uri: /api/orders/*
upstream:
nodes:
"order-service:8080": 1
type: roundrobin
- uri: /api/payments/*
upstream:
nodes:
"payment-service:8080": 1
type: roundrobin
```
### 高级路由策略
| 策略 | 场景 | 示例 |
|------|------|------|
| **路径匹配** | 按 URL 前缀路由 | `/api/v1/*` → v1 版本服务 |
| **Header 匹配** | A/B 测试、灰度发布 | `x-canary: true` → 新版本 |
| **权重路由** | 金丝雀发布 | 90% 流量 → v1, 10% → v2 |
| **正则匹配** | 复杂 URL 模式 | `/api/user/(?P<id>\d+)/*` |
## 网关插件体系
网关的核心价值在于 **可插拔的中间件链**,类似 Express/Koa 的 middleware 概念。
```mermaid
graph LR
Req["Request"] -->|Plugin 1| RateLimiter["限流"]
RateLimiter -->|Plugin 2| Auth["鉴权"]
Auth -->|Plugin 3| CBR["熔断"]
CBR -->|Plugin 4| Transform["协议转换"]
Transform -->|Plugin 5| Logger["日志"]
Logger --> Backend["Backend Service"]
```
### 常用插件列表
| 插件 | 作用 | 推荐算法 |
|------|------|---------|
| **Rate Limiting** | 防刷限流 | 令牌桶 / 漏桶 |
| **CORS** | 跨域处理 | 预检缓存 |
| **IP 黑白名单** | 访问控制 | Redis Bloom Filter |
| **Response Rewrite** | 修改响应体 | JSON Patch |
| **Request Transformation** | Header/Body 改写 | Map-based |
| **Prometheus Exporter** | 指标采集 | 自动埋点 |
| **Fault Injection** | 混沌测试注入延迟/错误 | 按比例注入 |
## 网关的高可用设计
```mermaid
graph TB
DNS["DNS / CLB"] --> GW1["Gateway Node 1"]
DNS --> GW2["Gateway Node 2"]
subgraph GW_Cluster["网关集群"]
GW1
GW2
end
GW1 --> B1["backend-pool-v1"]
GW2 --> B2["backend-pool-v1"]
B1 --> Svc1[[order-service]]
B1 --> Svc2[[user-service]]
B2 --> Svc1
B2 --> Svc2
GW1 -.-> Config["配置中心 (同步)"]
GW2 -.-> Config
```
**关键点**:
- 网关无状态设计,可横向扩展
- 配置通过注册中心实时同步,无需重启
- 多节点前接负载均衡器(CLB/Nginx)
## 关联笔记
- [[02-服务治理/服务发现/README]] — 网关需要订阅服务实例列表
- [[02-服务治理/流量治理/README]] — 高级路由和灰度发布的延伸
- [[hzh/MS/API 设计原则]] — API Gateway 的设计与 REST/gRPC 选型
+54
View File
@@ -0,0 +1,54 @@
---
tags: [microservice, service-governance]
create time: 2026-04-29 12:02
---
# 服务治理
## 概述
服务治理是微服务架构的"操作系统"——它不直接实现业务逻辑,但决定了系统能否在复杂、高并发、多团队协作的环境中稳定运转。
> [!question] 开篇思考
> 假设你有 10 个微服务,每个服务有 3 个实例,总共 30 个服务实例。手动维护它们的地址列表会带来哪些问题?你能想到几种解决方案?
答案藏在下面每一个章节里——从服务发现到流量治理,每一层都在解决特定维度的复杂度。
## 知识体系
```mermaid
graph LR
A["服务发现"] --> B["API 网关"]
B --> C["服务间通信"]
C --> D["容错模式"]
D --> E["配置管理"]
E --> F["分布式追踪"]
F --> G["流量治理"]
G --> H["安全机制"]
```
| # | 主题 | 核心问题 |
|---|------|----------|
| 1 | [[02-服务治理/服务发现/README]] | 服务如何找到彼此?注册中心的工作原理是什么? |
| 2 | [[02-服务治理/API网关/README]] | 统一入口承担哪些职责?网关应该瘦还是胖? |
| 3 | [[02-服务治理/服务间通信/README]] | RPC vs RESTful?同步 vs 异步怎么选? |
| 4 | [[02-服务治理/容错模式/README]] | 网络不可靠,故障怎么隔离和恢复? |
| 5 | [[02-服务治理/配置管理/README]] | 上百个服务的配置如何统一管理? |
| 6 | [[02-服务治理/分布式追踪/README]] | 请求穿越多个服务后,如何追踪链路? |
| 7 | [[02-服务治理/流量治理/README]] | 灰度发布如何做?高级路由策略有哪些? |
| 8 | [[02-服务治理/安全机制/README]] | 服务间调用如何认证和授权?mTLS 是什么? |
### 学习建议
> [!tip] 学习路径
> 按编号顺序阅读。前三个主题是理解服务治理的基础,后面的容错、配置、追踪是在此之上的稳定性保障。
> [!question] 带着问题读
> 每篇文章都设计了启发式问题。先自己想一遍答案,再看文档,效果更好。
### 关联笔记
- [[01-基础概念]] — 微服务的整体架构认知
- [[03-数据一致性]] — 服务调用链路上的数据一致性问题
- [[04-可观测性]] — 日志、指标、链路追踪的完整体系
- [[05-部署运维]] — K8s Service 天然提供负载均衡和服务发现
@@ -0,0 +1,209 @@
---
tags: [microservice, distributed-tracing, opentelemetry, jaeger, skywalking]
create time: 2026-05-05
---
# 分布式链路追踪
## 概述
单个服务的日志只能告诉你局部信息。分布式链路追踪将一次请求跨越多个服务的完整调用链串起来,形成全局视图。
> [!question] 定位问题的困难
> 用户反映下单慢,你的系统由订单、支付、库存、会员 4 个服务串联而成。没有工具的情况下,你要怎么知道是哪个服务拖慢了整体响应时间?
## 核心概念
```mermaid
flowchart TB
Req["请求 trace_id=abc123"] --> Span1["Span #1<br/>API Gateway<br/>5ms"]
Span1 --> Span2["Span #2<br/>Order Service<br/>70ms"]
Span1 --> Span3["Span #3<br/>User Service<br/>15ms"]
Span2 --> Span4["Span #4<br/>Inventory DB Query<br/>60ms"]
style Span2 fill:#ff9999
style Span4 fill:#ffcc99
```
| 概念 | 说明 |
|------|------|
| **Trace** | 一次完整请求的调用链,由一个唯一的 `trace_id` 标识 |
| **Span** | 链路中的一个执行片段(如一次 HTTP 调用、一次 SQL 查询),有独立的 `span_id` |
| **Parent-Child** | Span 之间通过 `parent_span_id` 建立父子关系,形成树状结构 |
| **Context Propagation** | 通过 Header 传递 `trace_id`/`span_id`,贯穿整条链路 |
| **Sampling** | 不是每条请求都采样,按比例或策略选择,降低存储开销 |
### Trace/Span 数据结构
```json
{
"traceId": "abc123def456...",
"spans": [
{
"spanId": "span-001",
"parentId": null,
"operationName": "POST /orders",
"startTime": "2026-05-05T10:00:00.000Z",
"durationMs": 120,
"tags": {
"http.method": "POST",
"http.url": "/api/orders",
"http.status_code": 200
},
"logs": [
{
"timestamp": "2026-05-05T10:00:00.050Z",
"fields": [{"key": "event", "value": "order.created"}]
}
]
},
{
"spanId": "span-002",
"parentId": "span-001",
"operationName": "GetUserById (gRPC)",
"startTime": "2026-05-05T10:00:00.010Z",
"durationMs": 15,
"tags": {
"rpc.system": "grpc",
"rpc.service": "UserService",
"rpc.method": "GetUser"
}
}
]
}
```
## 上下文传播 (Context Propagation)
`trace_id` 如何从上游服务传递到下游?
### HTTP 场景:W3C Trace Context 标准
```json
// HTTP Header 中实际传递的字段
{
"traceparent": "00-abc123def456...-789ghi012jkl-01",
"tracestate": "congo=t61rcWkgMzE"
}
```
格式:`version-trace_id-span_id-flags`
```go
// OpenTelemetry SDK 自动处理 context 注入和提取
func Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// 从入站请求提取 trace context
ctx := propagator.Extract(r.Context(), headerReader{r.Header})
// 创建新的 span
ctx, span := tracer.Start(ctx, "handleOrder")
defer span.End()
// 将 trace context 注入到出站请求
r = r.WithContext(ctx)
propagator.Inject(ctx, headerWriter{r.Header})
next.ServeHTTP(w, r)
})
}
```
### MQ 场景:Message Header 透传
```go
// RocketMQ 生产者 - 自动注入 trace context
msg := rocketmq.NewMessage("order-created", payload)
msg.WithProperty("trace_id", currentTraceID) // 手动注入
msg.WithProperty("span_id", currentSpanID)
// RocketMQ 消费者 - 恢复 trace context
traceID := msg.GetProperty("trace_id")
span, _ := tracer.Start(ctx, "processOrderCreated",
trace.WithSpanKind(trace.SpanKindConsumer))
```
## 主流方案对比
| 方案 | 协议 | 存储后端 | 侵入程度 | 特色能力 |
|------|------|---------|---------|---------|
| **OpenTelemetry** | OTLP | Prometheus/Jaeger/Zipkin | SDK + Auto-Instrumentation | CNCF 标准,厂商中立,未来趋势 |
| **Jaeger** | Jaeger native | Cassandra/Elasticsearch | Agent / SDK | Uber 开源,UI 友好,支持业务 Tags |
| **SkyWalking** | SkyWalking | MySQL/Elasticsearch/ES | 零侵入 Java Agent | 国产,中文文档完善,APM 一体 |
> [!tip] 选型建议
> 新项目优先选 **OpenTelemetry**——它是行业标准,未来会被所有工具支持。如果团队需要开箱即用的 APM,**SkyWalking** 的 Java Agent 零侵入方案是快速上手的最佳选择。
## OpenTelemetry Go 实战
```go
// 初始化 TracerProvider
provider := sdktrace.NewTracerProvider(
sdktrace.WithBatcherExporter(exporter), // 异步批量上报,不阻塞
)
defer provider.Shutdown(context.Background())
tracer := provider.Tracer("order-service")
func handleOrder(w http.ResponseWriter, r *http.Request) {
ctx, span := tracer.Start(r.Context(), "handleOrder")
defer span.End()
// 设置丰富的属性,方便查询和过滤
span.SetAttributes(
attribute.String("http.method", r.Method),
attribute.Int("http.status_code", http.StatusOK),
attribute.String("user.id", getUserID(r)),
)
// 调用下游服务(trace context 自动传播)
resp, err := callInventoryService(ctx)
if err != nil {
span.RecordError(err)
span.SetStatus(codes.Error, err.Error())
w.WriteHeader(http.StatusBadGateway)
return
}
w.Write(resp.Body)
}
```
## 采样策略
```mermaid
flowchart LR
AllReq["全部请求"] --> Sampler{"采样决策"}
Sampler -->|"100%"| Core["核心链路全量采集"]
Sampler -->|"5~10%"| Normal["普通路径随机采样"]
Sampler -->|"0%"| Health["健康检查/内部心跳"]
Core --> Storage["Trace 存储"]
Normal --> Storage
Health --> Drop["丢弃"]
```
| 环境 | 采样率 | 理由 |
|------|--------|------|
| **开发 / 测试** | 100% | 方便调试,无存储压力 |
| **生产 - 核心链路** | 100% | 下单、支付等关键路径必须全量 |
| **生产 - 普通路径** | 5~10% | 平衡成本和覆盖率 |
| **生产 - 错误链路** | 100% | 出错时的请求优先保留 |
> [!note] 基于错误的智能采样
>
> 高级做法:**正常路径低采样,一旦检测到错误立即提升当前请求的采样率**。这样既省了存储,又能在出问题时有足够的数据回溯。
## 渐进式落地路线
> [!tip] 不要试图一开始就采集全部 Span
>
> 1. **第一步**:先接 Tracing,覆盖核心链路(下单、支付),rate=100%
> 2. **第二步**:加入 Metrics 监控(Prometheus + Grafana)
> 3. **第三步**:集中 Logging(Loki / ELK),与 trace_id 关联
> 4. **第四步**:全量上 OpenTelemetry Collector,统一管理
## 关联笔记
- [[02-服务治理/API网关/README]] — API Gateway 可以在入口处注入 trace_id
- [[04-可观测性/链路追踪/README]] — 更详细的链路追踪设计方法论
- [[02-服务治理/配置管理/README]] — 配置中心的动态刷新可以联动调整采样率
@@ -0,0 +1,181 @@
---
tags: [microservice, security, mTLS, JWT, OAuth2, zero-trust]
create time: 2026-05-05
---
# 安全机制
## 概述
微服务架构下,服务间通信的安全保障比单体应用复杂得多。每个服务的 API 都是潜在的攻击面,需要多层防御。
```mermaid
graph TB
subgraph "防御层"
L1["L1: 网络隔离<br/>VPC / 安全组"]
L2["L2: 传输加密<br/>mTLS / TLS"]
L3["L3: 身份认证<br/>JWT / Service Token"]
L4["L4: 授权控制<br/>RBAC / ABAC"]
L5["L5: 审计追踪<br/>操作日志"]
end
L1 -.-> L2 -.-> L3 -.-> L4 -.-> L5
```
> [!tip] Zero Trust 原则
> **永不信任,始终验证**。每个服务调用都要经过认证和授权,无论请求来自内网还是外网。
## 服务间认证
### mTLS (双向 TLS)
```mermaid
sequenceDiagram
participant Client as 调用方 Service
participant CA as Certificate Authority
participant Svc as 被调方 Service
Client->>CA: 申请证书 (SPIFFE ID)
Svc->>CA: 申请证书 (SPIFFE ID)
Client->>Svc: TLS Handshake + ClientCert
Svc->>Svc: 验证书 + SPIFFE ID
Svc-->>Client: ✅ 认证成功
note over Client,Svc: 建立加密通道
```
**mTLS 的核心优势**:
- 双方都持有对方可验证的证书,防止中间人攻击
- 证书自动轮换(配合 Istio/Linkerd 等服务网格)
- 零代码侵入——Sidecar 代理处理握手
### JWT / Service Token
适用于不具备 mTLS 基础设施的场景:
```go
// 生成 Service Token
token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{
"sub": "order-service", // 服务标识
"iss": "auth-server", // 签发者
"aud": "payment-service", // 目标服务
"exp": time.Now().Add(1*time.Hour).Unix(),
"iat": time.Now().Unix(),
})
tokenString, _ := token.SignedString(secretKey)
```
| 方案 | 安全性 | 复杂度 | 适用场景 |
|------|--------|--------|---------|
| **mTLS** | ⭐⭐⭐⭐⭐ | 中(需 PKI 基础设施) | 服务网格环境、K8s 内部通信 |
| **JWT + Shared Secret** | ⭐⭐⭐⭐ | 低 | 中小规模,快速上手 |
| **mTLS + JWT** | ⭐⭐⭐⭐⭐ | 中高 | 金融级安全要求 |
> [!tip] 最佳实践
> 在生产环境中,推荐 **mTLS + JWT 双层防护**:mTLS 保证通道安全,JWT 传递调用方的身份信息(谁在调用)。
## API 鉴权模型
### RBAC (基于角色的访问控制)
```mermaid
flowchart LR
User["用户"] -->|"拥有"| Role["角色"]
Role -->|"拥有"| Permission["权限"]
Permission -->|"访问"| Resource["资源"]
Admin["Admin"] --> ReadWrite
Editor["Editor"] --> ReadWrite
Viewer["Viewer"] --> ReadOnly
ReadWrite --> OrderCRUD
ReadOnly --> OrderRead
OrderCRUD --> CRUD["Create/Read/Update/Delete"]
OrderRead --> Read["Read Only"]
```
### ABAC (基于属性的访问控制)
适合细粒度场景:
```go
func canAccess(user User, resource Resource, action string) bool {
// 属性比较规则
rules := []Rule{
{
Condition: user.Department == resource.Department, // 同部门
Action: "read",
},
{
Condition: user.Role == "admin" || user.ID == resource.OwnerID,
Action: "write",
},
}
for _, r := range rules {
if r.Condition && r.Action == action {
return true
}
}
return false
}
```
## 输入校验与防攻击
### SQL 注入防护
```go
// ❌ 危险:字符串拼接
query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", userInput)
// ✅ 安全:参数化查询
rows, err := db.Query("SELECT * FROM users WHERE name = ?", userInput)
```
### XSS 防护
```go
// Go HTML template 自动转义
tpl.ExecuteTemplate(w, "page.html", data) // {{.Name}} 自动 HTML 转义
// JSON API 天然免疫(Content-Type: application/json)
```
### 常见攻击防护清单
| 威胁 | 防护手段 | 实现位置 |
|------|---------|---------|
| **SQL 注入** | 参数化查询 / ORM | 数据访问层 |
| **XSS** | 输出转义 / CSP Header | Web 框架 / Gateway |
| **CSRF** | SameSite Cookie / CSRF Token | 网关 / Session |
| **DDoS** | 限流 + WAF | API Gateway |
| **重放攻击** | Nonce + Timestamp | API 签名 |
| **凭证泄露** | HTTPS + Secure Cookie | 传输层 |
## API 签名(防重放攻击)
对高安全要求的 API,客户端需要对请求签名:
```
Signature = HMAC-SHA256(Secret, HTTP_Method + URL + Timestamp + Body)
```
```
GET /api/orders?userId=123&timestamp=1714900000&nonce=abc123
↓ 用 Secret 做 HMAC-SHA256 签名
a1b2c3d4e5f6...
```
服务端验证步骤:
1. 检查 `timestamp` 是否在允许窗口内(如 ±5 分钟)
2. 检查 `nonce` 是否已使用(Redis SetNX,TTL 5min)
3. 用同样的算法计算签名,比对是否一致
## 关联笔记
- [[02-服务治理/API网关/README]] — 网关层的统一鉴权和限流
- [[02-服务治理/服务发现/README]] — 服务注册时也需要安全认证
- [[hzh/MS/API 设计原则]] — API 设计中的安全考量
@@ -0,0 +1,288 @@
---
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]] — 熔断器的指标暴露和告警联动
@@ -0,0 +1,205 @@
---
tags: [microservice, service-discovery, consul, nacos, etcd, kubernetes]
create time: 2026-05-05
---
# 服务发现
## 概述
服务实例的动态变化(扩容、缩容、故障重启)让硬编码地址成为不可能。服务要回答的核心问题是:**我怎么找到你?**
> [!question] 引出问题
> 假设你的订单服务有 3 个实例,运行在 `10.0.1.1:8080`、`10.0.1.2:8080`、`10.0.1.3:8080`。当其中一个实例扩容下线时,调用方如何得知最新的地址列表?手动改配置滚动重启?还是……有更好的办法?
## 两种发现模式
```mermaid
graph TB
subgraph Client["客户端发现模式"]
A[Service A] -->|1.查询注册中心| R1[(Registry)]
R1 -->|2.返回实例列表| A
A -->|3.自选实例直连| B[Service B]
noteA[客户端内置负载均衡逻辑]
end
subgraph Server["服务端发现模式"]
C[Service C] -->|请求| P[LoadBalancer Proxy]
P -->|查询注册中心| R2[(Registry)]
R2 -->|返回实例| P
P -->|转发| D[Service D]
noteC[客户端无感知,透明代理]
end
```
**对比**:
| 维度 | 客户端发现 | 服务端发现 |
|------|-----------|-----------|
| 典型实现 | Eureka、Consul、Nacos | Nginx、Envoy、K8s Service |
| 耦合度 | 客户端嵌入注册逻辑 | 透明代理,客户端无感知 |
| 性能 | 直连调用,延迟最低 | 多一跳代理开销 |
| 运维复杂度 | 每门语言需独立 SDK | 统一部署代理,技术无关 |
| 灵活性 | 客户端可自定义路由策略 | 受限于代理能力 |
### 客户端发现的优劣势分析
**优势**:
- 直连调用,少了一跳网络开销
- 客户端可以根据本地缓存做智能选择(如就近节点优先)
- 框架成熟,Eureka/Consul 社区案例丰富
**劣势**:
- 每个服务实现中都需要集成 SDK,跨语言迁移成本高
- 客户端需要处理实例列表的缓存与刷新逻辑
- 新增服务时需要修改调用方的代码或配置
### 服务端发现的优劣势分析
**优势**:
- 完全解耦——调用方只需知道 Proxy 地址
- 统一治理入口,可以在代理层加限流、熔断等逻辑
- 适合多语言团队,无需各自维护 SDK
**劣势**:
- 额外一跳增加延迟(通常 <1ms,高吞吐场景累积明显)
- Proxy 本身成为单点瓶颈,必须做横向扩展
- 调测复杂度上升——出了问题要看两层日志
## 主流注册中心选型
### Nacos(推荐国内团队首选)
阿里开源,支持 AP(临时实例)+ CP(持久实例)双模型,同时提供配置管理功能。
```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",
})
```
### Consul
HashiCorp 出品,基于 Raft 共识协议(CP),天然支持健康检查和 DNS 接口。
**核心特点**:
- 内建 KV 存储,可作为轻量配置中心
- 支持 multi-datacenter 部署
- DNS 接口:`order-service.service.consul` 可直接解析 IP
### etcd + K8s Service
纯容器环境下最自然的选择。etcd 存数据,K8s 提供完整的发现和负载均衡体系。
```yaml
# K8s Service — 服务端发现模式的代表
apiVersion: v1
kind: Service
metadata:
name: order-service
spec:
selector:
app: order
ports:
- port: 80
targetPort: 8080
type: ClusterIP
```
调用方只需 `http://order-service:80`,K8s 通过 iptables/IPVS 自动实现负载均衡。
### Eureka
Netflix 开源,AP 模型。**注意**:Eureka 2.x 已开源关闭,不建议新项目使用。已有系统在迁移到 Nacos 或 Consul 之前可以继续用。
## 关键机制
### 服务注册流程
```mermaid
sequenceDiagram
participant S as 服务实例
participant R as 注册中心
participant H as 健康检查器
S->>R: 1. Register(instance)
R-->>S: 2. ACK
Note over S,R: 实例写入内存 / etcd
loop 定时心跳
S->>R: 3. Heartbeat (每 5s)
R-->>S: 4. ACK
end
R->>H: 5. 超过心跳间隔未收到 → 标记不健康
H->>R: 6. 摘除实例
```
### 临时实例 vs 持久实例
| 类型 | 宕机检测方式 | 数据存储 | 适用场景 |
|------|-------------|---------|---------|
| **临时实例 (ephemeral)** | 心跳超时自动摘除 | 内存 | Web 服务、应用实例 |
| **持久实例 (persistent)** | 主动注销才摘除 | etcd | 数据库连接、外部依赖 |
> [!warning] 常见陷阱
> 把数据库当成临时实例注册 —— 数据库挂了不需要被 "检测到",它自己控制启停。应该用持久实例。
### 优雅上下线
服务下线时不能直接断网,否则正在处理的请求会失败。
**上线流程**:
1. 进程启动 → 加载配置、初始化连接池
2. `/ready` 探针返回 200 → 注册中心注册 + K8s 加入负载均衡池
3. 开始接收流量
**下线流程**:
1. 注册中心注销实例 → 停止接收新请求
2. 等待正在处理的请求完成(最大等待 N 秒)
3. 关闭连接池、保存状态
4. 进程退出
> [!tip] K8s Pod 优雅终止
> ```yaml
> spec:
> terminationGracePeriodSeconds: 30 # 最多等 30 秒再 SIGKILL
> ```
> 配合 `preStop` Hook 可以提前摘除流量:
> ```yaml
> lifecycle:
> preStop:
> exec:
> command: ["sh", "-c", "sleep 15"] # 给负载均衡摘流的时间
> ```
## 关联笔记
- [[02-服务治理/API网关/README]] — API Gateway 是服务发现的用户之一
- [[02-服务治理/容错模式/README]] — 健康检查是容错体系的基础
- [[05-部署运维/Kubernetes/README]] — K8s Service 的服务发现原理
@@ -0,0 +1,201 @@
---
tags: [microservice, rpc, rest, grpc, message-queue, event-driven]
create time: 2026-05-05
---
# 服务间通信
## 概述
微服务之间如何通信,是架构设计的核心决策之一。选错了通信方式,后面会带来一系列问题:耦合度太高、性能瓶颈、或者运维复杂度爆炸。
## RPC vs RESTful API
### 对比分析
| 维度 | REST over HTTP/JSON | gRPC (HTTP/2) | GraphQL |
|------|---------------------|---------------|---------|
| **性能** | 序列化开销大 | Protocol Buffers,二进制高效 | 中等(JSON) |
| **人类可读** | URL + JSON 直观 | 需要 Proto 定义辅助理解 | 查询语言自描述 |
| **语言兼容性** | 广泛,任何能发 HTTP 的语言都能用 | 需要代码生成 | 需客户端 SDK |
| **缓存** | 天然支持 HTTP 缓存 | 不支持(HTTP/2 多路复用替代) | 可设计缓存层 |
| **版本管理** | URL 路径 (/v1/, /v2/) | Proto 文件向前兼容 | Schema 演进工具 |
| **适用场景** | 对外 API、跨团队/跨组织调用 | 内部服务间高频强类型调用 | 前端聚合查询、移动端 |
```mermaid
graph TB
subgraph "何时选什么?"
D{"你的场景是?"}
D -->|"对外暴露 API"| REST["REST over HTTP"]
D -->|"内部服务高频调用"| GRPC["gRPC"]
D -->|"前端/移动端复杂查询"| GraphQL["GraphQL"]
D -->|"异步解耦通知"| MQ["消息队列"]
end
```
### 混合通信模式(推荐)
实际工程中很少单一选型。**最佳实践是混合使用**:
```mermaid
sequenceDiagram
participant C as 客户端
participant GW as API Gateway
participant O as Order Service
note over C,O: 外部请求 — 用 REST/gRPC
C->>GW: POST /orders (HTTP/JSON)
GW->>O: CreateOrder (gRPC)
note over O,O: 内部处理 — 同步 + 异步组合
O->>O: 创建订单记录 (本地事务)
O-->>GW: {orderId: "ORD-001"}
GW-->>C: 200 OK
O->>O: Publish OrderCreated Event (MQ)
Note right of O: 通知类场景异步处理
```
## 同步通信
### gRPC 协议详解
gRPC 基于 Protocol Buffers(Proto3),利用 HTTP/2 的多路复用特性,非常适合高性能的内部服务间调用。
```go
// proto/order/v1/order.proto
syntax = "proto3";
package order.v1;
service OrderService {
rpc CreateOrder(CreateOrderRequest) returns (CreateOrderResponse);
rpc GetOrder(GetOrderRequest) returns (Order);
rpc StreamOrders(StreamRequest) returns (stream Order); // 服务端流
}
message CreateOrderRequest {
string user_id = 1;
repeated Item items = 2;
float total = 3;
}
message Item {
string product_id = 1;
int32 quantity = 2;
}
enum OrderStatus {
UNKNOWN = 0;
PENDING = 1;
PAID = 2;
SHIPPED = 3;
CANCELLED = 4;
}
```
**Proto3 注意事项**:
- 字段编号(`= 1`, `= 2`)不能重编——破坏向后兼容
- 新增字段标记为 `optional` 时 Proto3 才能区分"未设置"和"默认值"
- `repeated` 字段空数组不会省略,需要特殊处理
### RESTful API 设计规范
```
GET /api/v1/orders → 获取订单列表
POST /api/v1/orders → 创建订单
GET /api/v1/orders/{id} → 获取订单详情
PUT /api/v1/orders/{id} → 更新订单(全量替换)
PATCH /api/v1/orders/{id} → 部分更新
DELETE /api/v1/orders/{id} → 删除订单
```
**核心原则**:
- **资源名词化**:URL 用名词复数,不用动词
- **无状态**:每个请求携带完整上下文(通过 Header 传 Token)
- **统一接口**:用标准 HTTP Method + Status Code 表达语义
## 异步通信
### 事件驱动架构
> [!question] 关键时刻的判断
> 用户点击"提交订单"后,系统要做:① 创建订单记录 ② 扣减库存 ③ 扣款 ④ 发送短信通知。哪些步骤必须同步完成?哪些可以异步处理?为什么?
**答案**:①~③需要立即得到结果或保证一致性,走同步流程;④纯粹的通知类场景完全异步,用消息队列解耦。即使扣款失败,短信也没必要发出。
```mermaid
graph LR
A["订单创建"] --> B{"是否需要同步响应?"}
B -->|"是"| Sync["同步 RPC 链"]
B -->|"否"| Async["异步 MQ 事件"]
Sync --> Stock["查库存 (RPC)"]
Stock --> Pay["支付 (RPC)"]
Async --> SMS["发短信 (Event)")
Async --> Points["加积分 (Event)"]
Async --> Email["发确认邮件 (Event)"]
```
### 消息队列选型
| 方案 | 吞吐量 | 延迟 | 可靠性 | 特色能力 |
|------|--------|------|--------|---------|
| **RocketMQ** | 极高 (百万级) | 毫秒级 | 事务消息支持 | 阿里出品,国内首选 |
| **Kafka** | 最高 (千万级) | 秒级 | 持久化 | 大数据生态集成 |
| **RabbitMQ** | 中等 (十万级) | 亚毫秒 | 高 | 灵活的路由模型 |
| **Pulsar** | 高 | 毫秒级 | 高 | 存储计算分离 |
### 发布订阅 vs 点对点
```mermaid
graph TB
Publisher[生产者] --> Broker[(MQ Broker)]
Broker --> Sub1["消费者组 A<br/>Topic: order-events"]
Broker --> Sub2["消费者组 B<br/>Topic: order-events"]
Broker --> Sub3["消费者组 C<br/>Topic: order-events"]
style Sub1 fill:#cfe2f3
style Sub2 fill:#d9e2f3
style Sub3 fill:#e2d9f3
```
- **发布订阅 (Pub/Sub)**:一个消息被多个消费组分别消费,每个组内只有一个实例收到
- **点对点 (Queue)**:一条消息只被一个消费者处理
## 同步与异步的组合拳
```mermaid
sequenceDiagram
participant Client
participant Gateway
participant Order
participant MQ
participant Notify
participant Analytics
Client->>Gateway: 下单请求
Gateway->>Order: 创建订单
Order->>Order: 写库 + 出消息(事务)
Order-->>Gateway: 返回 orderId
Gateway-->>Client: 202 Accepted ⚡
Note over MQ: 异步后续处理
MQ->>Notify: 发送通知
MQ->>Analytics: 数据统计
Note right of Client: 用户体验好:<br/>主流程快速响应<br/>非关键任务后台处理
```
> [!keypoint] 关键洞察
> - 同步链路:**gRPC 直连**,延迟低,适合核心事务链
> - 异步链路:**消息队列**,解耦非关键路径,即使失败也不影响主干
> - 核心原则:**同步保性能,异步保解耦**
## 关联笔记
- [[02-服务治理/API网关/README]] — API Gateway 负责协议转换
- [[03-数据一致性/分布式事务/README]] — 消息投递的一致性保障
- [[05-部署运维/Kubernetes/README]] — Sidecar 代理透明拦截通信流量
@@ -0,0 +1,164 @@
---
tags: [microservice, canary-release, blue-green, traffic-routing, istio]
create time: 2026-05-05
---
# 流量治理
## 概述
灰度发布(也叫金丝雀发布)是降低变更风险的核心手段。它让你将新版本逐步推向一小部分用户,观察指标正常后再全量。
> [!question] 如何安全地上线?
> 你修复了一个紧急 Bug,但这次改动涉及核心支付链路。直接全量发布一旦出问题损失巨大。有没有办法先小范围验证,确认没问题再放量?
## 灰度策略全景
```mermaid
flowchart LR
A["🟢 v1 (稳定版)<br/>95% 流量"] --> B["🔵 v2 (测试版)<br/>5% 流量"]
B --> C{"监控指标?"}
C -- "✅ 一切正常" --> D["逐步放量<br/>10% → 25% → 50%"]
C -- "❌ 错误率飙升" --> E["自动回滚到 v1 🔄"]
D --> F{"全部放量至 100%"}
F --> G["🟢 v2 成为新稳定版"]
```
### 常见灰度策略对比
| 策略 | 描述 | 优点 | 缺点 | 适用场景 |
|------|------|------|------|---------|
| **按百分比** | 随机分配 N% 流量到新版本 | 简单粗暴,覆盖面广 | 可能只命中特定用户 | 通用场景 |
| **按用户 ID** | 指定用户/内部账号走新版 | 可精准控制测试范围 | 样本不够代表性 | 内部验收阶段 |
| **按 Header** | `x-canary: true` 路由到新版本 | 灵活性最高 | 需要客户端配合 | QA / AB 测试 |
| **按地域** | 某个城市/机房的新版本 | 地域性问题的理想试验场 | 地域偏差大 | 全球化部署 |
| **按设备类型** | iOS 新用户先用新版 | 覆盖目标人群 | 样本有限 | 移动端发版 |
## 蓝绿 vs 灰度
> [!summary] 两种发布策略对比
>
> | 维度 | 蓝绿部署 | 灰度发布 (金丝雀) |
> |------|---------|-----------------|
> | **切换方式** | 一次性全切 | 逐步放量 |
> | **资源消耗** | 双倍(新旧并行) | 初期少量副本 |
> | **回滚速度** | 秒级(切回 LB 即可) | 同样秒级 |
> | **风险等级** | 中高(全量暴露问题) | 低(渐进式) |
> | **适合场景** | 大型变更、重大版本 | 日常迭代、高风险链路 |
### 蓝绿部署流程
```mermaid
stateDiagram-v2
[*] --> Blue: 初始状态
Blue --> DeployGreen: 部署 v2 到 Green 环境
DeployGreen --> TestGreen: 验证 Green
TestGreen --> Switch: 切换入口流量
Switch --> Green: 全部流量到 Green/v2
Green --> CleanupBlue: 删除 Blue/v1
CleanupBlue --> [*]
```
### 金丝雀发布流程
```mermaid
graph TB
S1["v1 运行中<br/>100% 流量"]
S2["部署 v2<br/>5% 流量"]
S3["监控 15min"]
S4{"P99 延迟 < SLA?"}
S4 -- 否 --> Rollback["回滚 v2 🔄"]
S4 -- 是 --> S5["放量 25%"]
S5 --> S6["监控 30min"]
S6 --> S7{"错误率 < 0.1%?"}
S7 -- 否 --> Rollback
S7 -- 是 --> S8["放量 50%"]
S8 --> S9["监控 1h"]
S9 --> S10{"全项达标?"}
S10 -- 是 --> S11["100% 全量 ✅"]
S10 -- 否 --> Rollback
style Rollback fill:#ffebee
style S11 fill:#e8f5e9
```
## 高级路由规则
### Istio VirtualService
```yaml
# 灰度规则:header 携带 canary=true 的用户走 v2
apiVersion: networking.istio.io/v1beta1
kind: VirtualService
metadata:
name: order-route
spec:
hosts: ["order-service"]
http:
# 灰度规则
- 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
```
### 权重路由示例
```yaml
# 按比例分流
http:
- route:
- destination:
host: order-service
subset: v1
weight: 90
- destination:
host: order-service
subset: v2
weight: 10
```
## 流量治理的其他维度
除了发布策略,流量治理还包括:
| 能力 | 说明 |
|------|------|
| **熔断降级** | 下游不可用时自动降级,参见 [[02-服务治理/容错模式/README]] |
| **限流** | 保护上游不因过量请求被打垮,参见 [[02-服务治理/容错模式/README]] |
| **重试** | 对瞬态故障自动恢复,参见 [[02-服务治理/容错模式/README]] |
| **黑白名单** | IP 级别访问控制 |
| **A/B Testing** | 基于用户分组的长期实验 |
## 灰度发布的量化决策依据
> [!tip] 必须有可量化的观测指标作为决策依据
>
> 不要凭感觉放量,让数据说话:
>
> | 指标 | 阈值参考 | 告警级别 |
> |------|---------|---------|
> | **错误率** | < 0.1% (v1 baseline 对比) | > 1% 立即回滚 |
> | **P99 延迟** | ≤ v1 × 1.1 | > v1 × 1.3 暂停放量 |
> | **CPU/Memory** | 在 limits 的 70% 以内 | > 85% 扩容 |
> | **下游依赖成功率** | > 99.5% | < 95% 熔断 |
## 关联笔记
- [[02-服务治理/API网关/README]] — API Gateway 的路由和权重分发
- [[04-可观测性/Metrics监控/README]] — 灰度期间的监控面板设计
- [[05-部署运维/SRE实践/README]] — 基于 SLO 的发布决策
@@ -0,0 +1,173 @@
---
tags: [microservice, config-management, nacos, apollo, spring-cloud-config]
create time: 2026-05-05
---
# 配置管理
## 概述
当服务实例数以百计时,手动管理配置是不可想象的。配置中心提供 **集中化、动态化、版本化** 的配置管理能力。
> [!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)按角色隔离 |
| **配置审计** | 记录谁在什么时候改了什么 |
## 配置分层模型
```mermaid
graph TB
subgraph "配置优先级(低 → 高)"
Base["Base 基线配置<br/>各项目共享的默认值"]
App["应用级配置<br/>每个服务的专属配置"]
Env["环境级配置<br/>dev/test/prod 差异"]
Instance["实例级配置<br/>单节点调优参数"]
end
style Base fill:#e3f2fd
style App fill:#fff3e0
style Env fill:#fce4ec
style Instance fill:#e8f5e9
```
**典型配置项分层**:
| 层级 | 示例 | 修改频率 |
|------|------|---------|
| 基线配置 | Redis 集群地址、公共超时时间 | 极低 |
| 应用配置 | 线程池大小、日志级别 | 低 |
| 环境配置 | DB 连接串、Feature Flag | 中 |
| 实例配置 | 单机限流阈值、调试开关 | 高 |
## 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",
})
// 监听配置变化——Nacos 推送更新回调
configClient.ListenChange(config_param.ListenChangeParam{
DataId: "order-service.yaml",
Group: "DEFAULT_GROUP",
Callback: func(content string) {
fmt.Println("配置更新了:", content)
// 重新加载配置...
ReloadConfig(content)
},
})
```
### 配置文件的命名规范
推荐格式:`{service-name}.{environment}.yaml`
| 服务名 | 环境 | Data ID |
|--------|------|---------|
| order-service | dev | `order-service.dev.yaml` |
| order-service | prod | `order-service.prod.yaml` |
| user-service | prod | `user-service.prod.yaml` |
## Apollo vs Nacos Config 对比
| 维度 | Apollo (携程) | Nacos Config (阿里) | Spring Cloud Config |
|------|--------------|---------------------|--------------------|
| **界面体验** | Web UI 完善,操作直观 | 较好 | 需自建 |
| **发布流程** | 支持审核、灰度、回滚 | 基础发布+回滚 | 依赖 Git 工作流 |
| **配置粒度** | 应用/Cluster/Namespace 多维 | Service/Group | File-based |
| **性能** | AP 模型,延迟 < 1s | AP 模型,延迟 < 1s | 读取 Git,延迟稍高 |
| **生态集成** | 适合 Java/Spring 体系 | Java + Go + Python 等 | 仅 Spring 生态 |
| **适用场景** | 大型企业,多团队协同 | 中小团队快速落地 | 纯 Spring 项目 |
## K8s ConfigMap & Secret
纯 K8s 环境内的原生方案:
```yaml
apiVersion: v1
kind: ConfigMap
metadata:
name: order-service-config
data:
application.yaml: |
server:
port: 8080
datasource:
url: jdbc:mysql://db-host:3306/orders
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
---
apiVersion: v1
kind: Secret
metadata:
name: order-service-secrets
type: Opaque
stringData:
DB_USERNAME: "app_user"
DB_PASSWORD: "s3cret_p@ss"
```
注入到容器:
```yaml
envFrom:
- configMapRef:
name: order-service-config
- secretRef:
name: order-service-secrets
```
> [!warning] K8s 原生方案的局限
> - 修改 ConfigMap 后 Pod 不会自动重载配置(需配合 Sidecar 或手动触发 reload)
> - 没有版本管理和灰度发布能力
> - **建议**:简单项目用 K8s ConfigMap,复杂场景上 Apollo/Nacos
## 敏感信息处理
> [!danger] 安全红线
> **永远不要**在代码仓库中硬编码密码、API Key、私钥等敏感信息。
```mermaid
flowchart LR
Dev["开发者本地"] -->|"K8s Secret / Vault"| Store["加密存储"]
Store -->|"运行时解密"| Runtime["运行时的环境变量"]
Runtime --> App["应用程序"]
Audit["审计系统"] -.->|"只读访问"| Store
```
**推荐方案**:
- **K8s Secret** — 小型集群内使用(base64 编码,非加密,配合 EncryptionConfiguration 增强)
- **HashiCorp Vault** — 企业级密钥管理,动态秘钥、自动轮换
- **云厂商 KV 服务** — AWS Secrets Manager / 阿里云 KMS / 腾讯云 SecretManager
## 关联笔记
- [[02-服务治理/服务发现/README]] — Nacos 同时提供服务发现和配置管理
- [[05-部署运维/Kubernetes/README]] — ConfigMap/Secret 是 K8s 的配置注入方式