「系统出了问题但不知道哪里出了问题」是后端工程师最痛苦的体验。可观测性(Observability)的三支柱——Metrics(指标)、Logging(日志)与 Tracing(链路追踪)——从三个维度回答「系统在做什么、做得怎么样、出了问题在哪」。三者不是替代关系,是互补关系:Metrics 告诉你「有问题」,Logging 告诉你「细节是什么」,Tracing 告诉你「在调用链的哪一环」。本篇讲清三支柱的分工与统一。
支柱一:Metrics——系统的体检报告
Metrics 的特征:数值型的时间序列数据——QPS、延迟 P99、错误率、CPU 使用率、内存占用——以固定间隔采集,适合聚合与告警。Metrics 的数据模型:指标名 + 标签集 + 时间戳 + 值——例如 http_request_duration_seconds{method="POST", path="/api/orders", quantile="0.99"} = 0.35。三种指标类型:Counter(计数器)——只增不减(请求总数、错误总数——适合算速率 rate());Gauge(仪表盘)——可增可减的瞬时值(CPU 使用率、队列长度、连接数);Histogram(直方图)——值的分布(请求延迟的分桶——P50/P95/P99 从分桶计算)。Metrics 的告警是它的核心价值——Prometheus 的告警规则基于 PromQL 查询(rate(http_errors_total[5m]) / rate(http_requests_total[5m]) > 0.05——5 分钟错误率超过 5% 触发告警)。Metrics 的局限:只有「发生了什么」(错误率上升了),没有「为什么」(哪个请求、什么参数、调用了哪个下游出错)——细节要靠 Logging 和 Tracing 补。
支柱二:Logging——事件的详细记录
Logging 的特征:离散的事件记录——每条日志包含时间戳、级别、消息与上下文——记录「在某个时刻发生了某件事」。日志级别的语义:ERROR——需要人工介入的异常(数据库连接失败、未捕获异常);WARN——需要关注的异常(重试成功、降级触发、接近阈值);INFO——关键业务事件(订单创建、支付完成、用户登录);DEBUG——调试信息(变量值、SQL 语句——生产环境通常关闭)。结构化日志(structured logging)是现代日志的最佳实践:用 JSON 格式代替纯文本({"timestamp": "...", "level": "ERROR", "service": "order", "trace_id": "abc123", "msg": "payment failed", "order_id": "12345"})——结构化日志可以被日志系统(如 ELK)精确检索与聚合,纯文本日志只能靠正则匹配。日志的纪律:每条 ERROR 级日志必须有 trace_id(关联到调用链)、不要在循环中打日志(日志洪灾淹没有效信息)、不要在日志中记录敏感数据(密码、Token、身份证号)。
支柱三:Tracing——调用链的全景图
Tracing 的特征:一次请求在分布式系统中经过的所有服务的调用链——Trace 是一次请求的完整链路,包含多个 Span(每个 Span 代表一次服务调用或操作),Span 之间有父子关系(调用关系)与时间关系(开始与结束时间)。Trace 的结构:TraceId(全局唯一,贯穿整个调用链)→ 多个 Span(每个 Span 有 SpanId、ParentSpanId、服务名、操作名、开始时间、持续时间、标签)。Tracing 的核心价值:定位慢在哪——一次用户请求 500ms,Tracing 展示:网关 5ms → 认证服务 10ms → 订单服务 200ms(其中数据库查询 180ms)→ 通知服务 50ms——180ms 的数据库查询就是瓶颈。traceId 的传播是分布式追踪的关键:入口服务生成 traceId,通过 HTTP 头(traceparent 头,W3C Trace Context 标准)传递给下游服务,下游服务在日志与指标中也携带 traceId——一个 traceId 串起了 Metrics、Logging 与 Tracing 三支柱。
OpenTelemetry:三支柱的统一
OpenTelemetry(OTel)是 CNCF 的可观测性框架,目标是统一三支柱的数据采集与传输。OTel 的架构:SDK(应用程序嵌入,自动采集 Metrics/Logs/Traces)→ OTLP 协议(统一的数据格式)→ Collector(接收、处理、导出到任意后端——Prometheus、Jaeger、ELK 等)。OTel 的价值:** instrumentation 一次,到处可用——应用程序只需要接入 OTel SDK(自动注入 traceId、自动采集指标、自动记录日志),数据可以导出到任意可观测性后端——避免厂商锁定。自动 instrumentation是 OTel 的重要特性:Java 的 Java Agent 方式(零代码修改,字节码增强自动采集 HTTP 调用、数据库查询、消息队列等)、Python 的自动 instrumentation 库——减少了手动埋点的工程成本**。
Tracing 的实践:Span 的设计与 traceId 的传播
分布式链路追踪的核心概念:Trace 是一次请求的完整链路(包含多个 Span),Span 是一次服务调用或操作的记录(有开始时间、结束时间、标签与日志)。Span 的设计:每个 Span 代表一个「有意义的操作单元」——一个 HTTP 调用、一次数据库查询、一次消息发送、一个函数调用——Span 之间有父子关系(调用关系)形成树状结构。traceId 的传播:入口服务生成 traceId,通过 HTTP 头传递给下游服务(W3C Trace Context 标准的 traceparent 头),每个服务在日志中携带 traceId——一个 traceId 串起了一次请求经过的所有服务的所有日志。Span 采样的策略:全量采样在大规模系统中成本过高(每个请求都记录完整链路),采样策略有头部采样(在入口决定是否采样——简单但可能遗漏有问题的请求)与尾部采样(在完成后决定是否保留——可以保留所有异常请求的链路但需要暂存所有数据)。OpenTelemetry 的自动 instrumentation 大幅降低了 Tracing 的接入成本(Java Agent 方式零代码修改、Python 的自动 instrumentation 库)。
Logging 的结构化实践:JSON 日志与 traceId
结构化日志是现代日志的最佳实践:用 JSON 格式代替纯文本格式。纯文本日志的问题:无法精确检索(正则匹配的误报率高)、无法聚合分析(不同格式的日志无法统一处理)、无法关联上下文(日志与调用链脱节)。JSON 日志的优势:可以被日志系统(ELK/Loki)精确检索与聚合——按 traceId 查找一次请求的所有日志、按 level 聚合错误率、按 service 统计各服务的日志量。JSON 日志的字段设计:timestamp(ISO 8601 格式)、level(ERROR/WARN/INFO/DEBUG)、service(服务名)、trace_id(链路追踪 ID——串联三支柱的线索)、span_id、message(人类可读的描述)、以及自定义的业务字段(order_id、user_id 等)。日志库的配置:Java 用 Logback + Logstash Encoder 输出 JSON、Python 用 structlog 或 python-json-logger、Go 用 zap 或 zerolog——每个语言都有成熟的 JSON 日志库。日志的纪律:每条 ERROR 级日志必须有 trace_id;不要在循环中打日志;不要在日志中记录敏感数据(密码、Token、身份证号)。
Metrics 的告警设计:P99 延迟与 SLO
Metrics 的告警设计需要从 SLO(Service Level Objective)出发——而不是从「什么指标可以采集」出发。SLO 的定义:服务应该达到的可靠性目标(如 99.9% 的请求在 200ms 内返回、99.95% 的可用性)。SLO 的配套概念:SLI(Service Level Indicator)——度量 SLO 是否达成的指标(延迟 P99、错误率、可用性);Error Budget(错误预算)——1 减去 SLO 得到的「允许出错的预算」(99.9% 的 SLO 意味着每月允许 43 分钟的不可用)。告警的分级:Page(立即处理)——错误预算消耗超过 50% 或 SLI 严重违反;Ticket(当天处理)——错误预算消耗超过 25%;无动作——在预算内正常消耗。告警的纪律:不要为每个指标设告警(告警疲劳会让团队忽略所有告警)——只为 SLO 相关的指标设告警。Prometheus 的告警规则基于 PromQL 查询——告警的条件应该是「SLI 违反 SLO」而不是「某个指标超过某个随机阈值」。
OpenTelemetry 的自动 instrumentation:零代码修改的可观测性
OpenTelemetry 的自动 instrumentation 大幅降低了 Tracing 的接入成本。Java 的 Java Agent 方式:零代码修改——通过字节码增强自动采集 HTTP 调用、数据库查询、消息队列等操作的 Span——只需要在 JVM 启动参数中添加 javaagent 路径。Python 的自动 instrumentation 库:opentelemetry-instrumentation 包可以自动 instrument Flask、FastAPI、SQLAlchemy、requests 等常见框架。自动 instrumentation 的价值:不需要手动埋点(本库语境的手动埋点的成本是可观测性接入的最大阻力)、覆盖了常见的框架与库、减少了人为遗漏的风险。代价是自动 instrumentation 可能采集一些不需要的 Span(噪音)——需要通过配置过滤不需要的自动 instrumentation。
Tracing 的实践与 Span 设计
分布式链路追踪的核心概念 Trace 是一次请求的完整链路包含多个 Span 每个 Span 代表一次服务调用或操作。Span 的设计每个 Span 代表一个有意义的操作单元——一个 HTTP 调用一次数据库查询一次消息发送。traceId 的传播入口服务生成 traceId 通过 HTTP 头传递给下游服务每个服务在日志中携带 traceId——一个 traceId 串起了一次请求经过的所有服务的所有日志。Span 采样策略全量采样在大规模系统中成本过高采样策略有头部采样与尾部采样。OpenTelemetry 的自动 instrumentation 大幅降低了 Tracing 的接入成本。
Metrics 的告警设计与 SLO
Metrics 的告警设计需要从 SLO 出发而不是从什么指标可以采集出发。SLO 的定义服务应该达到的可靠性目标如 99.9% 的请求在 200ms 内返回。SLI 是度量 SLO 是否达成的指标延迟 P99 错误率可用性。Error Budget 是 1 减去 SLO 得到的允许出错的预算。告警的分级 Page 立即处理错误预算消耗超过 50% 或 SLI 严重违反 Ticket 当天处理错误预算消耗超过 25%。告警的纪律不要为每个指标设告警只为 SLO 相关的指标设告警。
OpenTelemetry 的 Collector:数据管道的中间层
OpenTelemetry Collector 是可观测性数据的中间层:接收来自应用程序的 Metrics Logs Traces 数据,处理后导出到任意后端。Collector 的三个角色:Receiver(接收数据——支持多种协议 OTLP/Jaeger/Prometheus)、Processor(处理数据——批量、采样、过滤、属性添加)、Exporter(导出数据——支持多种后端 Prometheus/Jaeger/ELK/Zipkin)。Collector 的价值:解耦应用程序与后端(应用程序只需要发送到 Collector,后端切换不需要修改应用程序)、数据处理的集中管理(采样、过滤、PII 脱敏在 Collector 层统一处理)。Collector 的部署模式:Agent 模式(每个节点部署一个——本地处理减少网络开销)与 Gateway 模式(集中部署一个——统一管理与配置——大规模场景的推荐模式)。
小结
可观测性三支柱收成四条:Metrics 告诉你「有问题」(数值时间序列 + 告警)、Logging 告诉你「细节」(离散事件 + 结构化日志)、Tracing 告诉你「在哪一环」(调用链全景 + 瓶颈定位)——三者互补不可替代;traceId 是串联三支柱的线索——Metrics 带标签、日志带 trace_id、Span 带 traceId;OpenTelemetry 统一三支柱的数据采集——instrumentation 一次到处可用,避免厂商锁定;可观测性的落地路径——先 Metrics + 告警(发现问题)→ 加 Logging(定位细节)→ 加 Tracing(定位瓶颈)→ 统一到 OpenTelemetry(消除碎片化)。可观测性的终极目标:当系统出问题时,你不需要「猜」——三支柱的数据会告诉你答案。
参考资料
- OpenTelemetry 官方文档 —— 统一可观测性框架
- 本库前篇:《LLM 应用的可观测性》《操作系统内功 · 第 10 篇|性能观测方法论》
- Charity Majors, 《Observability Engineering》—— 可观测性理念的经典著作
读者留言
COMMENTS 暂无还没有留言,来说第一句?