Init
This commit is contained in:
@@ -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{"这个值是<br/>只增不减的吗?"}
|
||||
|
||||
Q -- "是" --> C["Counter<br/>请求数、错误数、订单量"]
|
||||
Q -- "否" --> G["Gauge<br/>在线用户数、队列长度、内存使用量"]
|
||||
|
||||
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-可观测性/04-告警管理]] — Metrics 是告警的基础数据来源
|
||||
- [[04-可观测性/03-链路追踪]] — Tracing 与 Metrics 互补,定位具体故障
|
||||
- [[05-部署运维/04-SRE实践]] — SLO 基于 Metrics 数据
|
||||
@@ -0,0 +1,149 @@
|
||||
---
|
||||
tags: [microservice, logging, elk, loki, structured-logging]
|
||||
create time: 2026-05-05
|
||||
---
|
||||
|
||||
# 日志系统
|
||||
|
||||
## 概述
|
||||
|
||||
Logging 回答的问题是:**具体发生了什么?**——通过原始事件记录提供上下文。
|
||||
|
||||
在微服务架构中,所有服务的日志必须集中收集。散落在各台机器上的日志无法支撑有效的故障排查。
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
APP[应用容器] -->|stdout/stderr| COLLECTOR[采集器<br/>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["采集器<br/>Filebeat / FluentBit"]
|
||||
Collector --> Backend["存储后端<br/>Elasticsearch / Loki"]
|
||||
Backend --> UI["查询面板<br/>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<br/>低成本、轻量"]
|
||||
Q -- "是,复杂的全文搜索" --> ELK["✅ ELK<br/>功能强大"]
|
||||
|
||||
LOKI --> Check{"日日志量 > 1TB?"}
|
||||
Check -- 是 --> ConsiderES["考虑混合架构<br/>高频用 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-可观测性/01-Metrics监控]] — Metrics + Logging = 完整的问题定位能力
|
||||
- [[04-可观测性/03-链路追踪]] — trace_id 是日志与追踪的桥梁
|
||||
- [[04-可观测性/04-告警管理]] — 日志可以触发基于模式的告警
|
||||
@@ -0,0 +1,166 @@
|
||||
---
|
||||
tags: [microservice, tracing, opentelemetry, context-propagation]
|
||||
create time: 2026-05-05
|
||||
---
|
||||
|
||||
# 链路追踪
|
||||
|
||||
## 概述
|
||||
|
||||
Tracing 回答的问题是:**请求在哪一步慢了 / 失败了?**——通过完整的调用链定位瓶颈和故障。
|
||||
|
||||
一次请求在多个服务中的完整调用路径:
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
RID["Request<br/>trace_id: abc-123"]
|
||||
|
||||
subgraph SPANS["调用链 (Trace)"]
|
||||
direction TB
|
||||
S1["Span #1<br/>API Gateway<br/>5ms"]
|
||||
S2["Span #2<br/>Order Service<br/>70ms"]
|
||||
S3["Span #3<br/>Payment RPC<br/>160ms"]
|
||||
S4["Span #4<br/>Inventory RPC<br/>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-可观测性/01-Metrics监控]] — Tracing 与 Metrics 互补
|
||||
- [[04-可观测性/02-日志系统]] — trace_id 串联日志和追踪
|
||||
- [[04-可观测性/04-告警管理]] — 基于 Trace 数据的异常检测告警
|
||||
@@ -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["冻结发布 ❄️<br/>专注稳定性修复"]
|
||||
|
||||
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["激进策略:<br/>保持常规告警阈值<br/>加快迭代节奏"]
|
||||
BudgetLeft -- "10~50%" --> Moderate["保守策略:<br/>降低告警阈值<br/>收紧发布频率"]
|
||||
BudgetLeft -- "< 10%" --> Emergency["紧急策略:<br/>所有非紧急告警暂停<br/>全员关注稳定性"]
|
||||
|
||||
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-可观测性/01-Metrics监控]] — Metrics 是告警的数据来源
|
||||
- [[05-部署运维/04-SRE实践]] — SLO/Error Budget 的详细方法论
|
||||
- [[02-服务治理/06-容错模式]] — 熔断器状态可以作为告警信号
|
||||
@@ -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-可观测性/01-Metrics监控]] | 怎么量化系统的健康度?RED/USE 方法怎么用? |
|
||||
| 2 | [[04-可观测性/02-日志系统]] | 日志怎么集中收集?结构化日志的最佳实践是什么? |
|
||||
| 3 | [[04-可观测性/03-链路追踪]] | trace_id 如何贯穿跨服务调用链?OpenTelemetry 怎么用? |
|
||||
| 4 | [[04-可观测性/04-告警管理]] | 如何设计告警避免疲劳?SLO/Error Budget 怎么做? |
|
||||
|
||||
### 为什么需要独立的"可观测性"体系?
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph MONO["单体系统"]
|
||||
S[Service] --> DB[(DB)]
|
||||
LOG["同一份日志<br/>一目了然"]
|
||||
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["问题:日志分散、链路断裂<br/>传统监控无法回答'这个请求经历了什么'"]
|
||||
MICRO --> NOTE
|
||||
```
|
||||
|
||||
> [!keypoint] 可观测性与监控的本质区别
|
||||
>
|
||||
> **监控系统**告诉你"出事了"(已知未知),**可观测性系统**让你去探究"为什么会出事"(未知未知)。
|
||||
|
||||
### 三大支柱
|
||||
|
||||
| 支柱 | 回答的问题 | 典型工具 |
|
||||
|------|-----------|---------|
|
||||
| **Metrics** | 系统现在健康吗?— 趋势、告警 | Prometheus + Grafana |
|
||||
| **Logging** | 具体发生了什么?— 历史回溯 | ELK / Loki |
|
||||
| **Tracing** | 请求在哪一步慢了/失败了?— 链路追踪 | Jaeger / Zipkin |
|
||||
|
||||
### 关联笔记
|
||||
|
||||
- [[02-服务治理]] — 熔断器可以触发告警,与监控系统联动
|
||||
- [[05-部署运维/02-Kubernetes]] — K8s Liveness/Readiness Probe 是可观测性的基础
|
||||
- [[hzh/MS/README.md]] — 完整微服务知识索引
|
||||
Reference in New Issue
Block a user