diff --git a/hzh/MS/02-服务治理.md b/hzh/MS/02-服务治理.md deleted file mode 100644 index a4d8f35..0000000 --- a/hzh/MS/02-服务治理.md +++ /dev/null @@ -1,572 +0,0 @@ ---- -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] 权衡题 -> 有人说:"鉴权放到网关统一做,避免每个服务重复写。"但如果某个内部服务不需要鉴权呢?或者不同团队要求不同的鉴权方式呢?你怎么设计才能兼顾统一性和灵活性? -> 👉 [[02-服务治理/网关鉴权策略]] — 分层鉴权模型详解 - -## 服务间通信 - -### 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; -} -``` - -### 典型请求链路 - -一次下单操作会串联多个服务和两种通信模式: - -```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 保解耦」的组合拳。 - -### 同步 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 -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 -``` - -```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-可观测性]] — 日志、指标、链路追踪的完整体系 diff --git a/hzh/MS/02-服务治理/API网关/README.md b/hzh/MS/02-服务治理/API网关/README.md new file mode 100644 index 0000000..8735d63 --- /dev/null +++ b/hzh/MS/02-服务治理/API网关/README.md @@ -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
JWT 校验 + OAuth2"] + GW --> S1[公开接口
无需额外鉴权] + GW --> S2[内部接口
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\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 选型 diff --git a/hzh/MS/02-服务治理/README.md b/hzh/MS/02-服务治理/README.md new file mode 100644 index 0000000..ffe8e33 --- /dev/null +++ b/hzh/MS/02-服务治理/README.md @@ -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 天然提供负载均衡和服务发现 diff --git a/hzh/MS/02-服务治理/分布式追踪/README.md b/hzh/MS/02-服务治理/分布式追踪/README.md new file mode 100644 index 0000000..97ae778 --- /dev/null +++ b/hzh/MS/02-服务治理/分布式追踪/README.md @@ -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
API Gateway
5ms"] + Span1 --> Span2["Span #2
Order Service
70ms"] + Span1 --> Span3["Span #3
User Service
15ms"] + Span2 --> Span4["Span #4
Inventory DB Query
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]] — 配置中心的动态刷新可以联动调整采样率 diff --git a/hzh/MS/02-服务治理/安全机制/README.md b/hzh/MS/02-服务治理/安全机制/README.md new file mode 100644 index 0000000..af8aa58 --- /dev/null +++ b/hzh/MS/02-服务治理/安全机制/README.md @@ -0,0 +1,181 @@ +--- +tags: [microservice, security, mTLS, JWT, OAuth2, zero-trust] +create time: 2026-05-05 +--- + +# 安全机制 + +## 概述 + +微服务架构下,服务间通信的安全保障比单体应用复杂得多。每个服务的 API 都是潜在的攻击面,需要多层防御。 + +```mermaid +graph TB + subgraph "防御层" + L1["L1: 网络隔离
VPC / 安全组"] + L2["L2: 传输加密
mTLS / TLS"] + L3["L3: 身份认证
JWT / Service Token"] + L4["L4: 授权控制
RBAC / ABAC"] + L5["L5: 审计追踪
操作日志"] + 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×tamp=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 设计中的安全考量 diff --git a/hzh/MS/02-服务治理/容错模式/README.md b/hzh/MS/02-服务治理/容错模式/README.md new file mode 100644 index 0000000..3da78d0 --- /dev/null +++ b/hzh/MS/02-服务治理/容错模式/README.md @@ -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["请求放行
⏱ 实时统计成功率
⚠️ 连续失败 → 熔断"] + 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]] — 熔断器的指标暴露和告警联动 diff --git a/hzh/MS/02-服务治理/服务发现/README.md b/hzh/MS/02-服务治理/服务发现/README.md new file mode 100644 index 0000000..dda7e13 --- /dev/null +++ b/hzh/MS/02-服务治理/服务发现/README.md @@ -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 的服务发现原理 diff --git a/hzh/MS/02-服务治理/服务间通信/README.md b/hzh/MS/02-服务治理/服务间通信/README.md new file mode 100644 index 0000000..687c3b9 --- /dev/null +++ b/hzh/MS/02-服务治理/服务间通信/README.md @@ -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
Topic: order-events"] + Broker --> Sub2["消费者组 B
Topic: order-events"] + Broker --> Sub3["消费者组 C
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: 用户体验好:
主流程快速响应
非关键任务后台处理 +``` + +> [!keypoint] 关键洞察 +> - 同步链路:**gRPC 直连**,延迟低,适合核心事务链 +> - 异步链路:**消息队列**,解耦非关键路径,即使失败也不影响主干 +> - 核心原则:**同步保性能,异步保解耦** + +## 关联笔记 + +- [[02-服务治理/API网关/README]] — API Gateway 负责协议转换 +- [[03-数据一致性/分布式事务/README]] — 消息投递的一致性保障 +- [[05-部署运维/Kubernetes/README]] — Sidecar 代理透明拦截通信流量 diff --git a/hzh/MS/02-服务治理/流量治理/README.md b/hzh/MS/02-服务治理/流量治理/README.md new file mode 100644 index 0000000..2ccc6e9 --- /dev/null +++ b/hzh/MS/02-服务治理/流量治理/README.md @@ -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 (稳定版)
95% 流量"] --> B["🔵 v2 (测试版)
5% 流量"] + B --> C{"监控指标?"} + + C -- "✅ 一切正常" --> D["逐步放量
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 运行中
100% 流量"] + S2["部署 v2
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 的发布决策 diff --git a/hzh/MS/02-服务治理/配置管理/README.md b/hzh/MS/02-服务治理/配置管理/README.md new file mode 100644 index 0000000..468e784 --- /dev/null +++ b/hzh/MS/02-服务治理/配置管理/README.md @@ -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 基线配置
各项目共享的默认值"] + App["应用级配置
每个服务的专属配置"] + Env["环境级配置
dev/test/prod 差异"] + Instance["实例级配置
单节点调优参数"] + 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 的配置注入方式 diff --git a/hzh/MS/03-数据一致性.md b/hzh/MS/03-数据一致性.md deleted file mode 100644 index b12426d..0000000 --- a/hzh/MS/03-数据一致性.md +++ /dev/null @@ -1,217 +0,0 @@ ---- -tags: [microservice, distributed-system, database, data-consistency, saga-pattern, tcc, idempotency, outbox-pattern] -create time: 2026-04-29 12:03 ---- - -# 数据一致性 - -## 概述 - -微服务的核心设计原则是 **"每个服务拥有独立数据库"**,这带来了分布式事务和数据一致性的经典难题。本文梳理主要解决方案及其取舍。 - -## 为什么不能共享数据库 - -```mermaid -graph LR - S1[订单服务 DB] - S2[库存服务 DB] - - subgraph BAD["反模式:共享数据库"] - S1 --- SHARED[(共享 DB)] - S2 --- SHARED - end -``` - -> [!failure] 反模式警告 -> 如果两个服务连接同一个数据库的同一张表,它们就不再是独立的微服务——你得到的是**分布式单体**。服务可以随意互相查询彼此的数据,失去了边界和自治性。 - -## 分布式事务方案概览 - -| 方案 | 一致性级别 | 性能 | 复杂度 | 适用场景 | -|------|-----------|------|--------|---------| -| 本地事务 + 事件通知 | 最终一致 | 高 | 低 | 绝大多数场景 | -| Saga 模式 | 最终一致 | 中 | 中 | 长流程业务 | -| TCC | 强最终一致 | 中 | 高 | 对一致性要求较高的场景 | -| AT 模式 (Seata) | 伪强一致 | 中低 | 低 | 不想改业务代码时 | - -> [!info] 选择策略 -> 先默认用 **本地事务 + 异步事件**(最简单、最高效),只有在业务明确需要 Saga 或 TCC 时才升级。 - -## 方案一:本地事务 + 消息队列 - -这是**最常用**的方案,核心思想是将"数据变更 + 发消息"合并到一个本地事务中。 - -```mermaid -sequenceDiagram - participant U as 用户 - participant O as 订单服务 - participant MQ as 消息队列 - participant I as 库存服务 - - U->>O: 创建订单 - O->>O: BEGIN 事务 - O->>O: 插入订单记录 - O->>MQ: 发送订单创建消息 - O->>O: COMMIT 事务 - MQ-->>I: 投递消息 - I->>I: 扣减库存 - I-->>O: 返回结果(可选) -``` - -### 实战:电商订单创建流程 - -以「用户下单」为例,这个流程涉及 **订单服务**(创建订单)和 **库存服务**(扣减库存),是典型的跨服务数据一致性问题: - -| 步骤 | 动作 | 一致性保障点 | -|------|------|-------------| -| 1 | 用户点击"提交订单" | 前端防重复提交(按钮置灰 / Token 机制) | -| 2 | 订单服务写入订单表 + 发送 `order.created` 消息 | **事务边界**:两件事必须在同一个本地事务中完成 | -| 3 | 消息队列投递给库存服务 | 保证消息至少一次投递(At-Least-Once) | -| 4 | 库存服务扣减库存 | **幂等性**:防止消息重投导致库存被扣两次 | -| 5 | 库存不足时回滚订单 | 通过 Sagas 补偿:已创建的订单标记为"取消" | - -> [!question] 思考 -> 如果步骤 2 的数据库写成功了,但 MQ 发消息失败了,会发生什么?用户看到下单成功但实际上库存没被锁定。这说明为什么必须用事务消息或本地消息表,而不是直接调 MQ API。 - ---- - -### 如何保证"事务内既写库又发消息"? - -**方案 A:事务消息**(推荐) -- RocketMQ /阿里云 MSE 原生支持事务消息 -- MQ 回查机制确保消息不丢 - -**方案 B:本地消息表** -- 在业务库里建一张 `outbox` 表 -- 业务事务同时写入业务数据和消息记录 -- 后台定时任务扫描未发送的消息推送出去 - -```go -// 本地消息表方案示意 -tx, _ := db.Begin() -// 1. 业务操作 -tx.Exec("UPDATE accounts SET balance = balance - ? WHERE id = ?", amount, fromID) -// 2. 记录待发送消息(带状态字段) -tx.Exec("INSERT INTO outbox (topic, payload, status, created_at) VALUES (?, ?, 'pending', NOW())", "order.created", jsonPayload) -tx.Commit() -// 后台进程轮询 outbox 表中 status='pending' 的记录,发送到 MQ 后更新为 'sent' -``` - -#### Outbox 表建表示例 - -```sql -CREATE TABLE outbox ( - id BIGSERIAL PRIMARY KEY, - topic VARCHAR(255) NOT NULL, -- 消息主题 - payload JSONB NOT NULL, -- 消息体 - status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending / sent / failed - error_msg TEXT, -- 失败原因 - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - sent_at TIMESTAMP -); - --- 加速定时扫描查询 -CREATE INDEX idx_outbox_pending ON outbox (status, created_at) WHERE status = 'pending'; -``` - -### 消费幂等性 - -消息可能重复投递(网络超时、MQ 重投),消费者必须做到**幂等**——处理一次和处理多次的结果完全相同。 - -**三种常见策略:** - -| 策略 | 实现方式 | 适用场景 | -|------|---------|---------| -| 数据库唯一约束 | 用 `msg_id` 或业务主键做 `UNIQUE` | 最可靠,推荐首选 | -| Redis 去重键 | SET 一个 TTL Key(如 `dedup:{msg_id}` 过期 24h) | 高吞吐场景,需考虑 Redis 可用性 | -| 乐观锁版本控制 | UPDATE 时加 `WHERE version = old_version` | 适用于金额调整类操作 | - -```go -// 推荐方案:利用 UNIQUE 约束做幂等保障 -_, err := db.Exec(` - INSERT INTO order_events (msg_id, order_id, action, amount) - VALUES ($1, $2, $3, $4) - ON CONFLICT (msg_id) DO NOTHING -- 重复到达时静默忽略 -`, msgID, orderID, action, amount) -if err != nil { - // 其他错误才需要处理,UNIQUE 冲突直接忽略 - if !isUniqueViolation(err) { - log.Error("process message failed", err) - } -} -// 执行业务逻辑... -``` - -> [!tip] 设计要点 -> `ON CONFLICT DO NOTHING` 比先查后插更可靠——它消除了竞态窗口。即使用两个实例同时收到同一条消息,只会有一个成功写入。 - -> [!question] 思考 -> 为什么要在生产者侧保证消息可靠投递,而不是靠消费者反复拉取重试? - -## 方案二:Saga 模式 - -Saga 适用于**跨多个服务的长流程操作**,将大事务拆成一系列本地小事务,每个步骤都有对应的补偿操作。 - -```mermaid -graph LR - S1[① 创建订单
→ 取消订单] - S2[② 扣减库存
→ 恢复库存] - S3[③ 支付扣款
→ 退款] - S4[④ 发货
→ 无补偿] - - S1 --> S2 --> S3 --> S4 -``` - -**编排式 vs 编舞式**: - -```mermaid -graph TB - subgraph ORCHESTRATION["编排式 — 中心协调器"] - CO[Coordinator] --> S1[OrderSvc] - CO --> S2[InventorySvc] - CO --> S3[PaymentSvc] - end - - subgraph CHOREOGRAPHY["编舞式 — 事件驱动"] - E1[订单创建] --> S11[订单服务] - S11 --> E2[库存不足事件] - E2 --> S12[库存服务] - S12 --> E3[扣减完成事件] - E3 --> S13[支付服务] - end -``` - -| | 编排式 | 编舞式 | -|---|--------|--------| -| 控制流 | 中心化 Coordinator | 各服务通过事件自发响应 | -| 可观测性 | ✅ 集中管理 | ❌ 流程散布在各服务 | -| 耦合度 | 依赖 Coordinator | 服务间仅感知事件 | - -## 方案三:TCC 与 AT 模式 - -> [!abstract] 进阶了解 -> -> **TCC** (Try-Confirm-Cancel):每个事务步骤实现三个接口。适合对一致性要求严格且能承受开发成本的业务。 -> -> **AT 模式** (Seata):框架层自动处理两阶段提交,对业务透明。本质上是基于 undo_log 的增强型 XA,性能低于 Saga。 - -## 总结选型指南 - -> [!summary] 决策树 -> -> ```mermaid -> graph TD -> A["跨几个服务"] -->|"1-2"| B["本地事务 + 事件通知"] -> A -->|"3+"| C["Saga 模式"] -> B -->|"强"| D["加幂等,必要时上 TCC"] -> B -->|"弱"| E["本地事务已够"] -> C -->|"不能"| F["考虑 AT 模式"] -> C -->|"能"| G["自行实现补偿"] -> ``` -> -> **经验法则**:80% 的场景,本地事务 + MQ 就足够了。先别急着上复杂方案。 - -## 关联笔记 - -- [[01-基础概念]] — 微服务拆分与独立数据库原则 -- [[02-服务治理]] — 服务调用链路上的超时、重试、熔断 diff --git a/hzh/MS/03-数据一致性/ID生成/README.md b/hzh/MS/03-数据一致性/ID生成/README.md new file mode 100644 index 0000000..076fa0c --- /dev/null +++ b/hzh/MS/03-数据一致性/ID生成/README.md @@ -0,0 +1,157 @@ +--- +tags: [microservice, id-generation, snowflake, uuid, distributed-id] +create time: 2026-05-05 +--- + +# 分布式 ID 生成 + +## 概述 + +在微服务架构中,数据库被拆分成多个独立实例,不再共享自增主键。如何生成分布式环境下全局唯一的 ID,是一个经典问题。 + +```mermaid +graph LR + A["❌ 数据库 AUTO_INCREMENT"] -->|"分库后冲突"| Problem["不可用"] + + B["✅ 分布式 ID"] --> Unique["全局唯一"] + B --> Ordered["趋势有序"] + B --> HighThroughput["高吞吐"] + + style Problem fill:#ffebee + style Unique fill:#e8f5e9 + style Ordered fill:#e8f5e9 + style HighThroughput fill:#e8f5e9 +``` + +> [!question] 为什么要自己生成?不用数据库自增? +> +> - **分库后**:每个库的自增 ID 从 1 开始,必然冲突 +> - **性能瓶颈**:DB 序列或号段模式在高并发下成为瓶颈 +> - **耦合度高**:ID 生成依赖特定数据库类型 + +## 主流方案对比 + +| 方案 | 唯一性保证 | 有序性 | 性能 (QPS) | 复杂度 | 适用场景 | +|------|-----------|--------|-----------|--------|---------| +| **UUID** | MD5/SHA 哈希保证 | ❌ 完全无序 | ⭐⭐⭐⭐⭐ | 极低 | 临时标识、缓存 Key | +| **雪花算法 (Snowflake)** | WorkerId + 时间戳 | ✅ 趋势有序 | ⭐⭐⭐⭐⭐ | 中 | 通用方案,首选 | +| **号段模式** | DB 批量发放 | ✅ 严格有序 | ⭐⭐⭐⭐ | 中 | 已有 DB 架构改造 | +| **Leaf (美团)** | Snowflake + 号段 | ✅ 趋势/严格有序 | ⭐⭐⭐⭐⭐ | 中高 | 大规模生产环境 | +| **NanoID** | Base62 + CSPRNG | ❌ 无序 | ⭐⭐⭐⭐⭐ | 低 | API Token、短链 | + +## 雪花算法 (Snowflake) + +Twitter 开源的经典算法,将 ID 分为三部分: + +``` +64 bit: 1 bit(符号位) + 41 bit(时间戳) + 10 bit(机器ID) + 12 bit(序列号) + │ │ │ │ + │ │ │ └─ 每毫秒最多 4096 个 ID + │ │ └──────────────── 1024 台机器 + │ └────────────────────────────── ~69 年跨度 + └────────────────────────────────────────── 固定为 0 +``` + +### 核心公式 + +```go +// 伪代码 +func generateID() int64 { + timestamp = currentMillis() // 当前毫秒时间戳(相对时间戳) + workerId = 1 // 机器编号 (0-1023) + sequence = incrementSequence() // 递增序号 (0-4095) + + return (timestamp << 22) | // 时间戳占高位 + (workerId << 12) | // 机器 ID 放中间 + sequence // 序列号放在低位 +} +``` + +### Go 实现要点 + +```go +type Snowflake struct { + mu sync.Mutex + lastTime int64 + sequence int64 + workerId int64 + workerBits uint8 = 10 + stepBits uint8 = 12 +} + +func (sf *Snowflake) NextID() int64 { + sf.mu.Lock() + defer sf.mu.Unlock() + + now := time.Now().UnixMilli() + if now < sf.lastTime { + panic("clock moved backwards") // 时钟回拨保护 + } + + if now == sf.lastTime { + sf.sequence++ + } else { + sf.sequence = 0 + } + + sf.lastTime = now + + return (now << 22) | (sf.workerId << 12) | sf.sequence +} +``` + +### 注意事项 + +| 问题 | 解决方案 | +|------|---------| +| **时钟回拨** | 等待到回拨前的时间点再继续;或分配备用 WorkerId | +| **WorkerId 分配** | 启动时从注册中心获取,或使用 K8s Pod Name 映射 | +| **单机 QPS 上限** | 单 WorkerId × 单机房约 409.6 万/秒,通常够用 | +| **跨代际兼容性** | 新字段插入会改变位移量,设计时需预留比特位 | + +## UUID v7 (时间有序 UUID) + +如果你不想维护 Snowflake 的 WorkerId,可以考虑 **UUID v7**——它是 RFC 9562 定义的新版本,保持了时序性。 + +``` +UUID v7: 48-bit timestamp + 12-bit rand_a + 4-bit version + 12-bit rand_b + 2-bit variant + 62-bit rand_c +``` + +| 维度 | UUID v4 | UUID v7 | +|------|---------|---------| +| **生成方式** | 纯随机 | 时间戳 + 随机数 | +| **有序性** | ❌ 完全随机 | ✅ 趋势有序 | +| **索引效率** | ❌ 随机插入导致页分裂 | ✅ 顺序插入友好 | +| **存储大小** | 16 bytes | 16 bytes | +| **实现复杂度** | 极低 | 低(标准库支持) | + +> [!tip] 如果你的数据库是 MySQL 5.7+,直接改用 `BINARY(16)` 存储 UUID v7,比 BIGINT AUTO_INCREMENT 更优。 + +## 号段模式 + +基于数据库分批领取 ID 区间: + +```mermaid +sequenceDiagram + App->>DB: SELECT max_id FROM id_generator WHERE biz='order' + DB-->>App: max_id = 10000, step = 2000 + + Note over App: 内存中维护 [10000, 12000) 号段 + + loop 每次请求 + App->>App: ID++ (本地原子操作) + end + + App->>DB: UPDATE id_generator SET max_id = 12000 WHERE max_id = 10000 + DB-->>App: ACK + + Note over App: 下次取号段: [12000, 14000) +``` + +**优点**:兼容现有 MySQL 架构,无需引入外部组件。 +**缺点**:存在 ID 浪费(宕机未用完的号段),极端情况下可能产生空洞。 + +## 关联笔记 + +- [[03-数据一致性/数据库拆分/README]] — 分库分表场景下的 ID 生成 +- [[03-数据一致性/分布式事务/README]] — Outbox 消息 ID 也需要全局唯一 diff --git a/hzh/MS/03-数据一致性/README.md b/hzh/MS/03-数据一致性/README.md new file mode 100644 index 0000000..e191e2c --- /dev/null +++ b/hzh/MS/03-数据一致性/README.md @@ -0,0 +1,39 @@ +--- +tags: [microservice, data-consistency] +create time: 2026-04-29 12:03 +--- + +# 数据一致性 + +## 概述 + +微服务的核心设计原则是 **"每个服务拥有独立数据库"**,这带来了分布式事务和数据一致性的经典难题。本文梳理主要解决方案及其取舍。 + +## 知识体系 + +```mermaid +graph LR + A["数据库拆分"] --> B["分布式事务"] + B --> C["ID 生成"] + + style A fill:#e3f2fd + style B fill:#fff3e0 + style C fill:#e8f5e9 +``` + +| # | 主题 | 核心问题 | +|---|------|----------| +| 1 | [[03-数据一致性/数据库拆分/README]] | 每个服务独立 DB,表大了怎么拆分?跨库查询怎么做? | +| 2 | [[03-数据一致性/分布式事务/README]] | 本地事务 + MQ、Saga、TCC,哪个方案最合适? | +| 3 | [[03-数据一致性/ID生成/README]] | 没有自增主键了,全局唯一 ID 怎么生成? | + +### 学习建议 + +> [!tip] 学习路径 +> 先理解"为什么不能共享数据库",再学分库分表的策略,最后深入分布式事务方案选型。ID 生成是最轻量的话题,可以随时了解。 + +### 关联笔记 + +- [[01-基础概念]] — 微服务拆分与独立数据库原则 +- [[02-服务治理]] — 服务调用链路上的超时、重试、熔断 +- [[hzh/MS/README.md]] — 完整微服务知识索引 diff --git a/hzh/MS/03-数据一致性/分布式事务/README.md b/hzh/MS/03-数据一致性/分布式事务/README.md new file mode 100644 index 0000000..030c5cb --- /dev/null +++ b/hzh/MS/03-数据一致性/分布式事务/README.md @@ -0,0 +1,249 @@ +--- +tags: [microservice, distributed-transactions, saga, tcc, outbox] +create time: 2026-05-05 +--- + +# 分布式事务 + +## 概述 + +每个服务拥有独立数据库,跨服务的"一次操作"实际上涉及**多个本地事务**。如何保证这些本地事务要么全部成功、要么全部回滚,就是分布式事务要解决的问题。 + +```mermaid +graph LR + A["用户下单"] --> B["订单服务
写库"] + B --> C["库存服务
扣减"] + C --> D["支付服务
扣款"] + + style A fill:#e3f2fd + style B fill:#fff3e0 + style C fill:#fff3e0 + style D fill:#fff3e0 +``` + +## 分布式事务方案全景 + +| 方案 | 一致性级别 | 性能 | 复杂度 | 适用场景 | +|------|-----------|------|--------|---------| +| **本地事务 + MQ 事件** | 最终一致 | ⭐⭐⭐⭐⭐ | ⭐ | 绝大多数场景 | +| **Saga 模式** | 最终一致 | ⭐⭐⭐ | ⭐⭐ | 长流程业务 | +| **TCC** | 强最终一致 | ⭐⭐⭐ | ⭐⭐⭐⭐ | 对一致性要求较高的场景 | +| **AT 模式 (Seata)** | 伪强一致 | ⭐⭐ | ⭐ | 不想改业务代码时 | + +> [!tip] 选择策略 +> **先默认用本地事务 + 异步事件(最简单、最高效)**,只有在业务明确需要 Saga 或 TCC 时才升级。80% 的场景,本地事务 + MQ 就足够了。 + +## 方案一:本地事务 + 消息队列 + +核心思想:**将"数据变更 + 发消息"合并到一个本地事务中。** + +### Outbox 模式(推荐) + +```mermaid +sequenceDiagram + participant App as 应用服务 + participant DB as 数据库 + participant Outbox as Outbox 表 + participant MQ as 消息队列 + participant Sub as 订阅方 + + App->>DB: BEGIN 事务 + App->>DB: 写业务数据 + App->>Outbox: 写入待发送消息 + App->>DB: COMMIT + + loop 定时任务 + MQ->>Outbox: 扫描 status='pending' + Outbox-->>MQ: 返回消息列表 + MQ->>Sub: 投递消息 + MQ->>Outbox: 更新为 'sent' + end + + Note right of Sub: 至少一次投递 + 消费者幂等 +``` + +#### Outbox 表建表示例 + +```sql +CREATE TABLE outbox ( + id BIGSERIAL PRIMARY KEY, + topic VARCHAR(255) NOT NULL, -- 消息主题 + payload JSONB NOT NULL, -- 消息体 + status VARCHAR(20) NOT NULL DEFAULT 'pending', -- pending / sent / failed + error_msg TEXT, -- 失败原因 + created_at TIMESTAMP NOT NULL DEFAULT NOW(), + sent_at TIMESTAMP +); + +-- 加速定时扫描查询 +CREATE INDEX idx_outbox_pending ON outbox (status, created_at) +WHERE status = 'pending'; +``` + +```go +// Go 示例:出事务内同时写业务数据和 outbox 记录 +tx, _ := db.Begin() +tx.Exec("INSERT INTO orders (user_id, total) VALUES ($1, $2)", userID, total) +tx.Exec(`INSERT INTO outbox (topic, payload, status) + VALUES ('order.created', $1, 'pending')`, jsonPayload) +tx.Commit() + +// 后台 goroutine 轮询并推送 +func outboxWorker(ctx context.Context, ticker *time.Ticker) { + for { + select { + case <-ctx.Done(): + return + case <-ticker.C: + sendPendingMessages(ctx) + } + } +} +``` + +#### 事务消息(RocketMQ 原生支持) + +如果使用的是 RocketMQ,可以绕过 Outbox 模式直接用事务消息: + +```go +// 发送事务消息 +txMsg := rocketmq.NewTransactionMessage("order-created", payload) +localTx := &MyLocalTxChecker{} + +// half 消息发送 → 本地事务执行 → 提交/回查 +res, _ := producer.SendMessageInTransaction(txMsg, localTx) +``` + +RocketMQ 的事务消息流程: +1. 生产者发送 "half 消息" 到 MQ(消费者不可见) +2. 执行本地事务 +3. 根据结果 Commit(消费者可见)或 Rollback(丢弃) +4. 如果步骤 2 超时,MQ 回查本地事务状态 + +### 消费幂等性 + +> [!warning] 关键保障 +> 消息可能重复投递(网络超时、MQ 重投),消费者必须做到**幂等**——处理一次和处理多次的结果完全相同。 + +**三种常见策略:** + +| 策略 | 实现方式 | 适用场景 | +|------|---------|---------| +| **数据库唯一约束** | `msg_id` 做 `UNIQUE` | 最可靠,推荐首选 | +| **Redis 去重键** | `SET dedup:{msg_id} 1 NX EX 86400` | 高吞吐场景 | +| **乐观锁版本控制** | `UPDATE SET qty = qty - N WHERE version = V` | 金额调整类操作 | + +```go +// 推荐方案:利用 UNIQUE 约束做幂等保障 +_, err := db.Exec(` + INSERT INTO order_events (msg_id, order_id, action, amount) + VALUES ($1, $2, $3, $4) + ON CONFLICT (msg_id) DO NOTHING +`, msgID, orderID, action, amount) + +if !isUniqueViolation(err) { + log.Error("process message failed", err) + return +} +// 执行业务逻辑——到这里说明消息是新到达的 +``` + +## 方案二:Saga 模式 + +Saga 适用于**跨多个服务的长流程操作**,将大事务拆成一系列本地小事务,每个步骤都有对应的补偿操作。 + +### 编排式 vs 编舞式 + +```mermaid +graph TB + subgraph ORCHESTRATION["编排式 — Coordinator 中心化"] + CO[Coordinator] --> S1[OrderSvc: Create] + CO --> S2[InventorySvc: Reserve] + CO --> S3[PaymentSvc: Charge] + + S1 -.->|失败→Cancel| CO + S2 -.->|失败→Cancel| CO + S3 -.->|失败→Cancel| CO + end + + subgraph CHOREOGRAPHY["编舞式 — 事件驱动"] + E1[OrderCreated] --> S11[OrderService] + S11 --> E2[StockReserved] + E2 --> S12[InventoryService] + S12 --> E3[PaymentCharged] + E3 --> S13[PaymentService] + + S13 -.-> E4[PaidFailed] -.-> S11 + end +``` + +| 维度 | 编排式 | 编舞式 | +|------|--------|--------| +| **控制流** | 中心化 Coordinator | 各服务通过事件自发响应 | +| **可观测性** | ✅ 集中管理全流程 | ❌ 流程散布在各服务 | +| **耦合度** | 依赖 Coordinator | 服务间仅感知事件 | +| **适合规模** | 5~15 步的 Saga | 简单链路 (< 5 步) | + +### Saga 补偿设计原则 + +每个正向操作必须有对应的**反向补偿**: + +| 正向操作 | 补偿操作 | +|---------|---------| +| 创建订单 | 取消订单 | +| 预留库存 | 释放库存 | +| 扣款 | 退款 | +| 发送通知 | 撤销通知(一般不需要) | + +> [!warning] 补偿操作的幂等性 +> 补偿操作也必须幂等——CancelOrder 可能被触发多次。用订单状态的流转来保证(如只有 PENDING 才能转到 CANCELLED)。 + +## 方案三:TCC (Try-Confirm-Cancel) + +TCC 在每个事务步骤中实现三个接口: + +``` +Try: 预留资源(冻结余额 / 锁定库存) +Confirm: 确认使用资源(正式扣减 / 正式锁定) +Cancel: 释放资源(解冻余额 / 解锁库存) +``` + +### TCC 时序图 + +```mermaid +sequenceDiagram + participant Orch as Coordinator + participant O as Order Service + participant I as Inventory Service + participant P as Payment Service + + Orch->>O: Try(CreateOrder) + O-->>Orch: OK + + Orch->>I: Try(ReserveStock) + I-->>Orch: OK + + Orch->>P: Try(ChargeBalance) + P-->>Orch: OK + + Orch->>O: Confirm + Orch->>I: Confirm + Orch->>P: Confirm + + Note over Orch,P: 全部 Confirm → 事务完成 +``` + +### TCC vs Saga 对比 + +| 维度 | TCC | Saga | +|------|-----|------| +| 一致性强度 | 较强(资源被占用期间不允许其他事务使用) | 较弱(中间态数据可见) | +| 开发成本 | 高(每个业务方法实现 Try/Confirm/Cancel) | 低(只需正向 + 反向操作) | +| 性能 | 中(需要两阶段提交) | 中高(单阶段本地事务) | +| 适用场景 | 资金、库存等高敏感业务 | 订单流程、审批流等业务链 | + +## 关联笔记 + +- [[03-数据一致性/数据库拆分/README]] — 数据库拆分是分布式事务的前提 +- [[02-服务治理/容错模式/README]] — 熔断器和重试在分布式事务中的作用 +- [[02-服务治理/服务间通信/README]] — 消息投递的一致性保障 diff --git a/hzh/MS/03-数据一致性/数据库拆分/README.md b/hzh/MS/03-数据一致性/数据库拆分/README.md new file mode 100644 index 0000000..a1609e7 --- /dev/null +++ b/hzh/MS/03-数据一致性/数据库拆分/README.md @@ -0,0 +1,198 @@ +--- +tags: [microservice, database, sharding, replication] +create time: 2026-05-05 +--- + +# 数据库拆分 + +## 概述 + +微服务的核心设计原则是 **"每个服务拥有独立数据库"**,这意味着每个服务的表结构、数据存储、甚至数据库类型都可以不同。但当单表数据量持续增长时,就面临拆分的需求。 + +```mermaid +graph LR + S1[订单服务 DB] + S2[库存服务 DB] + + subgraph BAD["反模式:共享数据库"] + S1 --- SHARED[(共享 DB)] + S2 --- SHARED + end + + style SHARED fill:#ffebee,stroke:#ef5350 +``` + +> [!failure] 反模式警告 +> 如果两个服务连接同一个数据库的同一张表,它们就不再是独立的微服务——你得到的是**分布式单体**。服务可以随意互相查询彼此的数据,失去了边界和自治性。 + +## 垂直拆分 vs 水平拆分 + +### 垂直拆分(按业务域) + +按 **微服务边界** 拆库——这是微服务的标配。 + +```mermaid +graph TB + subgraph "DB-Order" + orders[orders] + order_items[order_items] + order_status[order_status] + end + + subgraph "DB-User" + users[users] + user_profiles[user_profiles] + user_addresses[user_addresses] + end + + subgraph "DB-Product" + products[products] + categories[categories] + product_images[product_images] + end +``` + +| 特点 | 说明 | +|------|------| +| 每个服务独占一个数据库 | 物理隔离,互不干扰 | +| 可异构选型 | 订单用 MySQL,用户用 PostgreSQL,搜索用 Elasticsearch | +| 天然解耦 | 服务间不能直接查对方库 | + +### 水平拆分(分库分表) + +当单个表的记录量达到千万级以上,需要进一步拆分。 + +```mermaid +graph TB + subgraph "分库策略" + DB1[(DB-01)] + DB2[(DB-02)] + DB3[(DB-03)] + end + + subgraph "order_0001 表" + Row1[userId=1 → order_0001] + Row2[userId=4 → order_0001] + end + + subgraph "order_0002 表" + Row3[userId=2 → order_0002] + Row4[userId=5 → order_0002] + end + + userId_mod["ORDER BY user_id % 2"] --> DB1 + userId_mod --> DB2 +``` + +### 常见分片策略 + +| 策略 | 哈希公式 | 优点 | 缺点 | +|------|---------|------|------| +| **Hash Mod** | `user_id % N` | 简单高效,路由确定 | 扩缩容困难,数据迁移成本高 | +| **Range** | `user_id BETWEEN x AND y` | 范围查询友好 | 热点用户集中到单分片 | +| **Time-based** | `year_month` | 按生命周期管理 | 新分片写入压力大 | +| **Geo-based** | 按地域分片 | 本地化访问,延迟低 | 跨区域操作复杂 | + +### ShardingSphere / MyCat + +> [!tip] 推荐中间件 +> +> **Apache ShardingSphere** 是国内使用最广泛的分库分表方案: +> - 支持 JDBC / Proxy / Sidecar 三种部署模式 +> - 内置分片算法:Mod、Range、Hash、Tag +> - 分布式主键生成器(Snowflake)原生集成 +> - 读写分离、强制路由、广播表等高级特性 + +#### ShardingSphere 配置示例 + +```yaml +# sharding-jdbc 配置 +sharding jdbc: + data-sources: + ds0: { type: HikariCP, ... } + ds1: { type: HikariCP, ... } + + sharding: + tables: + orders: + actual-data-nodes: ds$->{0..1}.orders$->{0..1} + table-strategy: + standard: + sharding-column: user_id + sharding-algorithm-name: user-id-mod + key-generate-strategy: + column: order_id + key-generator-name: snowflake + + sharding-algorithms: + user-id-mod: + type: MOD + props: + sharding-count: 2 +``` + +## 跨库查询方案 + +> [!question] 经典难题 +> 订单服务需要展示用户的姓名和手机号来做收货地址。但用户信息在用户库,订单数据在订单库——怎么办? + +### 方案对比 + +| 方案 | 描述 | 性能 | 复杂度 | 适用场景 | +|------|------|------|--------|---------| +| **冗余字段** | 订单表存用户名字段 | ⭐⭐⭐⭐⭐ | 低 | 只读字段,变更频率低 | +| **接口组装** | 先查订单,再调用户服务补全 | ⭐⭐⭐ | 中 | 偶尔需要关联的场景 | +| **CQRS / 宽表** | 异步同步一份宽表用于查询 | ⭐⭐⭐⭐ | 高 | 高频关联查询 | +| **搜索引擎** | ES/Kibana 做关联查询 | ⭐⭐⭐⭐ | 中高 | 复杂搜索 + 聚合 | + +### 冗余字段实践 + +```sql +-- 订单表冗余关键字段 +CREATE TABLE orders ( + id BIGINT PRIMARY KEY, + user_id BIGINT NOT NULL, + username VARCHAR(64), -- 冗余用户名(快照,不参与编辑) + phone VARCHAR(20), -- 冗余手机号(脱敏存储) + created_at TIMESTAMP DEFAULT NOW(), + INDEX idx_user (user_id) +); +``` + +> [!note] 冗余数据的维护 +> +> 用户改名字了怎么办?**不改订单表**。订单上的 username 是该时刻的"快照"——它反映的是下单时的状态,不是当前状态。这符合业务语义。 +> +> 如果需要批量更新冗余字段(如用户头像),通过消息队列异步通知订单服务更新。 + +## 数据库迁移工具 + +```mermaid +graph LR + Dev["开发环境"] -->|"flyway migrate"| Stage["Staging"] + Stage -->|"人工审批"| Prod["Production"] + Prod -->|"flyway validate"| Check["校验版本一致性"] +``` + +| 工具 | 语言 | 特点 | +|------|------|------| +| **Flyway** | Java | 基于文件命名,SQL 脚本方式,简单直观 | +| **Liquibase** | Java | XML/YAML/JSON 格式,支持回滚生成 | +| **golang-migrate** | Go | CLI 工具,轻量,适合 Go 项目 | + +### Flyway 迁移流程 + +``` +db/migration/ +├── V1__create_users_table.sql +├── V2__add_user_phone.sql +├── V3__create_orders_table.sql +└── V4__add_order_status_enum.sql +``` + +执行顺序:V1 → V2 → V3 → V4。Flyway 内部维护 `_schema_version` 表追踪已执行的迁移。 + +## 关联笔记 + +- [[03-数据一致性/分布式事务/README]] — 拆分后的数据一致性问题 +- [[01-基础概念]] — DDD 限界上下文与数据库拆分的对应关系 diff --git a/hzh/MS/04-可观测性.md b/hzh/MS/04-可观测性.md deleted file mode 100644 index be41122..0000000 --- a/hzh/MS/04-可观测性.md +++ /dev/null @@ -1,446 +0,0 @@ ---- -tags: [microservice, observability, monitoring, tracing] -create time: 2026-04-29 12:04 ---- - -# 可观测性 - -## 概述 - -微服务架构下,一个请求可能穿越十几个甚至上百个服务。**排查问题就像在大海捞针**。可观测性通过三大支柱(Metrics、Logging、Tracing)让系统行为变得"可见"。 - -> [!question] 核心思考 -> 单体应用出问题,SSH 到服务器上 `tail -f` 日志就能定位。但当你有 50 个服务、每个 3 个副本,日志散落在不同容器里——你还能用同样的方式排障吗?如果不能,什么体系能替代它? - -### 为什么需要独立的"可观测性"体系? - -```mermaid -flowchart LR - subgraph MONO["单体系统"] - S[Service] --> DB[(DB)] - LOG["同一份日志
一目了然"] - S --> LOG - end - - subgraph MICRO["微服务系统"] - REQ("Request") --> GW["Gateway"] - GW --> O1["Order Svc"] - GW --> U1["User Svc"] - O1 --> PAY["Payment Svc"] - O1 --> INV["Inventory Svc"] - PAY --> DB1[(DB)] - INV --> DB2[(DB)] - - style O1 fill:#faa - style U1 fill:#faa - style PAY fill:#faa - style INV fill:#faa - end - - NOTE["问题:日志分散、链路断裂
传统监控无法回答'这个请求经历了什么'"] - MICRO --> NOTE -``` - -可观测性与监控的本质区别:**监控系统告诉你"出事了"**(已知未知),**可观测性系统让你去探究"为什么会出事"**(未知未知)。 - -### SLO 与 Error Budget - -可观测性的终极目标不是收集数据,而是**支撑决策**。SLO(Service Level Objective)将技术指标映射到用户体验。 - -> [!summary] SLI / SLO / SLA 三层模型 -> -> | 层级 | 含义 | 示例 | -> |------|------|------| -> | **SLI** (Indicator) | 实际测量的指标 | P99 延迟 = 120ms | -> | **SLO** (Objective) | 内部目标值 | P99 延迟 < 200ms(99.9% 的请求) | -> | **SLA** (Agreement) | 对外的契约承诺 | 可用性 ≥ 99.95%,否则赔偿 | - -> [!tip] Error Budget(错误预算) -> -> 如果 SLO 是 99.9%,那么每月允许的错误时间 = 30 × 24 × 60 × 0.1% ≈ **43 分钟**。 -> -> - **预算充足时**:可以大胆发布新功能、尝试激进方案 -> - **预算耗尽时**:冻结非功能性变更,专注稳定性修复 -> -> 这构成了"创新"和"稳定"之间的动态平衡机制。 - -## 三大支柱 - -| 支柱 | 回答的问题 | 典型工具 | -|------|-----------|---------| -| **Metrics** | 系统现在健康吗? — 趋势、告警 | Prometheus + Grafana | -| **Logging** | 具体发生了什么? — 历史回溯 | ELK / Loki | -| **Tracing** | 请求在哪一步慢了/失败了? — 链路追踪 | Jaeger / Zipkin | - -### Metrics:系统的脉搏 - -Metrics 回答的问题是:**系统现在健康吗?**——通过聚合后的数值揭示趋势。 - -> [!summary] RED 和 USE 方法 -> -> | 方法 | 适用于 | 指标 | -> |------|--------|------| -> | **RED** (Rate, Errors, Duration) | 有状态服务(API) | QPS、错误率、响应时间 P99 | -> | **USE** (Utilization, Saturation, Errors) | 基础设施(CPU/内存/网络) | 使用率、饱和度、错误数 | - -核心指标类型: - -- **Counter(计数器)**:只增不减,如 `http_requests_total`。适合统计请求总量、错误总数 -- **Gauge(仪表盘)**:可增可减,如 `queue_depth`、在线用户数 -- **Histogram(直方图)**:样本分布,自动分桶统计,如请求延迟的 P50/P90/P99 -- **Summary(摘要)**:类似 Histogram,但客户端计算分位数(Go SDK 默认) - -```go -// Go 示例:定义并注册 Prometheus 指标 -var ( - // Counter: HTTP 请求总数,按 method 和 status 标签分类 - httpRequestsTotal = prometheus.NewCounterVec( - prometheus.CounterOpts{ - Name: "http_requests_total", - Help: "Total HTTP requests", - }, - []string{"method", "status"}, - ) - - // Histogram: API 响应延迟分布 - apiLatency = prometheus.NewHistogramVec( - prometheus.HistogramOpts{ - Name: "api_latency_seconds", - Help: "API latency distribution", - Buckets: prometheus.DefBuckets, // [0.005, 0.01, 0.025, 0.05, 0.1, 0.25, 0.5, 1, 2.5, 5, 10] - }, - []string{"endpoint"}, - ) -) - -func init() { - prometheus.MustRegister(httpRequestsTotal, apiLatency) -} -``` - -> [!tip] 解读提示 -> Counter 用于统计"发生了多少",Histogram 用于理解"有多慢"。一个完整的告警面板通常同时展示两者:QPS 突降 + 延迟飙升 = 大概率出事了。 - -在 Grafana 中的典型视图: - -```mermaid -graph TB - subgraph GRAFANA["Grafana Dashboard"] - G1["QPS
[Counter]"] -->|关联分析| G2["P99 Latency
[Histogram]"] - G3["错误率
[Counter %]"] -->|关联分析| G2 - G2 --> G4["告警规则
阈值触发"] - end - - GRAFANA -.->|从 Prometheus 拉取| PROM[(Prometheus)] -``` - -### Logging:集中化日志 - -Logging 回答的问题是:**具体发生了什么?**——通过原始事件记录提供上下文。 - -- **所有服务的日志必须集中收集**,不能散落在各台机器上 -- 日志格式推荐 JSON,便于解析 -- 每条日志必须携带 `trace_id`,与 Tracing 串联 - -```json -{ - "timestamp": "2026-04-29T12:00:00Z", - "level": "ERROR", - "service": "order-service", - "trace_id": "abc-123-def", - "span_id": "span-456", - "msg": "failed to connect payment service", - "caller": "payment/client.go:42", - "cost_ms": 30000 -} -``` - -#### 结构化日志的核心字段 - -> [!note] 最小集合(必选) -> -> - `timestamp` — ISO 8601 格式,统一时区 UTC -> - `level` — TRACE / DEBUG / INFO / WARN / ERROR / FATAL -> - `service` — 微服务名称 -> - `trace_id` — 链路追踪 ID,用于跨服务关联 -> - `msg` — 人类可读的消息描述 - -> [!warning] 日志最佳实践 -> -> - **不要记录敏感信息**:密码、Token、身份证号等绝不能出现在日志中 -> - **INFO 级记录关键业务事件**:下单成功、支付回调——这些是排查业务问题的主线 -> - **WARN 级记录异常但不致命**:重试了一次、降级走了备用方案 -> - **ERROR 级必须有 trace_id 和错误堆栈**,否则毫无价值 - -#### 日志采集管线 - -```mermaid -flowchart LR - APP[应用容器] -->|stdout/stderr| LO[FLUENTD / Filebeat] - LO -->|TCP/Lumberjack| LO2[(Logstash / Loki)] - LO2 -->|解析 & 过滤| PIPE[Pipeline] - PIPE -->|写入| ES[(Elasticsearch)] - PIPE -->|跳转| GRAF[Grafana / Kibana] - - style PIPE fill:#ff9 -``` - -**常见方案对比**: - -| 方案 | 存储 | 查询能力 | 适用场景 | -|------|------|---------|---------| -| ELK (Elasticsearch) | ES 集群 | 全文检索强大,灵活度高 | 日志量大、需要复杂分析的团队 | -| Loki + Grafana | 对象存储 (S3) | 对标 LogQL,轻量高效 | 已经在用 Grafana 的团队 | -| CloudWatch / SLS | 云厂商托管 | 开箱即用,成本较高 | 全云原生环境 | - -> [!tip] 日志采样策略 -> -> 生产环境中并非所有日志都要采集。**正常路径采 1%,异常路径全量**。这既能大幅降低存储成本,又能在出问题时有足够的调试数据可用。采样逻辑建议写在日志库中,而非业务代码里。 - -### Tracing:分布式链路追踪 - -Tracing 回答的问题是:**请求在哪一步慢了 / 失败了?**——通过完整的调用链路定位瓶颈和故障。 - -一次请求在多个服务中的完整调用路径: - -```mermaid -flowchart LR - RID["Request
trace_id: abc-123"] - - subgraph SPANS["调用链 (Trace)"] - direction TB - S1["Span #1
API Gateway
5ms"] - S2["Span #2
Order Service
70ms"] - S3["Span #3
Payment RPC
160ms"] - S4["Span #4
Inventory RPC
70ms"] - - S1 --> S2 --> S3 - S2 --> S4 - end - - RID --> S1 - style S3 fill:#ff9999 - - note["Span #3 耗时最长 → Payment 是瓶颈"] - S3 -.-> note -``` - -> [!tip] 关键实践 -> - 确保 `trace_id` 在整个调用链中传递(HTTP Header 或 MQ Message Header) -> - 采样策略:**全量采集开发环境**,**生产环境按百分比采样**(避免存储爆炸) -> - 重点关注的 Span 标签:HTTP method、status code、database query -> - **Span 不要嵌套过深**,一个 HTTP 调用或 DB 查询对应一个 Span 即可 - -#### Context Propagation(上下文传播) - -trace_id 如何从上游服务传递到下游? - -```go -// 1. 入站:从 HTTP Header 提取 trace context -func Middleware(next http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // OpenTelemetry SDK 自动从 Header 恢复 span context - ctx := propagator.Extract(r.Context(), headerReader{r.Header}) - - // 2. 创建新的 span - ctx, span := tracer.Start(ctx, "handleOrder") - defer span.End() - - // 3. 将 trace context 注入到新请求的 Header 中 - r = r.WithContext(ctx) - propagator.Inject(ctx, headerWriter{r.Header}) - - next.ServeHTTP(w, r) - }) -} -``` - -```json -// HTTP Header 中实际传递的字段 (W3C Trace Context) -{ - "traceparent": "00-abc123def456...-789ghi012jkl-01", - "tracestate": "congo=t61rcWkgMzE" -} -``` - -> [!note] W3C Trace Context 标准 -> -> `traceparent` 格式:`version-trace_id-span_id-flags`,如 `00-{32位trace_id}-{16位span_id}-{2位flags}`。 -> 这是 W3C 推荐的标准,被 OpenTelemetry、Jaeger、Zipkin 等主流方案统一支持。**无需再自定义 Header**。 - -## 告警设计原则 - -### 告警疲劳是常态 - -> [!warning] 核心问题 -> -> 如果一个团队每天收到 200 条告警,其中 195 条是误报或无需处理,工程师会对剩下的 5 条真正重要的告警产生"脱敏"。这就是 **告警疲劳**。 - -- **告警要能 actionable**:收到告警后知道该做什么,否则不要告 -- **区分级别**:P0(电话叫醒)vs P1(工单处理)vs P2(次日处理) -- **基于 SLO 告警**:不是 CPU > 80% 就告警,而是用户感知到延迟增加时才告 -- **降噪**:同一个根因可能触发连锁告警,需要抑制机制 - -### 告警层级设计 - -```mermaid -flowchart TD - subgraph L1["L1: 页面上的用户受损"] - A1["HTTP 5xx 错误率 > 1%"] - A2["核心接口 P99 > 2s"] - end - - subgraph L2["L2: 系统健康度下降"] - B1["单个服务错误率 > 5%"] - B2["下游依赖超时率升高"] - end - - subgraph L3["L3: 资源预警"] - C1["磁盘使用 > 70%"] - C2["内存使用 > 80%"] - end - - A1 -->|P0 - 电话 + IM| ONCALL[On-Call 值班] - A2 -->|P0 - 电话 + IM| ONCALL - B1 -->|P1 - IM 通知| OPS[运维群] - B2 -->|P1 - IM 通知| OPS - C1 -->|P2 - 日报汇总| TEAM[团队看板] - C2 -->|P2 - 日报汇总| TEAM -``` - -> [!tip] 告警规则编写清单 -> -> 每条告警规则都应该能回答以下问题: -> 1. **什么问题?** — 清晰描述故障现象 -> 2. **谁负责?** — 指定明确的责任人 / On-Call -> 3. **怎么修复?** — 链接到 Runbook / 处置文档 -> 4. **多久升级?** — P1 无人响应则自动升级到上级 - -## OpenTelemetry:统一的可观测性标准 - -过去,Metrics、Logging、Tracing 各用各的工具链,数据割裂在完全不同的系统中。**OpenTelemetry (OTel)** 试图解决这个问题。 - -> [!summary] OTel 的核心思想 -> -> 一套 SDK → 三种信号(Metrics、Logs、Traces)→ 任意后端(Jaeger、Prometheus、ELK...) -> -> 这意味着:**你不需要在代码里为不同后端写多套采集逻辑**。换一个后端只需要改配置,不改代码。 - -### OTel Architecture - -```mermaid -flowchart LR - subgraph APP["你的应用"] - OTEL_SDK["OpenTelemetry SDK
auto-instrumentation Agent"] - end - - OTEL_SDK -->|SDK 自动采集| COLLECTOR[(OpenTelemetry Collector)] - - COLLECTOR -->|OTLP| JAEGER[(Jaeger
Tracing)] - COLLECTOR -->|OTLP| PROM[(Prometheus
Metrics)] - COLLECTOR -->|OTLP| LOKI[(Loki
Logging)] - - style OTEL_SDK fill:#ff9 -``` - -> [!note] 为什么需要 Collector? -> -> Collector 是一个独立的服务进程,承担三个职责: -> 1. **接收**:从应用的 SDK 接收数据 -> 2. **处理**:过滤、采样、富化(添加额外标签) -> 3. **导出**:将数据转发到不同的后端存储 -> -> 这样你的应用只关心"发送数据",Collector 负责"怎么存、存到哪里"。两者解耦。 - -### Go 中接入 OpenTelemetry - -```go -// 1. 初始化 TracerProvider(Tracing) -provider := sdktrace.NewTracerProvider( - sdktrace.WithBatcherExporter(exporter), // 异步批量上报,不阻塞业务 -) -defer provider.Shutdown(context.Background()) - -tracer := provider.Tracer("order-service") - -// 2. 在 handler 中创建 span -func handleOrder(w http.ResponseWriter, r *http.Request) { - ctx, span := tracer.Start(r.Context(), "handleOrder") - defer span.End() - - // 设置丰富的 Span 属性(供查询和过滤) - span.SetAttributes( - attribute.String("http.method", r.Method), - attribute.Int("http.status_code", 200), - attribute.String("user.id", userID), - ) - - // 调用下游服务(trace context 自动传播) - resp, err := callPayment(ctx) - if err != nil { - span.RecordError(err) - span.SetStatus(codes.Error, err.Error()) - return - } -} -``` - -> [!tip] 渐进式落地路线 -> -> 1. **第一步**:先接 Tracing(价值最大、投入最小),覆盖核心链路 -> 2. **第二步**:加 Metrics(Prometheus + Grafana),建立基础监控 -> 3. **第三步**:集中 Logging(Loki / ELK),与 trace_id 关联 -> 4. **第四步**:全量上 OTel Collector,统一管理三件套 -> -> 不要试图一步到位——Tracing 的 ROI 最高,先从这里开始。 - -## Dashboard 与 On-Call 实践 - -收集了这么多数据,下一步是怎么用。 - -### Service Health Dashboard - -一个优秀的 Dashboard 应该让任何团队成员在 **30 秒内**了解服务的整体状态: - -```mermaid -flowchart TB - subgraph DASH["Service Health Dashboard"] - ROW1["🔴 可用性 & 错误率 — 第一眼判断"] - ROW2["🟡 性能指标 — P50/P90/P99 趋势"] - ROW3["🔵 基础设施 — CPU/内存/连接数"] - ROW4["⚫ 业务指标 — 订单量/支付成功率"] - end - - ROW1 --> JUDGE{是否异常?} - ROW2 --> JUDGE - ROW3 --> JUDGE - ROW4 --> JUDGE - - JUDGE --"否" --> NORMAL["一切正常 ✓"] - JUDGE --"是" --> ALERT["触发告警 → On-Call"] -``` - -### Runbook:告警处置手册 - -告警来了之后该怎么办?答案不应存在某个资深工程师的脑子里。 - -> [!summary] Runbook 模板 -> -> | 字段 | 内容 | -> |------|------| -> | **标题** | `支付服务 P99 延迟 > 2s 持续 5 分钟` | -> | **影响范围** | 下单接口超时,用户体验受损 | -> | **检查步骤** | ① 看 Grafana 延迟面板确认峰值时间点 → ② 查同期部署记录 → ③ 检查下游 DB 慢查询 | -> | **常见原因** | ① 新上线代码有性能 regression → ② DB 连接池耗尽 → ③ 下游超时风暴 | -> | **快速恢复** | ① 回滚最近一次发布 → ② 扩容支付服务实例 → ③ 开启熔断降低下游压力 | -> | **彻底解决** | 排查根本原因,补充回归测试,完善容量规划 | - -> [!question] 反模式自检 -> -> 如果你的 On-Call 流程是这样的:收到告警 → 群里问 "有人遇到过吗?" → 等半天有人回复 → SSH 到服务器上翻日志 → ……——这说明你们的可观测性体系还不够成熟。**好的 On-Call 体验 = 清晰的告警 + 完善的 Runbook + 一键处置工具**。 - -## 关联笔记 - -- [[02-服务治理]] — 熔断器可以触发告警,与监控系统联动;分布式链路追踪章节也有详细说明 -- [[05-部署运维]] — K8s Liveness/Readiness Probe 是可观测性的基础 -- [[01-基础概念]] — 微服务的整体架构认知 diff --git a/hzh/MS/04-可观测性/Metrics监控/README.md b/hzh/MS/04-可观测性/Metrics监控/README.md new file mode 100644 index 0000000..54871b8 --- /dev/null +++ b/hzh/MS/04-可观测性/Metrics监控/README.md @@ -0,0 +1,160 @@ +--- +tags: [microservice, metrics, prometheus, grafana, sre] +create time: 2026-05-05 +--- + +# Metrics 监控 + +## 概述 + +Metrics 回答的问题是:**系统现在健康吗?**——通过聚合后的数值揭示趋势。 + +```mermaid +graph TB + App["应用服务"] -->|Push / Scrape| PROM[(Prometheus)] + PROM --> GRAF["Grafana Dashboard"] + PROM --> ALERT["Alertmanager"] + + style PROM fill:#e3f2fd + style GRAF fill:#fff3e0 + style ALERT fill:#fce4ec +``` + +## 指标类型 + +| 类型 | 含义 | 特点 | 示例 | +|------|------|------|------| +| **Counter** | 只增不减的计数器 | 可 Reset(重启) | `http_requests_total` | +| **Gauge** | 可升可降的仪表盘 | 反映当前状态 | `queue_depth`, `cpu_temp` | +| **Histogram** | 样本分布,自动分桶 | 计算 P50/P90/P99 | `api_latency_seconds` | +| **Summary** | 类似 Histogram,客户端算分位 | Go SDK 默认类型 | `grpc_duration_seconds` | + +### Counter vs Gauge 场景选择 + +```mermaid +flowchart LR + Q{"这个值是
只增不减的吗?"} + + Q -- "是" --> C["Counter
请求数、错误数、订单量"] + Q -- "否" --> G["Gauge
在线用户数、队列长度、内存使用量"] + + style C fill:#c8e6c9 + style G fill:#bbdefb +``` + +## RED 方法 (针对有状态服务) + +适用于 API、微服务等有明确请求/响应的服务。 + +| 指标 | 公式 | 说明 | +|------|------|------| +| **Rate** | `rate(http_requests_total[5m])` | 每秒请求量 (QPS) | +| **Errors** | `rate(http_requests_total{status="5xx"}[5m])` | 每秒错误数 | +| **Duration** | `histogram_quantile(0.99, rate(api_latency_bucket[5m]))` | P99 响应时间 | + +> [!tip] PromQL 关键函数 +> +> - `rate()` — 计算 Counter 每秒增长率(必须用于 Counter) +> - `irate()` — 即时速率,对突发更敏感 +> - `histogram_quantile(0.99, ...)` — 从直方图计算分位数 +> - `increase()` — 时间段内的增长总量 +> - `avg() / max() / min()` — 基础聚合函数 + +## USE 方法 (针对基础设施) + +适用于 CPU、内存、网络、磁盘等底层资源监控。 + +| 指标 | 说明 | Grafana PromQL 示例 | +|------|------|---------------------| +| **Utilization** | 使用率 | `1 - avg(rate(node_cpu_seconds_total{mode="idle"}[5m]))` | +| **Saturation** | 饱和度 | `node_memory_MemAvailable_bytes / node_memory_MemTotal_bytes` | +| **Errors** | 错误数 | `rate(node_network_receive_errs_total[5m])` | + +## Go Prometheus 集成 + +```go +var ( + httpRequestsTotal = prometheus.NewCounterVec( + prometheus.CounterOpts{ + Name: "http_requests_total", + Help: "Total HTTP requests by method and status", + }, + []string{"method", "status"}, + ) + + apiLatency = prometheus.NewHistogramVec( + prometheus.HistogramOpts{ + Name: "api_latency_seconds", + Help: "API latency distribution", + Buckets: prometheus.DefBuckets, // [0.005, 0.01, ..., 10] + }, + []string{"endpoint"}, + ) +) + +func init() { + prometheus.MustRegister(httpRequestsTotal, apiLatency) +} + +// Middleware 中使用 +func MetricsMiddleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + start := time.Now() + next.ServeHTTP(w, r) + + duration := time.Since(start).Seconds() + httpRequestsTotal.WithLabelValues(r.Method, fmt.Sprintf("%d", w.Status())).Inc() + apiLatency.WithLabelValues(r.URL.Path).Observe(duration) + }) +} +``` + +## Dashboard 设计原则 + +一个优秀的 Dashboard 应该让任何团队成员在 **30 秒内**了解服务的整体状态: + +```mermaid +flowchart TB + subgraph DASH["Service Health Dashboard"] + ROW1["🔴 可用性 & 错误率 — 第一眼判断"] + ROW2["🟡 性能指标 — P50/P90/P99 趋势"] + ROW3["🔵 基础设施 — CPU/内存/连接数"] + ROW4["⚫ 业务指标 — 订单量/支付成功率"] + end + + ROW1 --> JUDGE{是否异常?} + ROW2 --> JUDGE + ROW3 --> JUDGE + ROW4 --> JUDGE + + JUDGE --"否" --> NORMAL["一切正常 ✓"] + JUDGE --"是" --> ALERT["触发告警 → On-Call"] +``` + +### Dashboard 布局模板 + +``` +┌─────────────────────────────────────────────────┐ +│ Row 1: 🔴 Service Availability │ +│ ├─ QPS (Rate) ┌─ Error Rate (%) │ +│ ├─ Active Connections └─ 5xx Count │ +├─────────────────────────────────────────────────┤ +│ Row 2: 🟡 Performance │ +│ ├─ P50 Latency ┌─ P90 Latency │ +│ ├─ P99 Latency └─ Slow Requests (>1s) │ +├─────────────────────────────────────────────────┤ +│ Row 3: 🔵 Infrastructure │ +│ ├─ CPU % ┌─ Memory Usage │ +│ ├─ GC Pause Time └─ Goroutine Count │ +├─────────────────────────────────────────────────┤ +│ Row 4: ⚫ Business Metrics │ +│ ├─ Orders/Minute ┌─ Payment Success Rate │ +│ └─ New Users/Day └─ Failed Transactions │ +└─────────────────────────────────────────────────┘ +``` + +## 关联笔记 + +- [[04-可观测性/告警管理/README]] — Metrics 是告警的基础数据来源 +- [[04-可观测性/链路追踪/README]] — Tracing 与 Metrics 互补,定位具体故障 +- [[05-部署运维/SRE实践/README]] — SLO 基于 Metrics 数据 diff --git a/hzh/MS/04-可观测性/README.md b/hzh/MS/04-可观测性/README.md new file mode 100644 index 0000000..8c12aef --- /dev/null +++ b/hzh/MS/04-可观测性/README.md @@ -0,0 +1,78 @@ +--- +tags: [microservice, observability] +create time: 2026-04-29 12:04 +--- + +# 可观测性 + +## 概述 + +微服务架构下,一个请求可能穿越十几个甚至上百个服务。**排查问题就像在大海捞针**。可观测性通过三大支柱(Metrics、Logging、Tracing)让系统行为变得"可见"。 + +## 知识体系 + +```mermaid +graph LR + A["Metrics 监控"] --> C["日志系统"] + B["链路追踪"] --> C + C --> D["告警管理"] + + style A fill:#e3f2fd + style B fill:#fff3e0 + style C fill:#fce4ec + style D fill:#e8f5e9 +``` + +| # | 主题 | 核心问题 | +|---|------|----------| +| 1 | [[04-可观测性/Metrics监控/README]] | 怎么量化系统的健康度?RED/USE 方法怎么用? | +| 2 | [[04-可观测性/日志系统/README]] | 日志怎么集中收集?结构化日志的最佳实践是什么? | +| 3 | [[04-可观测性/链路追踪/README]] | trace_id 如何贯穿跨服务调用链?OpenTelemetry 怎么用? | +| 4 | [[04-可观测性/告警管理/README]] | 如何设计告警避免疲劳?SLO/Error Budget 怎么做? | + +### 为什么需要独立的"可观测性"体系? + +```mermaid +flowchart LR + subgraph MONO["单体系统"] + S[Service] --> DB[(DB)] + LOG["同一份日志
一目了然"] + S --> LOG + end + + subgraph MICRO["微服务系统"] + REQ("Request") --> GW["Gateway"] + GW --> O1["Order Svc"] + GW --> U1["User Svc"] + O1 --> PAY["Payment Svc"] + O1 --> INV["Inventory Svc"] + PAY --> DB1[(DB)] + INV --> DB2[(DB)] + + style O1 fill:#faa + style U1 fill:#faa + style PAY fill:#faa + style INV fill:#faa + end + + NOTE["问题:日志分散、链路断裂
传统监控无法回答'这个请求经历了什么'"] + MICRO --> NOTE +``` + +> [!keypoint] 可观测性与监控的本质区别 +> +> **监控系统**告诉你"出事了"(已知未知),**可观测性系统**让你去探究"为什么会出事"(未知未知)。 + +### 三大支柱 + +| 支柱 | 回答的问题 | 典型工具 | +|------|-----------|---------| +| **Metrics** | 系统现在健康吗?— 趋势、告警 | Prometheus + Grafana | +| **Logging** | 具体发生了什么?— 历史回溯 | ELK / Loki | +| **Tracing** | 请求在哪一步慢了/失败了?— 链路追踪 | Jaeger / Zipkin | + +### 关联笔记 + +- [[02-服务治理]] — 熔断器可以触发告警,与监控系统联动 +- [[05-部署运维/Kubernetes/README]] — K8s Liveness/Readiness Probe 是可观测性的基础 +- [[hzh/MS/README.md]] — 完整微服务知识索引 diff --git a/hzh/MS/04-可观测性/告警管理/README.md b/hzh/MS/04-可观测性/告警管理/README.md new file mode 100644 index 0000000..56babf8 --- /dev/null +++ b/hzh/MS/04-可观测性/告警管理/README.md @@ -0,0 +1,186 @@ +--- +tags: [microservice, alerting, slo, error-budget, on-call] +create time: 2026-05-05 +--- + +# 告警管理 + +## 概述 + +收集了这么多 Metrics、Logs 和 Traces,下一步是**用数据驱动决策**。告警系统是在问题影响用户之前及时响应的最后一道防线。 + +> [!warning] 核心挑战:告警疲劳 +> +> 如果一个团队每天收到 200 条告警,其中 195 条是误报或无需处理,工程师会对剩下的 5 条真正重要的告警产生"脱敏"。这就是 **告警疲劳 (Alert Fatigue)**。 + +## SLO / Error Budget 告警 + +### SLI / SLO / SLA 三层模型 + +| 层级 | 全称 | 含义 | 示例 | +|------|------|------|------| +| **SLI** | Service Level Indicator | 实际测量的指标 | P99 延迟 = 120ms | +| **SLO** | Service Level Objective | 内部目标值 | P99 延迟 < 200ms(99.9%) | +| **SLA** | Service Level Agreement | 对外契约承诺 | 可用性 ≥ 99.95%,否则赔偿 | + +```mermaid +flowchart LR + Real["真实用户请求"] --> SLI{成功率达标?} + SLI -- 是 --> SLO_OK["✅ SLO 达成"] + SLI -- 否 --> BUDGET["消耗错误预算"] + + BUDGET --> Left{"预算 > 0?"} + Left -- 是 --> Continue["继续发布新功能 🚀"] + Left -- 否 --> Freeze["冻结发布 ❄️
专注稳定性修复"] + + style SLO_OK fill:#e8f5e9 + style Freeze fill:#ffebee +``` + +### 错误预算计算 + +``` +每月允许停机时间 = 月总分钟数 × (1 - SLO) + +SLO = 99.9% → 30×24×60×0.1% = 43.2 分钟 +SLO = 99.95% → 30×24×60×0.05% = 21.6 分钟 +SLO = 99.99% → 30×24×60×0.01% = 4.3 分钟 +SLO = 99% → 30×24×60×1% = 432 分钟 ≈ 7.2 小时(几乎无意义) +``` + +### 基于错误预算的告警策略 + +```mermaid +flowchart TD + BudgetLeft{"错误预算剩余"} + + BudgetLeft -- "> 50%" --> Aggressive["激进策略:
保持常规告警阈值
加快迭代节奏"] + BudgetLeft -- "10~50%" --> Moderate["保守策略:
降低告警阈值
收紧发布频率"] + BudgetLeft -- "< 10%" --> Emergency["紧急策略:
所有非紧急告警暂停
全员关注稳定性"] + + style Aggressive fill:#e8f5e9 + style Moderate fill:#fff3e0 + style Emergency fill:#ffebee +``` + +## 告警设计原则 + +### 1. 告警必须 Actionable + +收到告警后知道该做什么,否则不要告。 + +每条告警规则都应该能回答以下问题: + +| # | 问题 | 示例答案 | +|---|------|---------| +| 1 | **什么问题?** | `支付服务 P99 延迟 > 2s 持续 5 分钟` | +| 2 | **谁负责?** | `支付组 On-Call: @zhangsan` | +| 3 | **怎么修复?** | 链接到 Runbook `📖 Runbook: 支付超时排查指南` | +| 4 | **多久升级?** | `P1 无人响应 → 5min 后升级至组长` | + +### 2. 分级设计 + +```mermaid +flowchart TD + subgraph L1["P0 — 立即响应(电话 + IM)"] + A1["HTTP 5xx 错误率 > 1% 持续 2min"] + A2["核心接口 P99 > 2s 持续 5min"] + A3["数据库连接池耗尽"] + end + + subgraph L2["P1 — 当天处理(IM 通知)"] + B1["单个服务错误率 > 5%"] + B2["下游依赖超时率升高"] + B3["磁盘使用 > 75%"] + end + + subgraph L3["P2 — 本周修复(日报汇总)"] + C1["CPU 使用率 > 80% 持续 1h"] + C2["内存使用 > 85% 持续 1h"] + C3["非核心服务 P99 异常"] + end + + L1 --> ONCALL[On-Call 值班群] + L2 --> OPS[运维监控群] + L3 --> DAILY["每日健康报告"] + + style L1 fill:#ffebee + style L2 fill:#fff3e0 + style L3 fill:#e3f2fd +``` + +### 3. 降噪与抑制 + +同一个根因可能触发连锁告警,需要抑制机制: + +``` +┌──────────┐ ┌──────────┐ ┌──────────┐ +│ DB 宕机 │───→│ 服务 A 报错│───→│ 关联告警 │ +└──────────┘ └──────────┘ │ 只发 Root Cause│ + │ 其他静默 │ + └──────────┘ +``` + +| 技术 | 说明 | +|------|------| +| **告警分组** | 同一根因的多个告警合并为一条 | +| **静默期** | Pod 重启后 5 分钟内不重复告警 | +| **维护窗口** | 已知变更期间暂时抑制非关键告警 | +| **继承抑制** | DB 挂了 → 自动抑制依赖 DB 的所有服务的告警 | + +## On-Call 最佳实践 + +### Runbook:告警处置手册 + +> [!summary] Runbook 模板 +> +> | 字段 | 内容 | +> |------|------| +> | **标题** | `支付服务 P99 延迟 > 2s 持续 5 分钟` | +> | **影响范围** | 下单接口超时,用户体验受损 | +> | **检查步骤** | ① 看 Grafana 延迟面板确认峰值时间点 → ② 查同期部署记录 → ③ 检查下游 DB 慢查询 | +> | **常见原因** | ① 新代码性能 regression → ② DB 连接池耗尽 → ③ 下游超时风暴 | +> | **快速恢复** | ① 回滚最近一次发布 → ② 扩容实例 → ③ 开启熔断降负载 | +> | **彻底解决** | 排查根本原因,补充回归测试,完善容量规划 | + +### Blameless Postmortem + +事故复盘不问"谁干的",问"流程哪里可以改进": + +```mermaid +flowchart LR + Incident["事故发生"] --> Contain["控制影响面"] + Contain --> Investigate["调查根因"] + Investigate --> Action["制定改进行动"] + Action --> Share["分享经验教训"] + + style Incident fill:#ffebee + style Contain fill:#fff3e0 + style Investigate fill:#e3f2fd + style Action fill:#e8f5e9 + style Share fill:#f3e5f5 +``` + +> [!tip] 事后复盘三问 +> +> 1. **什么导致了这次事故?** (技术根因) +> 2. **为什么监控系统没有更早发现?** (检测延迟) +> 3. **下次如何避免同类问题?** (流程改进) + +## 告警疲劳自检清单 + +如果你的团队出现以下情况,说明告警体系需要重构: + +- [ ] On-Call 工程师下班前都要关闭/忽略一堆告警 +- [ ] 有人会在群里说 "这个告警不用管" +- [ ] 同一个服务每天都触发相同的告警 +- [ ] 新同事不知道哪些告警是真的严重 +- [ ] PagerDuty/Oncall 通知已读率低于 50% + +如果以上超过 2 项 ✅ —— 你的告警需要认真治理了。 + +## 关联笔记 + +- [[04-可观测性/Metrics监控/README]] — Metrics 是告警的数据来源 +- [[05-部署运维/SRE实践/README]] — SLO/Error Budget 的详细方法论 +- [[02-服务治理/容错模式/README]] — 熔断器状态可以作为告警信号 diff --git a/hzh/MS/04-可观测性/日志系统/README.md b/hzh/MS/04-可观测性/日志系统/README.md new file mode 100644 index 0000000..8eb443c --- /dev/null +++ b/hzh/MS/04-可观测性/日志系统/README.md @@ -0,0 +1,149 @@ +--- +tags: [microservice, logging, elk, loki, structured-logging] +create time: 2026-05-05 +--- + +# 日志系统 + +## 概述 + +Logging 回答的问题是:**具体发生了什么?**——通过原始事件记录提供上下文。 + +在微服务架构中,所有服务的日志必须集中收集。散落在各台机器上的日志无法支撑有效的故障排查。 + +```mermaid +flowchart LR + APP[应用容器] -->|stdout/stderr| COLLECTOR[采集器
FLUENTD / Filebeat] + COLLECTOR --> PARSE["解析 & 过滤"] + PARSE --> ES[(Elasticsearch)] + PARSE --> LOKI[(Loki)] + ES --> GRAF["Grafana / Kibana"] + LOKI --> GRAF + + style COLLECTOR fill:#fff3e0 + style PARSE fill:#e3f2fd +``` + +## 结构化日志 + +**不要写纯文本日志**。推荐 JSON 格式,便于程序解析和查询: + +```json +{ + "timestamp": "2026-05-05T10:30:00Z", + "level": "ERROR", + "service": "order-service", + "trace_id": "abc-123-def", + "span_id": "span-456", + "msg": "failed to connect payment service", + "caller": "payment/client.go:42", + "cost_ms": 30000, + "user_id": "user-42" +} +``` + +### 最小字段集合(必选) + +| 字段 | 格式 | 说明 | +|------|------|------| +| `timestamp` | ISO 8601 (`2026-05-05T10:30:00Z`) | UTC 时区,不可省略 | +| `level` | TRACE / DEBUG / INFO / WARN / ERROR / FATAL | 大小写统一 | +| `service` | 字符串 | 微服务名称 | +| `trace_id` | 字符串 | 链路追踪 ID,与 Tracing 串联 | +| `msg` | 字符串 | 人类可读的消息描述 | + +### 日志级别使用规范 + +| 级别 | 使用场景 | 示例 | +|------|---------|------| +| **TRACE** | 调试级详细输出(仅开发环境) | 方法入参/出参、循环体内部状态 | +| **DEBUG** | 诊断信息 | 缓存命中率、连接池状态 | +| **INFO** | 关键业务事件 | 下单成功、支付回调、用户注册 | +| **WARN** | 异常但不致命 | 重试了一次、走降级、配置热更新 | +| **ERROR** | 影响单个请求的失败 | DB 超时、下游调用失败、空指针 | +| **FATAL** | 进程无法继续运行 | 配置文件缺失、依赖服务完全不可用 | + +> [!warning] 最佳实践 +> +> - **不要记录敏感信息**:密码、Token、身份证号等绝不能出现在日志中 +> - **INFO 级记录关键业务事件**——这些是排查业务问题的主线 +> - **WARN 级记录异常但不致命**——如重试、降级 +> - **ERROR 级必须有 trace_id 和错误堆栈**,否则毫无价值 + +## 日志采集管线 + +```mermaid +flowchart LR + App["业务应用"] -->|stdout/stderr| K8sPod["Pod 容器日志"] + K8sPod --> Collector["采集器
Filebeat / FluentBit"] + Collector --> Backend["存储后端
Elasticsearch / Loki"] + Backend --> UI["查询面板
Grafana / Kibana"] + + Collector -->|"过滤掉 health check"| Drop["丢弃无用日志"] + + style Drop fill:#ffebee,stroke:#ef5350 +``` + +### 采集器选型 + +| 组件 | 语言 | 资源消耗 | 特点 | +|------|------|---------|------| +| **Filebeat** (ELK) | Go | 低 | 轻量,适合 K8s DaemonSet | +| **Fluentd** | Ruby | 中 | 插件生态丰富,处理灵活 | +| **FluentBit** | C | 极低 | 边缘场景首选,K8s 官方推荐 | + +## 日志方案对比 + +| 方案 | 存储 | 查询能力 | 成本 | 适用场景 | +|------|------|---------|------|---------| +| **ELK** (Elasticsearch) | ES 集群 | 全文检索强大 | 高 | 日志量大、需要复杂分析 | +| **Loki + Grafana** | 对象存储 (S3) | Label 索引 + LogQL | 低 | 已在用 Grafana 的团队 | +| **云 SLS / CloudWatch** | 云托管 | 开箱即用 | 中~高 | 全云原生环境 | + +### Loki vs ELK 决策树 + +```mermaid +flowchart TD + Q{"需要全文检索吗?"} + + Q -- "否,按时间/Service/TraceId 查询" --> LOKI["✅ Loki
低成本、轻量"] + Q -- "是,复杂的全文搜索" --> ELK["✅ ELK
功能强大"] + + LOKI --> Check{"日日志量 > 1TB?"} + Check -- 是 --> ConsiderES["考虑混合架构
高频用 Loki,低频归档到 ES"] + Check -- 否 --> Done["直接用 Loki ✅"] + + ELK --> Check2{"团队有经验?"} + Check2 -- 是 --> UseES["直接上 ES ✅"] + Check2 -- 否 --> Managed["用托管版 (阿里云 SLS)"] +``` + +## 采样策略 + +生产环境中并非所有日志都要采集。**正常路径采 1%,异常路径全量**。 + +``` +正常 HTTP 200 → 1% 采样率 → 每小时 ~360 条日志 +异常 HTTP 5xx → 100% 全量采集 → 可能数千条日志 +DB 慢查询 → 100% 全量采集 → 重点关注 +健康检查 → 不采集 → 零成本 +``` + +> [!tip] 实现方式 +> 采样逻辑建议写在日志库的中间件层,而非业务代码里: +> ```go +> func SampleLogger(next logger.Logger, rate float64) logger.Logger { +> return &sampledLogger{ +> next: next, +> shouldSample: func() bool { +> return rand.Float64() < rate +> }, +> } +> } +> ``` + +## 关联笔记 + +- [[04-可观测性/Metrics监控/README]] — Metrics + Logging = 完整的问题定位能力 +- [[04-可观测性/链路追踪/README]] — trace_id 是日志与追踪的桥梁 +- [[04-可观测性/告警管理/README]] — 日志可以触发基于模式的告警 diff --git a/hzh/MS/04-可观测性/链路追踪/README.md b/hzh/MS/04-可观测性/链路追踪/README.md new file mode 100644 index 0000000..5492fec --- /dev/null +++ b/hzh/MS/04-可观测性/链路追踪/README.md @@ -0,0 +1,166 @@ +--- +tags: [microservice, tracing, opentelemetry, context-propagation] +create time: 2026-05-05 +--- + +# 链路追踪 + +## 概述 + +Tracing 回答的问题是:**请求在哪一步慢了 / 失败了?**——通过完整的调用链定位瓶颈和故障。 + +一次请求在多个服务中的完整调用路径: + +```mermaid +flowchart LR + RID["Request
trace_id: abc-123"] + + subgraph SPANS["调用链 (Trace)"] + direction TB + S1["Span #1
API Gateway
5ms"] + S2["Span #2
Order Service
70ms"] + S3["Span #3
Payment RPC
160ms"] + S4["Span #4
Inventory RPC
70ms"] + + S1 --> S2 --> S3 + S2 --> S4 + end + + RID --> S1 + style S3 fill:#ff9999 + + note["Span #3 耗时最长 → Payment 是瓶颈"] + S3 -.-> note +``` + +## Trace/Span 核心概念 + +| 概念 | 说明 | +|------|------| +| **Trace** | 一次请求的完整调用链,全局唯一 `trace_id` | +| **Span** | 调用链中的一个执行单元,有独立的 `span_id` | +| **Parent-Child** | Span 树状结构,子 Span 继承父 Span 的 trace_id | +| **Tags / Attributes** | 键值对标注(HTTP method、status code) | +| **Logs** | Span 级别的时间戳事件(如 "DB query started") | +| **Sampling** | 按比例采样,降低存储开销 | + +### OpenTelemetry W3C Trace Context 标准 + +```json +// HTTP Header +{ + "traceparent": "00-{trace_id}-{span_id}-01", + "tracestate": "vendor=value" +} +``` + +格式解析:`version(2字节) - trace_id(32字节) - span_id(16字节) - flags(2字节)` + +``` +示例: 00-4bf92f3577b34da6a3ce929d0e0e4736-00f067aa0ba902b7-01 + │ └─── trace_id ───┘ └─ span_id ─┘ └flags─┘ +``` + +## 上下文传播详解 + +`trace_id` 必须从上游传递到下游。不同通信场景有不同的传播方式: + +### HTTP 传播 + +```go +func Middleware(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // 1. 提取入站 trace context + ctx := propagator.Extract(r.Context(), headerReader{r.Header}) + + // 2. 创建新的 Span + ctx, span := tracer.Start(ctx, "handleRequest") + defer span.End() + + // 3. 将 trace context 注入出站请求 + r = r.WithContext(ctx) + propagator.Inject(ctx, headerWriter{r.Header}) + + next.ServeHTTP(w, r) + }) +} +``` + +### MQ 消息头传播 + +```go +// 生产者 - 手动注入 trace context +msg.Properties.Set("trace_id", extractTraceID(ctx)) +msg.Properties.Set("span_id", extractSpanID(ctx)) + +// 消费者 - 恢复 trace context +traceID := msg.Properties.Get("trace_id") +span, _ := tracer.Start(traceContext, "consumeMessage", + trace.WithAttributes(attribute.String("mq.message.id", traceID))) +``` + +## 采样策略 + +```mermaid +flowchart TD + AllReq["全部请求"] --> Sampler{"采样决策"} + + Sampler -->|"100%"| Core["核心链路全量采集"] + Sampler -->|"5~10%"| Normal["普通路径随机采样"] + Sampler -->|"100%"| Error["错误链路优先保留"] + Sampler -->|"0%"| Health["健康检查/内部心跳"] + + Core --> Storage["Trace 存储"] + Normal --> Storage + Error --> Storage + Health --> Drop["丢弃"] +``` + +| 环境 | 采样率 | 说明 | +|------|--------|------| +| 开发 / 测试 | 100% | 方便调试,无存储压力 | +| 生产 - 核心链路 | 100% | 下单、支付等关键路径 | +| 生产 - 普通路径 | 5~10% | 平衡成本和覆盖率 | +| 生产 - 错误链路 | 100% | 出错时优先保留 | + +> [!tip] 基于错误的智能采样 +> +> 高级做法:**正常路径低采样,一旦检测到 HTTP 5xx 或 DB 超时,立即提升当前请求的采样率**。这样既省了存储,又能在出问题时有足够的数据回溯。 + +## 主流方案对比 + +| 方案 | 存储后端 | 侵入程度 | 中文支持 | 推荐理由 | +|------|---------|---------|---------|---------| +| **OpenTelemetry** | 任意 Jaeger/Prometheus/Loki | SDK + Auto-instrumentation | 良好 | CNCF 行业标准,未来趋势 | +| **Jaeger** | Cassandra / Elasticsearch | Agent / SDK | 社区支持 | Uber 开源,UI 优秀 | +| **SkyWalking** | MySQL / ES | 零侵入 Agent | ⭐ 完善 | 国产首选,开箱即用 | + +## 渐进式落地路线 + +> [!tip] 不要试图一步到位——先从 Tracing 开始,ROI 最高。 +> +> 1. **第一步**:接入 Tracing(OpenTelemetry),覆盖核心链路(下单、支付),rate=100% +> 2. **第二步**:加 Metrics(Prometheus + Grafana),建立基础监控面板 +> 3. **第三步**:集中 Logging(Loki / ELK),日志携带 trace_id 与 Tracing 关联 +> 4. **第四步**:全量上 OpenTelemetry Collector,统一管理三件套 + +## 性能影响评估 + +| 指标 | 预期影响 | +|------|---------| +| CPU 增加 | < 3%(异步批量上报) | +| 内存增加 | ~10MB(Span buffer) | +| 网络带宽 | 每条 Trace 约 2~5KB(取决于 Span 数量) | +| 延迟增加 | < 0.1ms(SDK 内处理不阻塞业务) | + +> [!note] 关键实践 +> - 使用 **BatchProcessor** 异步上报,不阻塞业务线程 +> - Span 不要嵌套过深,一个 HTTP 调用或 DB 查询对应一个 Span +> - 给 Span 设置丰富的 Attributes,方便查询和过滤 +> - 错误 Span 标记 `isError=true`,便于快速定位问题链路 + +## 关联笔记 + +- [[04-可观测性/Metrics监控/README]] — Tracing 与 Metrics 互补 +- [[04-可观测性/日志系统/README]] — trace_id 串联日志和追踪 +- [[04-可观测性/告警管理/README]] — 基于 Trace 数据的异常检测告警 diff --git a/hzh/MS/05-部署运维.md b/hzh/MS/05-部署运维.md deleted file mode 100644 index 1ffdf4e..0000000 --- a/hzh/MS/05-部署运维.md +++ /dev/null @@ -1,397 +0,0 @@ ---- -tags: [microservice, kubernetes, cicd, container, sre, observability] -create time: 2026-04-29 12:05 ---- - -# 部署运维 - -## 概述 - -微服务架构下,服务数量从几个增长到几百个。**人工运维完全不可行**。本文涵盖容器化编排、K8s 资源管理、发布策略、弹性伸缩、CI/CD 流水线、日志告警和 SRE 核心概念。 - -## 容器化与编排 - -### Docker 容器 — 标准交付单元 - -```dockerfile -# Go 多阶段构建示例 -FROM golang:1.22 AS builder -WORKDIR /app -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -o server . - -FROM alpine:latest -COPY --from=builder /app/server /server -EXPOSE 8080 -CMD ["/server"] -``` - -> [!tip] 关键实践 -> - **多阶段构建**:镜像只包含运行时产物,体积极大缩小 -> - **非 root 用户运行**:安全最佳实践 -> - `.dockerignore`:排除不必要的文件(git、vendor、测试文件) - -### Kubernetes 核心概念 - -```mermaid -graph TB - subgraph CLUSTER["K8s Cluster"] - master["Master Node
API Server / Scheduler / ETCD"] - - subgraph NODES["Worker Nodes"] - N1[Node A] - N2[Node B] - end - - master --> N1 - master --> N2 - - subgraph PODS1["Deployment: order-service"] - P1[Pod 1] - P2[Pod 2] - P3[Pod 3] - end - - subgraph PODS2["Deployment: payment-service"] - Q1[Pod 1] - Q2[Pod 2] - end - - N1 --> P1 & Q1 - N2 --> P2 & P3 & Q2 - end - - Svc["Service: order-service
Load Balancer → Pods"] - Svc --> P1 & P2 & P3 -``` - -| K8s 对象 | 作用 | -|---------|------| -| Pod | 最小部署单元,一个或多个容器 | -| Deployment | 管理 Pod 的声明式更新和扩缩容 | -| Service | 稳定的网络入口,实现负载均衡 | -| Ingress | HTTP/HTTPS 路由规则(外部访问入口) | -| ConfigMap / Secret | 配置注入,与代码分离 | -| HPA (Horizontal Pod Autoscaler) | 根据 CPU/自定义指标自动扩缩 | - -### 资源管理与健康检查 - -K8s 调度 Pod 的核心依据是 **资源请求 (Requests)**,而决定 pod 生死的是 **探针 (Probes)**。这两个概念配合使用才能保障服务稳定运行。 - -```yaml -# Deployment 核心片段:资源配置 + 探针 -spec: - replicas: 3 - template: - spec: - containers: - - name: order-service - image: registry.example.com/order:v1.2.3 - resources: - requests: # 调度依据:K8s 保证至少有这些资源 - cpu: "250m" # 0.25 核 - memory: "256Mi" # 256 MB - limits: # 硬上限:超过则 OOMKill / CPU Throttle - cpu: "500m" - memory: "512Mi" - livenessProbe: # 活体检测:死了就重启 - httpGet: - path: /healthz - port: 8080 - initialDelaySeconds: 15 - periodSeconds: 10 - readinessProbe: # 就绪检测:未就绪就不进负载均衡 - httpGet: - path: /ready - port: 8080 - initialDelaySeconds: 5 - periodSeconds: 5 - startupProbe: # 启动检测:慢启动服务友好 - httpGet: - path: /healthz - port: 8080 - failureThreshold: 30 - periodSeconds: 10 -``` - -> [!tip] Probe 选择指南 -> -> | 探针类型 | 触发条件 | 后果 | 适用场景 | -> |---------|---------|------|---------| -> | **Liveness** | `/healthz` 返回非 2xx | K8s **重启容器** | 死锁、无法恢复的崩溃 | -> | **Readiness** | `/ready` 返回非 2xx | **摘除 Service 流量** | 依赖 DB 连接池未就绪、热加载进行中 | -> | **Startup** | 首次成功前一直失败 | **不重启,只等待** | 大模型初始化、JVM cold start 等慢启动场景 | - -> [!question] 思考 -> 如果一个服务的 `/healthz` 因为数据库连接超时而持续返回 503,K8s 会怎么做? - -**答案**:Liveness Probe 会认为容器挂了并反复重启它 — 这就是经典的 **CrashLoopBackOff**。正确做法是让 `/healthz` 做降级判断(DB 不可用时返回 200 + 标注降级),用 `/ready` 来摘除流量。Liveness 应该只对"进程已死"的情况敏感,对"性能下降"保持宽容。 - ---- - -## 发布策略 - -微服务需要独立发布,如何在不停服的情况下完成更新? - -### 蓝绿发布 - -```mermaid -stateDiagram-v2 - [*] --> Blue: 初始状态 - Blue --> DeployGreen: 部署新版本到 Green - DeployGreen --> TestGreen: 灰度验证 - TestGreen --> Switch: 切换流量 - Switch --> Green: 全部流量到新版 - Green --> CleanupGreen: 删除旧版 Blue -``` - -- 优点:**回滚极速**(切回 Blue 即可),验证充分 -- 缺点:资源翻倍,需要两套环境 - -### 金丝雀发布 (Canary) - -```mermaid -graph LR - A["95% traffic to stable"] - B["5% traffic to canary"] - C["Metrics normal?"] - D["Gradually expand to 20% → 50% → 100%"] - E["Metrics abnormal → Auto rollback"] - - A --> B - B --> C - C -- "yes" --> D - C -- "no" --> E -``` - -- 优点:资源效率高,风险渐进暴露 -- 缺点:流程复杂,需要完善的监控配合 - -### 两种策略选择 - -> [!question] 思考 -> 你的团队应该用蓝绿还是金丝雀? - -**答案线索**:看你们的**监控成熟度和发布频率**。低频次(每周/每月)、监控完善时用金丝雀;高频次(每天多次)或想简化流程时用蓝绿。 - -## 弹性伸缩 - -```mermaid -graph TD - Metrics["CPU / Memory / Custom QPS"] --> Trigger{Threshold met?} - Trigger -- "yes" --> ScaleUp["Scale up new Pod"] - Trigger -- "no" --> Keep["Keep current replicas"] - ScaleDown{"Load decreased?"} -- "yes" --> ScaleDownPod["Scale down Pod"] - ScaleDown -- "no" --> Keep -``` - -**注意事项**: -- **预热时间**:新 Pod 启动后不能立刻算入统计,否则可能反复扩缩 -- **优雅关闭**:HPA 缩容前,Pod 需要先摘除 Service 流量再退出 -- **预留容量**:不要将集群利用率拉到 100%,给突发流量留缓冲 - -## CI/CD 流水线 - -```mermaid -graph LR - CODE["代码提交"] - TEST["单元测试 + 集成测试"] - SCAN["安全扫描 + 代码质量"] - BUILD["构建 Docker 镜像"] - PUSH["推送镜像仓库"] - DEPLOY["CI→Staging → 审批 → Production"] - - CODE --> TEST --> SCAN --> BUILD --> PUSH --> DEPLOY -``` - -> [!summary] CI/CD 设计原则 -> -> - **一次构建,多处部署**:镜像不随环境重新编译,只改 K8s ConfigMap/环境变量 -> - **语义化版本号**:镜像 tag 用 `v1.2.3`,tag 即版本溯源 -> - **自动化测试覆盖率要求**:合并 PR 前必须通过,否则不允许发布 - -### Pipeline 实战:GitHub Actions - -```yaml -# .github/workflows/deploy.yml -name: Deploy order-service -on: - push: - branches: [main] - paths: - - "services/order/**" # 仅该目录变更才触发 - -jobs: - build-and-push: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Build & push image - run: | - docker build -t registry/order:${{ github.sha }} . - docker tag registry/order:${{ github.sha }} \ - registry/order:v$(git describe --tags --abbrev=0) - docker push registry/order:${{ github.sha }} - - deploy-staging: - needs: build-and-push - runs-on: ubuntu-latest - environment: staging - steps: - - name: Apply K8s manifests - run: | - kubectl set image deployment/order-service \ - order=registry/order:${{ github.sha }} - kubectl rollout status deployment/order-service --timeout=120s - - deploy-production: - needs: deploy-staging - runs-on: ubuntu-latest - environment: production - steps: - - name: Canary release (10% → 50% → 100%) - run: | - kubectl patch canary order-service --type merge \ - -p '{"spec":{"weight":10}}' - # ... 等待监控确认,逐步放大流量 -``` - -> [!tip] Pipeline 设计要点 -> -> - **路径过滤**:只对相关服务的代码变更触发构建,避免全量重建 -> - **Commit SHA 作为镜像 tag**:保证精确回滚 — `git revert` 之后用同一 SHA 拉取旧镜像 -> - **分阶段部署**:Staging → Production 的审批关卡是最后的防线,不要跳过 - -## 日志管理与告警 - -微服务单实例崩溃不可怕 — 可怕的是 **不知道哪一台、为什么挂了**。日志和告警是运维的眼睛。 - -### 日志架构选型 - -```mermaid -graph LR - App["业务应用"] -->|stdout/stderr| K8sPod[Pod 容器日志] - K8sPod --> Filebeat[采集器: Filebeat / FluentBit] - Filebeat --> ES["Elasticsearch / Loki"] - ES --> Grafana["Grafana 查询面板"] - - App -->|structured log| sidecar[Sidecar 辅助日志] - sidecar --> Filebeat -``` - -**业界两种主流方案对比:** - -| 维度 | ELK (Elasticsearch) | EFK/Loki | -|------|---------------------|----------| -| 存储成本 | 高(全文索引) | 低(索引 label + 对象存储原文) | -| 查询性能 | 毫秒级全文检索 | 按 label 过滤较快,复杂查询慢 | -| 运维复杂度 | 高(ES 集群维护) | 低(Loki 无索引) | -| 适用规模 | 百万条/天以上 | 万 ~ 百万条/天 | - -> [!important] 结构化日志 -> -> 无论选择哪家方案,**日志格式必须是结构化的**(JSON),否则后期处理全是手工活: -> ```json -> { -> "timestamp": "2026-04-29T10:30:00Z", -> "level": "error", -> "service": "order-service", -> "trace_id": "abc-123-def", -> "msg": "payment gateway timeout", -> "duration_ms": 5023 -> } -> ``` - -### 告警策略设计 - -> [!question] 思考 -> 如果一个 Pod CPU 使用率从 20% 飙升到 90%,你应该立刻打电话叫值班工程师吗? - -不一定。**好的告警应该是" actionable"的** — 能让人立刻采取行动,而不是单纯告诉你"出事了"。 - -```mermaid -mindmap - root((告警设计原则)) - 区分等级 - P0: 立即响应 (页面电话) - P1: 当天处理 (IM 消息) - P2: 本周修复 (工单) - 抑制噪音 - 相关告警聚合: 一个根因一条告警 - 静默期: 重启后的自动恢复不打扰 - 附带上下文 - 告警信息包含: 什么服务 · 哪个指标 · 当前值 vs 阈值 -``` - ---- - -## SRE 核心概念 - -站点可靠性工程 (SRE) 把运维问题看作**软件工程问题**。它的核心理念是用 SLI/SLO/SLA 来量化服务质量,避免"我觉得系统很慢"这类模糊描述。 - -### SLI / SLO / SLA - -| 术语 | 全称 | 定义 | 谁定义 | -|------|------|------|--------| -| **SLI** | Service Level Indicator | **实际度量**:用户请求的成功率是多少? | 观测系统自动产出 | -| **SLO** | Service Level Objective | **内部目标**:我们承诺达到 99.9% 可用性 | SRE + 研发制定 | -| **SLA** | Service Level Agreement | **对外合同**:达不到就赔钱 | 法务 + 商务制定 | - -> [!tip] 实用比例 -> -> - **99.9% (三个九)** = 每年约 8.76 小时停机 — 适合大多数后端服务 -> - **99.95%** = 每年约 4.38 小时 — 核心交易链路 -> - **99.99% (四个九)** = 每年约 52 分钟 — 金融级系统 -> - **99%** 意味着每月近 7 小时不可用 — **几乎等于没有可用性目标** - -### 错误预算 (Error Budget) - -这是 SRE 最核心的机制 — **允许犯错,但设上限**: - -``` -错误预算 = 1 - SLO - -SLO = 99.9% → 错误预算 = 0.1% → 每月允许 21 分钟停机 -SLO = 99.99% → 错误预算 = 0.01% → 每月仅 4 分钟停机 -``` - -**基于错误预算的决策逻辑:** - -```mermaid -flowchart TD - Budget{"错误预算剩余 > 50%?"} - - Budget -- "是" --> Aggressive["可激进发布:
金丝雀 + 自动扩缩"] - Budget -- "< 50%" --> Conservative["保守发布:
蓝绿 + 全量人工审查"] - Budget -- "< 10%" --> Freeze["冻结发布:
优先修复稳定性
不允许新功能上线"] - - Aggressive --> Monitor["持续监控指标"] - Conservative --> Monitor -``` - -> [!summary] SRE 心法 -> -> 1. **用户视角定义 SLO**:不是"API P99 < 200ms",而是"用户在 3G 网络下打开页面 < 2s" -> 2. **错误预算用完 = 停止功能开发**:全力修 bug 加稳定性 -> 3. **Blameless Postmortem**:事故复盘不问"谁干的",问"流程哪里可以改进" - ---- - -## 运维 Checklist - -每次上线前快速过一遍这个清单,避免低级事故: - -| # | 检查项 | 说明 | -|---|--------|------| -| 1 | **探针已配置** | liveness/readiness/startup probe 都已设定 | -| 2 | **资源 limits 已设** | 防止单个 Pod 拖垮整台机器 | -| 3 | **日志为 JSON 格式** | 可被采集器解析 | -| 4 | **追踪 ID 透传** | trace_id 在跨服务调用链中不丢失 | -| 5 | **回滚预案明确** | 知道怎么回到上一版本,且演练过 | -| 6 | **告警已配置** | 关键指标异常时有人收到通知 | -| 7 | **数据迁移有回退脚本** | DB schema 变更要兼容新老版本共存 | - -## 关联笔记 - -- [[02-服务治理]] — K8s Service 提供原生的服务发现和负载均衡 -- [[04-可观测性]] — K8s Liveness/Readiness Probe 是可观测性的基础 diff --git a/hzh/MS/05-部署运维/CI-CD与GitOps/README.md b/hzh/MS/05-部署运维/CI-CD与GitOps/README.md new file mode 100644 index 0000000..9f10bd1 --- /dev/null +++ b/hzh/MS/05-部署运维/CI-CD与GitOps/README.md @@ -0,0 +1,176 @@ +--- +tags: [microservice, cicd, gitops, github-actions, argocd] +create time: 2026-05-05 +--- + +# CI/CD 与 GitOps + +## 概述 + +微服务需要**独立部署**。上百个服务的手工发布是不可想象的——必须用自动化流水线保证每次变更都能安全、快速地推送到生产环境。 + +```mermaid +flowchart LR + CODE["代码提交"] --> TEST["测试 & 静态分析"] + TEST --> SCAN["安全扫描"] + SCAN --> BUILD["构建镜像"] + BUILD --> PUSH["推送仓库"] + PUSH --> STAGING["Staging 验证"] + STAGING -->|"人工审批"| PROD["Production 部署"] + + style CODE fill:#e3f2fd + style TEST fill:#fff3e0 + style SCAN fill:#fce4ec + style BUILD fill:#e8f5e9 + style PUSH fill:#f3e5f5 + style STAGING fill:#e0f7fa + style PROD fill:#c8e6c9 +``` + +## CI/CD 设计原则 + +| 原则 | 说明 | +|------|------| +| **一次构建,多处部署** | 镜像不随环境重新编译,只改 K8s ConfigMap/环境变量 | +| **语义化版本** | 镜像 tag 用 `v1.2.3`,tag 即版本溯源 | +| **路径过滤** | 只对相关服务的代码变更触发构建 | +| **Commit SHA 作为镜像 tag** | 保证精确回滚 | +| **分阶段部署** | Staging → Production 的审批关卡不可跳过 | + +## GitHub Actions Pipeline 实战 + +```yaml +# .github/workflows/deploy.yml +name: Deploy order-service +on: + push: + branches: [main] + paths: + - "services/order/**" + +env: + REGISTRY: registry.example.com + IMAGE: order-service + +jobs: + # ========== Stage 1: Build & Test ========== + build-and-test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Run tests + run: make test + + - name: Security scan + uses: aquasecurity/trivy-action@master + with: + scan-type: 'fs' + severity: 'CRITICAL,HIGH' + + - name: Build Docker image + run: | + docker build -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \ + -t ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }} \ + -f services/order/Dockerfile \ + services/order + + - name: Push to registry + run: | + echo "${{ secrets.REGISTRY_PASSWORD }}" | docker login -u ${{ secrets.REGISTRY_USER }} --password-stdin + docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} + docker push ${{ env.REGISTRY }}/${{ env.IMAGE }}:v${{ github.run_number }} + + # ========== Stage 2: Deploy to Staging ========== + deploy-staging: + needs: build-and-test + runs-on: ubuntu-latest + environment: staging + steps: + - name: Deploy to staging + run: | + kubectl set image deployment/order-service \ + order=${{ env.REGISTRY }}/${{ env.IMAGE }}:${{ github.sha }} \ + --namespace=staging + kubectl rollout status deployment/order-service \ + --namespace=staging --timeout=120s + + # ========== Stage 3: Deploy to Production ========== + deploy-production: + needs: deploy-staging + runs-on: ubuntu-latest + environment: production + steps: + - name: Canary release (10% → 50% → 100%) + run: | + kubectl patch canary order-service --type merge \ + -p '{"spec":{"weight":10}}' + + # ... 等待监控确认,逐步放大流量 + echo "Monitor metrics before proceeding..." +``` + +## GitOps 工作流 + +GitOps 的核心思想:**K8s 集群的状态 = Git 仓库中声明式配置的当前状态。** + +```mermaid +flowchart LR + Dev["开发者 PR"] -->|"修改 K8s manifest"| Git[(Git Repo)] + + subgraph Cluster["K8s Cluster"] + Argo["ArgoCD / Flux"] -->|同步| K8sState["Pod/Service/ConfigMap"] + end + + Git -.->|Webhook| Argo + + Argo -->|"检测到差异"| Diff{"状态一致?"} + Diff -- 否 --> Sync["自动同步到 K8s ✅"] + Diff -- 是 --> OK["已一致 ⏸️"] + + style Git fill:#e3f2fd + style Argo fill:#fff3e0 + style K8sState fill:#e8f5e9 +``` + +### 与传统 CI/CD 的区别 + +| 维度 | 传统 CI/CD | GitOps | +|------|-----------|--------| +| **部署驱动** | CI 服务器主动推送 | Git 仓库变动触发拉取 | +| **状态源** | CI pipeline 的历史记录 | Git commit history | +| **回滚方式** | 回到上一次的 pipeline | `git revert` + 自动同步 | +| **漂移检测** | 通常无 | 持续比对,自动修复不一致 | +| **代表工具** | Jenkins / GitLab CI / GitHub Actions | ArgoCD / Flux | + +### GitOps 的优势 + +1. **审计完整** — 所有变更都在 Git 中可追溯 +2. **回滚简单** — `git revert` 就是回滚操作 +3. **自修复** — ArgoCD 持续监测并修复集群状态偏离 +4. **多人协作** — 通过 PR Review 流程管控配置变更 + +## Helm — K8s 的包管理 + +当每个服务都有几十行 YAML 时,Helm 能大幅简化部署: + +```bash +# 创建 Chart 模板 +helm create order-service + +# 使用 values.yaml 参数化部署 +helm upgrade --install order-service ./charts/order-service \ + --set image.tag=v1.2.3 \ + --set replicas=3 \ + --namespace=production +``` + +> [!tip] 为什么需要 Helm? +> +> 没有 Helm 时,每个服务都要手动维护 Deployment、Service、Ingress、ConfigMap 等几十个 YAML 文件。Helm 允许你把通用模板抽出来,只在 values.yaml 里改差异化配置。 + +## 关联笔记 + +- [[05-部署运维/容器化/README]] — Docker 镜像构建是 CI/CD 的第一步 +- [[05-部署运维/Kubernetes/README]] — K8s 是部署的目标平台 +- [[05-部署运维/SRE实践/README]] — 错误预算影响发布策略 diff --git a/hzh/MS/05-部署运维/Kubernetes/README.md b/hzh/MS/05-部署运维/Kubernetes/README.md new file mode 100644 index 0000000..e45b5a0 --- /dev/null +++ b/hzh/MS/05-部署运维/Kubernetes/README.md @@ -0,0 +1,266 @@ +--- +tags: [microservice, kubernetes, k8s, container-orchestration] +create time: 2026-05-05 +--- + +# Kubernetes + +## 概述 + +Kubernetes (K8s) 是微服务架构的事实标准编排引擎。它将容器化的服务组织成声明式的资源对象,自动处理部署、扩展、故障恢复。 + +```mermaid +graph TB + subgraph CLUSTER["K8s Cluster"] + Master["控制面
API Server / Scheduler / Controller Manager / etcd"] + + subgraph NODES["工作节点"] + N1["Node A
kubelet + kube-proxy"] + N2["Node B
kubelet + kube-proxy"] + end + + Master --> N1 + Master --> N2 + + subgraph APPS["应用层"] + Order["Order Service Deployment"] + Pay["Payment Service Deployment"] + end + + N1 --> Order + N2 --> Order + N1 --> Pay + N2 --> Pay + end + + SVC["Service (ClusterIP)"] -->|Load Balance| Order + Ingress["Ingress (HTTP Routing)"] --> SVC + + style Master fill:#e3f2fd + style N1 fill:#fff3e0 + style N2 fill:#fff3e0 +``` + +## 核心概念速查 + +| K8s 对象 | 用途 | 类比 | +|---------|------|------| +| **Pod** | 最小部署单元,包含一个或多个容器 | 应用实例 | +| **Deployment** | 管理 Pod 的副本数和滚动更新 | 应用的"模板" | +| **Service** | 稳定的网络入口,负载均衡 | 内部 VIP | +| **Ingress** | HTTP/HTTPS 路由规则 | 外部网关 | +| **ConfigMap** | 配置注入(明文) | 环境变量/配置文件 | +| **Secret** | 敏感配置注入(base64) | 密码/API Key | +| **HPA** | 根据指标自动扩缩容 | 弹性伸缩 | +| **StatefulSet** | 有状态应用的有序管理 | DB、ZK | +| **Job/CronJob** | 一次性任务 / 定时任务 | 批处理 | + +## Deployment 详解 + +### 完整示例 + +```yaml +apiVersion: apps/v1 +kind: Deployment +metadata: + name: order-service + labels: + app: order + version: v1.2.3 +spec: + replicas: 3 # 期望副本数 + strategy: + type: RollingUpdate + rollingUpdate: + maxSurge: 1 # 最多超额 1 个 Pod + maxUnavailable: 0 # 滚动更新期间不允许不可用 + + selector: + matchLabels: + app: order + + template: + metadata: + labels: + app: order + version: v1.2.3 + spec: + containers: + - name: order-service + image: registry.example.com/order:v1.2.3 + ports: + - containerPort: 8080 + + # ========== 资源配置 ========== + resources: + requests: # 调度依据:保证至少有这些 + cpu: "250m" + memory: "256Mi" + limits: # 硬上限:超过则 OOMKill/CPU Throttle + cpu: "500m" + memory: "512Mi" + + # ========== 探针 ========== + livenessProbe: + httpGet: + path: /healthz + port: 8080 + initialDelaySeconds: 15 + periodSeconds: 10 + failureThreshold: 3 # 连续失败 3 次才重启 + + readinessProbe: + httpGet: + path: /ready + port: 8080 + initialDelaySeconds: 5 + periodSeconds: 5 + failureThreshold: 3 + + startupProbe: + httpGet: + path: /healthz + port: 8080 + failureThreshold: 30 # 最长等待 300s (慢启动友好) + + # ========== 环境变量 & 挂载 ========== + envFrom: + - configMapRef: + name: order-service-config + - secretRef: + name: order-service-secrets + + lifecycle: + preStop: + exec: + command: ["sh", "-c", "sleep 15"] # 优雅退出,给 LB 摘流时间 +``` + +### Probe 选择指南 + +| 探针类型 | 触发条件 | 动作 | 适用场景 | +|---------|---------|------|---------| +| **Liveness** | `/healthz` 返回非 2xx | 重启容器 | 死锁、无法恢复的崩溃 | +| **Readiness** | `/ready` 返回非 2xx | 摘除 Service 流量 | 依赖未就绪、热加载中 | +| **Startup** | 首次成功前持续失败 | 不重启,只等待 | 大模型/JVM 冷启动 | + +> [!warning] 经典陷阱:CrashLoopBackOff +> +> 如果 Liveness Probe 因为 DB 连接超时而返回 503,K8s 会认为容器挂了并反复重启它——这就是 CrashLoopBackOff。正确做法是:让 `/healthz` 做降级判断(DB 不可用时返回 200),用 `/ready` 来摘除流量。 + +## Service 与 Ingress + +### Service 类型 + +| 类型 | 特点 | 使用场景 | +|------|------|---------| +| **ClusterIP** | 集群内 IP,外部不可访问 | 默认,内部服务间调用 | +| **NodePort** | 在每个 Node 上开端口 | 调试、临时访问 | +| **LoadBalancer** | 云厂商分配公网 IP | 对外暴露的服务 | +| **ExternalName** | CNAME 到外部域名 | 对接外部系统 | + +```yaml +# ClusterIP Service — 服务发现的载体 +apiVersion: v1 +kind: Service +metadata: + name: order-service +spec: + selector: + app: order + ports: + - port: 80 + targetPort: 8080 + protocol: TCP + type: ClusterIP +``` + +调用方只需 `http://order-service:80`,K8s 通过 iptables/IPVS 自动实现负载均衡。 + +### Ingress — HTTP 路由 + +```yaml +apiVersion: networking.k8s.io/v1 +kind: Ingress +metadata: + name: main-ingress + annotations: + nginx.ingress.kubernetes.io/rewrite-target: / +spec: + rules: + - host: api.example.com + http: + paths: + - path: /orders + pathType: Prefix + backend: + service: + name: order-service + port: + number: 80 + - path: /users + pathType: Prefix + backend: + service: + name: user-service + port: + number: 80 +``` + +## HPA 弹性伸缩 + +```yaml +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: order-service-hpa +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: order-service + minReplicas: 3 + maxReplicas: 20 + metrics: + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: 70 # CPU > 70% 时扩容 + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: 80 + behavior: + scaleUp: + stabilizationWindowSeconds: 60 # 扩容稳定期 + policies: + - type: Pods + value: 2 + periodSeconds: 60 # 每分钟最多扩 2 个 + scaleDown: + stabilizationWindowSeconds: 300 # 缩容稳定期 5min(防抖动) +``` + +## K8s 运维 Checklist + +每次上线前过一遍这个清单: + +| # | 检查项 | 说明 | +|---|--------|------| +| 1 | **Probe 已配置** | liveness/readiness/startup 都设定了阈值 | +| 2 | **Resources Limits** | 防止单个 Pod OOMKill 拖垮整台机器 | +| 3 | **日志输出到 stdout/stderr** | 可被采集器解析为 JSON | +| 4 | **trace_id 透传** | 跨服务调用链 trace_id 不丢失 | +| 5 | **回滚预案** | `kubectl rollout undo deployment/order-service` 能用 | +| 6 | **告警已配置** | 关键指标异常时有人收到通知 | +| 7 | **镜像 Tag** | 不用 latest,用语义化版本或 commit SHA | + +## 关联笔记 + +- [[02-服务治理/服务发现/README]] — K8s Service 是服务端发现模式的代表 +- [[02-服务治理/流量治理/README]] — Istio VirtualService 在 K8s 上的高级路由 +- [[05-部署运维/SRE实践/README]] — SLO/Error Budget 在 K8s 中的落地 diff --git a/hzh/MS/05-部署运维/README.md b/hzh/MS/05-部署运维/README.md new file mode 100644 index 0000000..873aef6 --- /dev/null +++ b/hzh/MS/05-部署运维/README.md @@ -0,0 +1,42 @@ +--- +tags: [microservice, deployment, ops] +create time: 2026-04-29 12:05 +--- + +# 部署运维 + +## 概述 + +微服务架构下,服务数量从几个增长到几百个。**人工运维完全不可行**。本文涵盖容器化编排、K8s 资源管理、发布策略、弹性伸缩、CI/CD 流水线和 SRE 核心概念。 + +## 知识体系 + +```mermaid +graph LR + A["容器化"] --> B["Kubernetes"] + B --> C["CI/CD 与 GitOps"] + C --> D["SRE 实践"] + + style A fill:#e3f2fd + style B fill:#fff3e0 + style C fill:#fce4ec + style D fill:#e8f5e9 +``` + +| # | 主题 | 核心问题 | +|---|------|----------| +| 1 | [[05-部署运维/容器化/README]] | Docker 镜像怎么优化?最佳实践有哪些? | +| 2 | [[05-部署运维/Kubernetes/README]] | K8s 核心概念和资源管理怎么做? | +| 3 | [[05-部署运维/CI-CD与GitOps/README]] | 自动化流水线怎么设计?GitOps 流程怎么走? | +| 4 | [[05-部署运维/SRE实践/README]] | SLI/SLO/Error Budget 如何落地? | + +### 学习建议 + +> [!tip] 学习路径 +> 先掌握容器化(Docker),再学 K8s 核心概念,CI/CD 和 SRE 可以在实践中逐步深入。 + +### 关联笔记 + +- [[02-服务治理]] — K8s Service 提供原生的服务发现和负载均衡 +- [[04-可观测性]] — K8s Liveness/Readiness Probe 是可观测性的基础 +- [[hzh/MS/README.md]] — 完整微服务知识索引 diff --git a/hzh/MS/05-部署运维/SRE实践/README.md b/hzh/MS/05-部署运维/SRE实践/README.md new file mode 100644 index 0000000..fdf64c8 --- /dev/null +++ b/hzh/MS/05-部署运维/SRE实践/README.md @@ -0,0 +1,160 @@ +--- +tags: [microservice, sre, slo, error-budget, postmortem] +create time: 2026-05-05 +--- + +# SRE 实践 + +## 概述 + +站点可靠性工程 (SRE) 把运维问题看作**软件工程问题**。它的核心理念是用 SLI/SLO/SLA 来量化服务质量,避免"我觉得系统很慢"这类模糊描述。 + +```mermaid +flowchart LR + User["用户体验"] --> SLI{"实际测量"} + SLI -->|"达标"| SLO_OK["✅ SLO 达成"] + SLI -->|"不达标"| Budget["消耗错误预算"] + + Budget --> Left{"预算剩余?"} + Left -- "> 50%" --> Ship["快速迭代 🚀"] + Left -- "< 10%" --> Stabilize["稳定优先 ❄️"] + + style User fill:#e3f2fd + style SLO_OK fill:#e8f5e9 + style Ship fill:#fff3e0 + style Stabilize fill:#ffebee +``` + +## SLI / SLO / SLA 详解 + +### 三层模型 + +| 术语 | 全称 | 定义 | 谁制定 | 变更频率 | +|------|------|------|--------|---------| +| **SLI** | Service Level Indicator | 实际度量:用户请求的成功率是多少? | 观测系统自动产出 | 持续更新 | +| **SLO** | Service Level Objective | 内部目标:我们承诺达到 99.9% 可用性 | SRE + 研发 | 季度回顾 | +| **SLA** | Service Level Agreement | 对外契约:达不到就赔钱 | 法务 + 商务 | 按合同约定 | + +### 实用性比例对照表 + +| SLO | 每年停机时间 | 每月停机时间 | 适用级别 | +|-----|-------------|-------------|---------| +| **99%** | ~87.6 小时 | ~7.2 小时 | 内部工具(几乎无意义) | +| **99.9% ("三个九")** | ~8.76 小时 | ~43 分钟 | 大多数后端服务 ✅ | +| **99.95%** | ~4.38 小时 | ~21 分钟 | 核心交易链路 | +| **99.99% ("四个九")** | ~52.6 分钟 | ~4.3 分钟 | 金融级 / 支付系统 | +| **99.999%** | ~5.26 分钟 | ~26 秒 | 电信级,极难实现 | + +> [!warning] "三个九"是底线 +> +> 如果团队宣称 SLO = 99%,这意味着每月可以容忍近 **7 小时的不可用**——这在生产环境中基本等于没有可用性目标。 + +## Error Budget (错误预算) 深度解析 + +### 计算方式 + +``` +可用率 = (总时间 - 故障时间) / 总时间 × 100% +错误预算 = 1 - SLO + +SLO = 99.9% → 错误预算 = 0.001 + 每月可容忍故障 = 30 × 24 × 60 × 0.001 = 43.2 分钟 + 每周可容忍故障 = 7 × 24 × 60 × 0.001 = 10.1 分钟 + +SLO = 99.99% → 错误预算 = 0.0001 + 每月可容忍故障 ≈ 4.3 分钟 +``` + +### 基于错误预算的决策矩阵 + +```mermaid +flowchart TD + Ratio["错误预算使用率 = 已消耗 / 总预算"] + + Ratio -- "< 50%" --> Green["🟢 绿灯阶段
预算充足: 可以大胆发布
新功能、尝试激进方案
常规发布节奏"] + Ratio -- "50~90%" --> Yellow["🟡 黄灯阶段
预算紧张: 收紧发布频率
增加人工审查
暂停非关键功能开发"] + Ratio -- "> 90%" --> Orange["🟠 橙灯阶段
严重不足: 冻结发布
专注稳定性修复
全员 On-Call"] + Ratio -- "> 100%" --> Red["🔴 红灯阶段
预算耗尽: 禁止所有
功能性变更
SLO 事故复盘"] + + style Green fill:#c8e6c9 + style Yellow fill:#fff9c4 + style Orange fill:#ffe0b2 + style Red fill:#ffcdd2 +``` + +### 错误预算告警联动 + +```yaml +# Prometheus 告警规则示例 +groups: + - name: error-budget + rules: + - alert: ErrorBudgetBurnRateHigh + expr: | + sum(rate(http_requests_total{status=~"5.."}[1h])) / + sum(rate(http_requests_total[1h])) > 0.001 + for: 1h + labels: + severity: warning + budget_phase: yellow + annotations: + summary: "错误预算消耗加速" + message: "当前错误率 {{ $value }}% > SLO 阈值 0.1%" + + - alert: ErrorBudgetExhausted + expr: | + (sum(rate(http_requests_total{status=~"5.."}[24h])) / + sum(rate(http_requests_total[24h]))) >= 0.001 + labels: + severity: critical + budget_phase: red + annotations: + summary: "错误预算已耗尽!" + message: "本月 SLO 已破线,冻结非紧急变更" +``` + +## SRE 心法 + +1. **用户视角定义 SLO** — 不是"API P99 < 200ms",而是"用户在正常网络下加载页面 < 2s" +2. **错误预算用完 = 停止功能开发** — 全力修 bug、加稳定性 +3. **Blameless Postmortem** — 不问"谁干的",问"流程哪里可以改进" +4. **自动化一切重复劳动** — 手动操作一定会出错 +5. **接受一定程度的失败** — 在可控范围内快速迭代,比追求完美更重要 + +## Blameless Postmortem 模板 + +| 字段 | 填写内容 | +|------|---------| +| **事件名称** | `2026-05-05 支付服务超时事故` | +| **影响范围** | 约 15% 的支付请求失败,持续 23 分钟 | +| **发现时间** | 14:32 (On-Call 接到告警) | +| **恢复时间** | 14:55 (回滚后确认恢复) | +| **根本原因** | 某次变更后支付网关的连接池大小从 50 降到 10 | +| **时间线** | 14:00 发布 v1.2.3 → 14:30 错误率开始升高 → 14:32 收到告警 → 14:35 开始排查 → 14:45 定位根因 → 14:50 回滚 → 14:55 完全恢复 | +| **改进行动** | ① 连接池参数变动必须经过压测验证;② 监控中补充连接池活跃数指标 | +| **跟进人** | @zhangsan (行动 ①)、@lisi (行动 ②) | + +## SRE Metrics 看板 + +除了 SLO,SRE 还需要关注以下运营指标: + +| 指标 | 说明 | 目标 | +|------|------|------| +| **MTTR** | Mean Time To Recovery | 核心服务 < 30min | +| **Change Failure Rate** | 变更导致故障的比例 | < 5% | +| **Lead Time for Changes** | 代码提交到上线的时间 | < 2h | +| **Deployment Frequency** | 日均部署次数 | > 5 (大规模团队) | + +> [!tip] DORA Metrics +> +> Google 提出的四大 DevOps 指标,广泛用于评估工程效能: +> - **部署频率** — 交付速度 +> - **变更前置时间** — 从代码提交到生产部署需要多久 +> - **变更失败率** — 多少部署导致了故障或回滚 +> - **MTTR** — 恢复服务的平均时间 + +## 关联笔记 + +- [[04-可观测性/告警管理/README]] — SLO/Error Budget 与告警体系的联动 +- [[05-部署运维/Kubernetes/README]] — K8s HPA 弹性伸缩支撑 SLO 保障 +- [[05-部署运维/CI-CD与GitOps/README]] — GitOps 支持安全的持续交付 diff --git a/hzh/MS/05-部署运维/容器化/README.md b/hzh/MS/05-部署运维/容器化/README.md new file mode 100644 index 0000000..f94053c --- /dev/null +++ b/hzh/MS/05-部署运维/容器化/README.md @@ -0,0 +1,155 @@ +--- +tags: [microservice, docker, container, image-optimization] +create time: 2026-05-05 +--- + +# 容器化 + +## 概述 + +Docker 容器是微服务交付的标准单元。它解决了"在我机器上是好的"这个经典问题——**开发、测试、生产使用完全一致的运行时环境**。 + +```mermaid +graph TB + CODE["源代码"] --> BUILD["CI 构建镜像"] + BUILD --> REGISTRY["镜像仓库
Harbor / ECR / ACR"] + REGISTRY --> K8s["Kubernetes 部署"] + + style BUILD fill:#e3f2fd + style REGISTRY fill:#fff3e0 + style K8s fill:#e8f5e9 +``` + +## Dockerfile 最佳实践 + +### Go 多阶段构建(推荐) + +```dockerfile +# ========== 阶段 1: 构建 ========== +FROM golang:1.22-alpine AS builder + +RUN apk --no-cache add git ca-certificates + +WORKDIR /app +COPY go.mod go.sum ./ +RUN go mod download + +COPY . . +ARG LDFLAGS="-s -w -extldflags '-static'" +RUN CGO_ENABLED=0 GOOS=linux go build -ldflags "$LDFLAGS" -o server . + +# ========== 阶段 2: 运行时 ========== +FROM alpine:latest + +RUN apk --no-cache add ca-certificates tzdata && \ + cp /usr/share/zoneinfo/Asia/Shanghai /etc/localtime && \ + echo "Asia/Shanghai" > /etc/timezone + +RUN addgroup -S appgroup && adduser -S appuser -G appgroup +USER appuser + +WORKDIR /app +COPY --from=builder /app/server . + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \ + CMD wget -qO- http://localhost:8080/healthz || exit 1 + +CMD ["./server"] +``` + +### 关键优化点 + +| 优化项 | 方法 | 效果 | +|--------|------|------| +| **多阶段构建** | 编译和运行分离 | 镜像从 800MB → 15MB | +| **alpine 基础镜像** | 替代 debian/ubuntu | 减小体积 | +| **非 root 运行** | `USER appuser` | 安全合规 | +| **静态链接** | `CGO_ENABLED=0` | 不依赖系统库 | +| **layer cache** | `go.mod` 先 COPY | CI 加速构建 | +| **健康检查** | HEALTHCHECK 指令 | K8s 原生支持 | + +### `.dockerignore` + +``` +.git +.gitignore +*.md +vendor/ +tests/ +*.log +.DS_Store +.idea/ +.vscode/ +``` + +> [!tip] 为什么 .dockerignore 很重要? +> +> 如果不排除 `.git` 目录,整个版本历史都会被打包进镜像(增加数百 MB)。如果排除不当,可能遗漏必要的配置文件。 + +## 镜像安全 + +```mermaid +flowchart LR + Scan["镜像扫描 (Trivy/Snyk)"] --> Clean{"漏洞等级?"} + + Clean -- "CRITICAL/HIGH" --> Block["❌ 阻止推送"] + Clean -- "MEDIUM" --> Review["⚠️ 人工审核"] + Clean -- "LOW/INFO" --> Allow["✅ 允许推送"] + + style Block fill:#ffebee + style Review fill:#fff3e0 + style Allow fill:#e8f5e9 +``` + +### 安全检查清单 + +- [ ] 不使用 `latest` tag(永远用具体版本号) +- [ ] 不安装不必要的软件包(`apk del --purge .build-deps`) +- [ ] 定期更新基础镜像(修复 CVE) +- [ ] 使用非 root 用户运行 +- [ ] 镜像不包含密钥、密码、私钥 +- [ ] 使用最小基础镜像(scratch / distroless) + +### Distroless 镜像 + +Google 推出的**无 shell、无包管理器**的极简运行时镜像: + +```dockerfile +# 最极致的精简 +FROM gcr.io/distroless/static-debian12 +COPY --from=builder /app/server . +USER nonroot +CMD ["./server"] +``` + +> ⚠️ 注意:distroless 镜像没有 shell (`/bin/sh`),调试时需要额外工具(如 debug 镜像或 `kubectl exec` 到 sidecar)。 + +## 镜像仓库管理 + +```mermaid +flowchart LR + Dev["开发者本地"] -->|"docker push"| DEV_REPO["dev 仓库
v1.2.3-dev"] + + Staging["Staging 验证"] -->|"通过"`PROD_REPO["prod 仓库
v1.2.3"] + + K8s["K8s Cluster"] -->|"pull"| PROD_REPO + + PR["PR Merge"] --> Tag["打标签 v1.2.3"] + Tag --> ProdRepoMove["移动到 prod 仓库"] + + style DEV_REPO fill:#fff3e0 + style PROD_REPO fill:#e8f5e9 +``` + +| 仓库方案 | 特点 | 适用场景 | +|---------|------|---------| +| **Harbor** | 自托管,RBAC + 扫描 | 国内企业首选 | +| **ECR / ACR / GCR** | 云厂商托管 | 全云环境 | +| **Docker Hub** | 公共免费 | 开源项目 | + +## 关联笔记 + +- [[05-部署运维/Kubernetes/README]] — K8s 以 Pod 为部署单元,镜像来自 Docker +- [[05-部署运维/CI-CD与GitOps/README]] — CI/CD 流水线中的镜像构建环节 diff --git a/hzh/MS/README.md b/hzh/MS/README.md index 156f4b2..c4c3155 100644 --- a/hzh/MS/README.md +++ b/hzh/MS/README.md @@ -7,7 +7,7 @@ create time: 2026-04-29 12:00 ## 概述 -本目录系统整理微服务架构的核心知识点。围绕 **5 个核心主题**,由浅入深地覆盖从概念理解到工程实践的关键路径。 +本目录系统整理微服务架构的核心知识点。围绕 **5 个核心主题**,由浅入深地覆盖从概念理解到工程实践的关键路径。每个主题下进一步拆分为多个专题文档。 ## 知识体系 @@ -18,17 +18,63 @@ graph LR B --> D["04 可观测性"] C --> E["05 部署运维"] D --> E + + style A fill:#e3f2fd + style B fill:#fff3e0 + style C fill:#fce4ec + style D fill:#e8f5e9 + style E fill:#f3e5f5 ``` -| # | 主题 | 核心问题 | -|---|------|----------| -| 1 | [[01-基础概念]] | 什么是微服务?单体 vs 微服务的权衡是什么? | -| 2 | [[02-服务治理]] | 服务如何发现彼此?如何通信?失败怎么处理? | -| 3 | [[03-数据一致性]] | 每个服务拥有独立数据库,跨服务数据一致性怎么保证? | -| 4 | [[04-可观测性]] | 请求穿越多个服务后,如何追踪、监控和排查问题? | -| 5 | [[05-部署运维]] | 上百个服务如何容器化编排、灰度发布、弹性伸缩? | +## 核心主题 -### 学习建议 +### 1. [[01-基础概念]] — 什么是微服务? + +微服务的核心定义、DDD 限界上下文拆分原则、单体 vs 微服务的权衡。 + +> [!tip] 理论基础篇 +> 这是整个系列的起点,建议在动手之前先通读。 + +### 2. [[02-服务治理]] — 服务如何被发现和治理? + +| 子主题 | 核心内容 | +|--------|---------| +| [[02-服务治理/服务发现/README]] | Nacos / Consul / K8s Service,客户端 vs 服务端发现模式 | +| [[02-服务治理/API网关/README]] | 统一入口的职责边界、路由策略、插件体系 | +| [[02-服务治理/服务间通信/README]] | gRPC vs REST,同步 vs 异步组合拳 | +| [[02-服务治理/容错模式/README]] | 超时 / 重试 / 熔断 / 限流 / 舱壁隔离 / 降级 | +| [[02-服务治理/配置管理/README]] | Nacos Config / Apollo,动态刷新与版本管理 | +| [[02-服务治理/分布式追踪/README]] | OpenTelemetry,trace/span 概念,采样策略 | +| [[02-服务治理/流量治理/README]] | 灰度发布策略,蓝绿 vs 金丝雀,Istio VirtualService | +| [[02-服务治理/安全机制/README]] | mTLS, JWT, RBAC/ABAC, 输入防护 | + +### 3. [[03-数据一致性]] — 跨服务数据一致性怎么保证? + +| 子主题 | 核心内容 | +|--------|---------| +| [[03-数据一致性/数据库拆分/README]] | 垂直拆分 / 水平分片,ShardingSphere,跨库查询方案 | +| [[03-数据一致性/分布式事务/README]] | Outbox 模式,Saga,TCC,AT 模式,幂等设计 | +| [[03-数据一致性/ID生成/README]] | Snowflake, UUID v7, 号段模式 | + +### 4. [[04-可观测性]] — 请求穿越多个服务后如何排查问题? + +| 子主题 | 核心内容 | +|--------|---------| +| [[04-可观测性/Metrics监控/README]] | Prometheus, RED/USE 方法, Counter/Gauge/Histogram | +| [[04-可观测性/日志系统/README]] | JSON 结构化日志, ELK vs Loki, 采集管线, 采样策略 | +| [[04-可观测性/链路追踪/README]] | OpenTelemetry, W3C Trace Context, 上下文传播, 采样 | +| [[04-可观测性/告警管理/README]] | SLO/Error Budget 告警, 分级降噪, On-Call 最佳实践 | + +### 5. [[05-部署运维]] — 上百个服务如何容器化编排与发布? + +| 子主题 | 核心内容 | +|--------|---------| +| [[05-部署运维/容器化/README]] | Dockerfile 最佳实践, 镜像安全, distroless | +| [[05-部署运维/Kubernetes/README]] | Pod/Deployment/Service/Ingress, Probe, HPA, 资源配置 | +| [[05-部署运维/CI-CD与GitOps/README]] | GitHub Actions Pipeline, GitOps (ArgoCD), Helm | +| [[05-部署运维/SRE实践/README]] | SLI/SLO/SLA, Error Budget, Blameless Postmortem, DORA Metrics | + +## 学习建议 > [!tip] 学习路径 > 按编号顺序阅读。第 1 篇是理论基础,后面 4 篇在实践中高度耦合,可以按需跳读。 @@ -36,6 +82,6 @@ graph LR > [!question] 带着问题读 > 每篇文章都设计了启发式问题。先自己想一遍答案,再看文档,效果更好。 -### 关联笔记 +## 关联笔记 -- [[API 设计原则]] — API Gateway 的设计与 REST/gRPC 选型 +- [[hzh/MS/API 设计原则]] — API Gateway 的设计与 REST/gRPC 选型