可观测性:日志、指标、链路¶
要点卡¶
30 秒回答
可观测性要从用户目标和故障问题反推信号:Metrics 看趋势与告警、Logs 记录离散事实、 Traces 还原跨服务因果,Profiles 定位代码级资源消耗。OpenTelemetry 是采集与传输框架, 不是存储后端;具体采用 Prometheus、Loki、Tempo 或商业平台取决于现有栈。设计时必须同时 控制基数、采样、敏感数据和成本。
3 分钟展开
- 是什么:Metrics = 聚合数值;Logs = 离散事件;Traces = 请求跨服务路径与耗时。
- 为什么:分布式下「哪个服务慢、哪条 SQL、是否重试」靠猜不行;MTTR 依赖可观测。
- 怎么做:按 W3C Trace Context 等标准传播上下文,把
trace_id/span_id关联到结构化日志; 为 HTTP/gRPC/DB 等关键边界埋点,指标标签保持低基数;按 SLO、故障保留需求和成本选择头采样 或尾采样,后端产品不是正确性的前提。
| 记忆槽 | 内容 |
|---|---|
| 三个不变量 | 信号从用户问题反推;跨信号上下文要可关联;指标基数、采样、PII 和成本必须有界 |
| 手画图 | request → metrics/logs/traces/profiles → collector → backends → SLO/diagnosis |
| 项目落点 | 用实际实时风控、Agent 工作流或链路监听说明从告警到 trace、日志和 profile 的定位路径;只引用本人实际参与部分和可解释指标 |
| 一个取舍 | 头采样成本低但未知结果;尾采样可保错误/慢链路却需要缓存完整 trace 和更多资源 |
错误表达
- ❌ “OpenTelemetry 就是监控后端;所有请求 100% trace 最完整;user_id 可直接放 metric label。”
- ✅ “OTel 负责采集/传输而非存储;采样与低基数设计是生产正确性和成本的一部分。”
自测追问:什么时候用 metrics、logs、traces、profiles?为什么 trace_id 适合日志字段却通常不适合指标标签?
10 分钟版(原理 + 图示)¶
flowchart LR
App[Go 服务 OTel SDK] --> Prom[Prometheus 指标]
App --> Loki[Loki / ELK 日志]
App --> Tempo[Tempo / Jaeger Trace]
Prom --> Grafana[Grafana 告警]
Loki --> Grafana
Tempo --> Grafana
RED vs USE
| 方法 | 适用 | 指标 |
|---|---|---|
| RED | 请求驱动服务 | Rate, Errors, Duration |
| USE | 资源 | Utilization, Saturation, Errors |
Go 关键指标(示例)
| 指标 | 类型 | 说明 |
|---|---|---|
http_request_duration_seconds |
Histogram | 按 bucket 聚合并估算分位数/SLO 好事件比例 |
http_requests_total |
Counter | 按 status code |
go_goroutines |
Gauge | 泄漏检测 |
process_resident_memory_bytes |
Gauge | OOM 预警 |
db_pool_in_use |
Gauge | 连接池饱和 |
容量估算(以下只用于演示量级,不能当产品默认值)
- Trace 采样:假设 10 万 QPS、每请求 10 span、每 span 1KB,原始量级约 1 GB/s, 尚未计索引、复制和协议开销,通常不能全量长期保留。头采样可在入口提前控成本,但当时还 不知道最终是否错误;若要优先保留错误/慢链路,可在 Collector 或支持该能力的后端做尾采样, 并为缓存完整 trace 的成本与丢弃策略做容量设计。
- 日志:10 万 QPS × 500B/条 ≈ 50 MB/s,需异步写 + 采样 debug。
生产场景¶
- P99 飙升:Grafana 看 RED → Trace 找慢 span → 日志搜 trace_id 看参数。
- 间歇 502:指标看 upstream 错误率 + 连接池。
- Go GC/调度毛刺:先看 runtime/metrics 中的 GC CPU、assist、heap goal、scheduler latency, 再用 execution trace、CPU/heap profile 定位;只看 GC pause 单一指标可能漏掉并发标记与 assist 成本。
排查与工具¶
| 工具 | 用途 |
|---|---|
| OpenTelemetry Go | 按当前稳定性支持采集、传播与导出信号 |
| Prometheus + Alertmanager | 告警 |
go tool pprof / trace |
进程内 |
| Grafana Explore | 日志+指标+Trace 关联 |
slog / zap |
结构化日志 |
路径:告警 → 指标定位服务 → Trace 定位 span → 日志定位参数 → pprof 定位代码。
架构取舍¶
| 方案 | 适用 | 不适用 |
|---|---|---|
| OTel 统一 | 新标准、多后端 | 极老系统 |
| 100% Trace | 短时诊断,或低 QPS 且容量/合规已验证 | 高 QPS 默认长期保留 |
| 同步写日志 | 开发 | 生产热路径 |
| 仅 Metrics | 资源监控 | 请求级根因 |
深挖问答¶
- Metrics 和 Logs 区别? → Metrics 便宜聚合告警;Logs 贵但含上下文。
- trace_id 怎么传递? → W3C
traceparentHeader;gRPC metadata;context.Context。 - Go 用什么日志库? → Go 1.21+
log/slog;高性能 zap;避免 fmt 拼接。 - Histogram 分桶怎么设? → 让 SLO 阈值成为明确边界,并结合真实延迟分布、可接受误差 和后端成本设置;不能照抄一组通用 bucket。
- 如何避免高 cardinality? → 不要把 user_id 作 label;用 trace 查个体。
反模式与事故¶
- 日志无 trace_id,跨服务无法关联。
- Prometheus label 爆炸(URL 全路径),TSDB 宕机。
- 只 collect 不告警,故障用户先发现。
- Debug 日志生产全开,IO 打满。
代码示例¶
import (
"log/slog"
"go.opentelemetry.io/otel"
"go.opentelemetry.io/otel/attribute"
)
func HandleOrder(ctx context.Context, id int64) error {
ctx, span := otel.Tracer("order").Start(ctx, "HandleOrder")
defer span.End()
span.SetAttributes(attribute.Int64("order.id", id))
slog.InfoContext(ctx, "processing order",
slog.Int64("order_id", id),
// 标准 slog 不会自动注入 trace_id;
// 需自定义/第三方 OTel-aware Handler 从 ctx 提取 SpanContext。
)
return process(ctx, id)
}