可观测性实践
可观测性三支柱
| 支柱 | 特点 | 回答的问题 | 典型工具 |
|---|---|---|---|
| Metrics | 聚合数值,低开销 | 发生了什么?多严重? | Prometheus、VictoriaMetrics |
| Logs | 非结构化/结构化文本 | 具体发生了什么? | ELK、Loki |
| Traces | 请求在服务间的完整路径 | 为什么慢?调用了谁? | Jaeger、Tempo、Zipkin |
OpenTelemetry
OpenTelemetry(OTel)是 CNCF 统一的可观测性数据采集标准,覆盖 Traces、Metrics、Logs 三种信号。
架构
Collector 配置
otel-collector-config.yaml
receivers:
otlp:
protocols:
grpc:
endpoint: 0.0.0.0:4317
http:
endpoint: 0.0.0.0:4318
processors:
batch:
timeout: 5s
send_batch_size: 512
# 尾部采样:只保留慢请求和错误请求的完整链路
tail_sampling:
policies:
- name: latency-policy
type: latency
latency: { threshold_ms: 500 }
- name: error-policy
type: status_code
status_code: { status_codes: [ERROR] }
- name: default-sample
type: probabilistic
probabilistic: { sampling_percentage: 10 }
exporters:
otlp/jaeger:
endpoint: jaeger:4317
tls:
insecure: true
prometheus:
endpoint: 0.0.0.0:8889
service:
pipelines:
traces:
receivers: [otlp]
processors: [batch, tail_sampling]
exporters: [otlp/jaeger]
metrics:
receivers: [otlp]
processors: [batch]
exporters: [prometheus]
应用集成示例(Go)
main.go
import (
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc"
sdktrace "go.opentelemetry.io/otel/sdk/trace"
)
func initTracer() func() {
exporter, _ := otlptracegrpc.New(ctx,
otlptracegrpc.WithEndpoint("otel-collector:4317"),
otlptracegrpc.WithInsecure(),
)
tp := sdktrace.NewTracerProvider(
sdktrace.WithBatcher(exporter),
sdktrace.WithResource(resource.NewWithAttributes(
semconv.SchemaURL,
semconv.ServiceNameKey.String("my-service"),
)),
)
otel.SetTracerProvider(tp)
return func() { tp.Shutdown(ctx) }
}
分布式链路追踪
Trace 数据模型
Trace(一次完整请求)
├── Span A: API Gateway (200ms)
│ ├── Span B: User Service (80ms)
│ │ └── Span D: DB Query (30ms)
│ └── Span C: Order Service (100ms)
│ └── Span E: Redis Cache (5ms)
每个 Span 包含:
- TraceID:全局唯一,贯穿整条链路
- SpanID / ParentSpanID:标识父子关系
- Operation Name:操作名称
- Duration:耗时
- Tags/Attributes:
http.method,http.status_code等 - Events/Logs:Span 内的事件记录
工具对比
| Jaeger | Tempo | Zipkin | |
|---|---|---|---|
| 存储 | ES/Cassandra/Kafka | 对象存储(S3) | ES/MySQL |
| 查询 | UI + API | TraceQL | UI + API |
| 成本 | 中等 | 低(无索引) | 低 |
| 适合 | 通用场景 | 大规模低成本 | 轻量场景 |
Tempo 的优势
Grafana Tempo 只存储完整 Trace,不建索引,存储成本极低。通过 TraceID 精准查询,配合 Exemplar 从指标跳转到 Trace。
SLI / SLO / SLA
| 概念 | 定义 | 示例 |
|---|---|---|
| SLI (指标) | 衡量服务质量的具体指标 | 请求成功率、P99 延迟 |
| SLO (目标) | 对 SLI 的目标值 | 可用性 ≥ 99.9%、P99 ≤ 200ms |
| SLA (协议) | 与客户签订的服务承诺(含赔偿) | 可用性 99.9%,否则赔偿 |
常用 SLI
| SLI 类型 | 计算公式 | 适用服务 |
|---|---|---|
| 可用性 | 成功请求数 / 总请求数 | API 服务 |
| 延迟 | 请求耗时 ≤ 阈值的比例 | API 服务 |
| 吞吐量 | 每秒处理请求数(QPS) | 数据管道 |
| 错误率 | 错误请求数 / 总请求数 | 所有服务 |
| 新鲜度 | 数据更新延迟 ≤ 阈值 | 数据服务 |
错误预算
SLO = 99.9%(月度)
月度总时间 = 30 × 24 × 60 = 43,200 分钟
允许不可用时间(错误预算)= 43,200 × 0.1% = 43.2 分钟
已消耗错误预算 = 本月累计不可用分钟数
剩余错误预算 = 43.2 - 已消耗
错误预算耗尽怎么办
当错误预算即将耗尽时,团队应停止新功能发布,集中精力提升可靠性。这是 SRE 团队平衡「创新速度」与「系统稳定性」的核心机制。
常见面试问题
Q1: 可观测性和传统监控有什么区别?
答案:
传统监控是「已知的已知」——预先定义好看什么指标、告警什么条件。可观测性是「已知的未知」和「未知的未知」——系统能让你在不预先预测问题的情况下,通过 Metrics + Logs + Traces 的关联分析来理解任何异常。
关键区别:
- 监控回答「是否出问题了?」
- 可观测性回答「出了什么问题?为什么?」
Q2: 如何设计合理的 SLO?
答案:
- 基于用户体验:选择用户能感知的指标(可用性、延迟),而非内部指标(CPU)
- 分析历史数据:先观察 1-3 个月的实际表现,以此为基础设定
- 留有余量:SLO 略高于 SLA(如 SLA 99.9% → SLO 99.95%)
- 不追求 100%:100% SLO 意味着不能做任何变更
- 定期回顾:每季度评估 SLO 是否合理,必要时调整
Q3: 链路追踪在生产环境如何控制开销?
答案:
- 采样策略:
- 头部采样:请求入口按比例采样(10%)
- 尾部采样:先收集完整链路,只保留慢请求/错误请求
- 异步上报:SDK 异步批量发送 Span,不阻塞业务
- 选择性埋点:核心服务全量采样,边缘服务低采样率
- 存储优化:使用 Tempo(无索引)降低存储成本