5设计分布式系统

可观测性

理解系统行为

指标日志追踪

🎯 AI 时代的学习建议:不要只读理论。理解这一章的设计动机(为什么需要这个概念?)和边界条件(什么情况下会失效?)。当 AI 生成的代码涉及这些概念时,你能判断它是否用对了场景。AI 能帮你总结内容,但无法替代你亲手阅读、思考、建立直觉的过程。

第5章 可观测性 (Observability)

一、导读

1.1 本章学习目标

理解可观测性(Observability)与传统监控(Monitoring)的区别与联系

掌握可观测性的三大支柱:指标(Metrics)、日志(Logs)、追踪(Traces)

理解分布式追踪(Distributed Tracing)的原理及其在微服务中的关键作用

掌握 OpenTelemetry 标准的架构、核心概念和集成方式

了解主流监控和可观测性平台(Prometheus、Grafana、Jaeger、ELK Stack)的架构与适用场景

理解结构化日志(Structured Logging)的设计原则与最佳实践

掌握 SLI(Service Level Indicator)、SLO(Service Level Objective)、SLA(Service Level Agreement)的定义与关系

能够设计和实现完整的可观测性方案

1.2 为什么本章如此重要

在分布式系统中,故障的表现形式越来越复杂。一个用户请求可能经过 10 个以上的微服务,任何一个环节的异常都可能导致最终结果的错误。传统的监控方式——基于预定义阈值的告警——已经不足以应对这种复杂性。我们需要一种能够从外部输出推断系统内部状态的能力,这就是可观测性。

可观测性不是"更好的监控",而是一种根本性的思维转变。传统监控关注"已知的未知"——我们知道可能发生什么,所以设置对应的监控指标。可观测性关注"未知的未知"——我们不知道系统会出现什么问题,但可以通过可观测性数据(指标、日志、追踪)来探索和诊断任何异常。

在微服务架构中,可观测性是开发和运维的"眼睛"。没有可观测性,你无法知道系统的性能瓶颈在哪里、用户请求为什么失败、某个服务的延迟为什么突然增加。可观测性直接影响故障发现时间(MTTD)、故障定位时间(MTTI)和故障修复时间(MTTR),是系统可靠性的关键保障。

1.3 核心问题引导

可观测性和监控有什么本质区别?为什么说可观测性 > 监控?

在微服务架构中,为什么分布式追踪比传统的日志分析更加重要?

指标、日志、追踪三种信号如何关联和互补?能否只用其中一种?

OpenTelemetry 解决了什么问题?它与之前的 OpenTracing、OpenCensus 有什么关系?

如何设计有效的告警规则?如何避免"告警疲劳"(Alert Fatigue)?

SLI/SLO/SLA 如何定义和使用?它们之间的关系是什么?

在高并发系统中,如何平衡可观测性数据的采集量和对系统性能的影响?


二、核心概念详解

概念一:可观测性的三大支柱

指标(Metrics):数值型的时间序列数据,适合聚合和趋势分析。指标是最轻量级的可观测性信号,占用资源最少,查询效率最高。

指标的类型:(1)计数器(Counter)——只增不减的值,如请求总数、错误总数;(2)仪表盘(Gauge)——可增可减的值,如当前连接数、内存使用率;(3)直方图(Histogram)——值的分布统计,如请求延迟分布;(4)摘要(Summary)——类似直方图但在客户端计算分位数。

指标的适用场景:系统资源使用率(CPU、内存、磁盘)、请求速率和错误率、延迟分布(P50/P95/P99)、业务指标(订单数、活跃用户数)。

日志(Logs):离散的事件记录,包含时间戳、日志级别、上下文信息和消息。日志是最丰富的可观测性信号,可以记录任意细节,但存储和查询成本较高。

日志的层次:(1)TRACE——最详细的调试信息;(2)DEBUG——开发调试信息;(3)INFO——一般信息性消息;(4)WARN——警告信息;(5)ERROR——错误信息;(6)FATAL——致命错误。

结构化日志(Structured Logging):使用 JSON 等格式输出日志,而非纯文本。结构化日志可以被机器解析和查询,大幅提高日志分析效率。例如:{"timestamp":"2024-01-01T00:00:00Z","level":"ERROR","service":"order-service","trace_id":"abc123","message":"Payment failed","error_code":"PAY_001"}。

追踪(Traces):记录一个请求在分布式系统中的完整调用路径。每个追踪(Trace)由多个跨度(Span)组成,每个 Span 代表一个操作(如一次 HTTP 调用、一次数据库查询)。Span 包含:操作名称、开始时间、持续时间、标签(Tags)和事件(Events)。

追踪的价值在于:(1)可视化请求的完整路径,快速定位延迟瓶颈;(2)理解服务间的依赖关系;(3)诊断跨服务的错误传播。

三大支柱的关系:指标用于"发现问题"(某个服务的延迟升高了);追踪用于"定位问题"(延迟发生在调用数据库的环节);日志用于"理解问题"(数据库查询慢是因为缺少索引)。三者协同工作,构成完整的可观测性体系。

概念二:分布式追踪的原理

分布式追踪需要解决两个核心问题:(1)上下文传播(Context Propagation)——如何在服务间传递追踪信息;(2)追踪数据收集——如何收集和存储追踪数据。

上下文传播:当一个服务收到请求并调用下游服务时,需要将追踪上下文(Trace ID、Span ID 等)传递给下游。传播方式取决于通信协议:HTTP 通过请求头传递(如 traceparent 头,W3C Trace Context 标准);gRPC 通过 metadata 传递;消息队列通过消息属性传递。

W3C Trace Context 标准定义了 traceparent 头的格式:00-{trace-id}-{parent-id}-{flags}。例如:00-0af7651916cd43dd8448eb211c80319c-b7ad6b7169203331-01。其中 trace-id 标识整个追踪,parent-id 标识父 Span,flags 标识采样标志。

追踪数据收集:有两种主要模式:

  • 同步收集:每个 Span 完成后直接发送到收集器。简单但增加了请求延迟。
  • 异步收集:Span 数据先写入本地缓冲区,由后台线程批量发送到收集器。对请求延迟影响小,但可能丢失数据(如进程崩溃时缓冲区中的数据)。OpenTelemetry 默认使用异步收集。

采样策略:在高流量系统中,收集所有追踪数据的成本可能过高。常见的采样策略:

  • 头部采样(Head-based Sampling):在追踪开始时决定是否采样。简单但可能丢失重要的追踪(如错误追踪)。
  • 尾部采样(Tail-based Sampling):在追踪完成后根据结果决定是否保留。可以保留所有错误追踪和慢请求追踪,但需要缓存所有 Span 数据,成本更高。

概念三:OpenTelemetry 标准

OpenTelemetry(简称 OTel)是 CNCF 的项目,由 OpenTracing 和 OpenCensus 合并而来。它提供了可观测性数据的标准化 API、SDK 和协议,目标是成为可观测性领域的统一标准。

OpenTelemetry 的核心组件:

API:定义了创建和操作 Traces、Metrics、Logs 的接口。API 是稳定的、与实现无关的——应用代码只依赖 API,不依赖具体的 SDK 实现。

SDK:API 的具体实现,负责数据的处理、导出和配置。SDK 是可插拔的——可以选择不同的 Exporter 将数据发送到不同的后端(Jaeger、Prometheus、ELK 等)。

Context Propagation:定义了上下文传播的标准,包括 W3C Trace Context、B3(Zipkin)等传播格式。

OTLP(OpenTelemetry Protocol):定义了可观测性数据的传输协议,支持 gRPC 和 HTTP/Protobuf。OTLP 是 OpenTelemetry 推荐的传输协议。

Instrumentation Libraries:为各种框架和库提供了自动插桩(Auto-instrumentation)能力。例如,OpenTelemetry 的 Java Agent 可以在不修改代码的情况下自动为 Spring Boot、gRPC、JDBC 等添加追踪。

OpenTelemetry 的价值在于:(1)供应商无关——切换可观测性后端不需要修改应用代码;(2)统一标准——Traces、Metrics、Logs 使用统一的 API 和 SDK;(3)社区驱动——由 CNCF 维护,得到了各大云厂商和可观测性厂商的支持。

概念四:Prometheus 与指标监控

Prometheus 是 CNCF 的毕业项目,是最流行的开源指标监控系统。它采用"拉"(Pull)模型——Prometheus Server 主动从目标服务拉取指标数据,而非由服务主动推送。

Prometheus 的核心架构:

Prometheus Server:负责指标数据的采集、存储和查询。它通过配置 scrape targets 定期拉取目标的 /metrics 端点。数据存储在本地时间序列数据库(TSDB)中。

Pushgateway:用于接收短生命周期任务的指标推送。这些任务可能在 Prometheus 拉取之前就结束了,因此需要主动推送指标到 Pushgateway,再由 Prometheus 拉取。

Alertmanager:负责告警的处理和通知。Prometheus Server 根据告警规则生成告警,发送到 Alertmanager 进行去重、分组、路由和通知(邮件、Slack、PagerDuty 等)。

Service Discovery:Prometheus 支持多种服务发现机制(Kubernetes、Consul、EC2 等),自动发现需要监控的目标。

PromQL:Prometheus 的查询语言,支持丰富的时间序列操作。例如:rate(http_requests_total{status="500"}[5m]) 计算过去 5 分钟内 500 错误的每秒速率。

Prometheus 的"拉"模型的优点:(1)可以知道目标是否存活(拉不到数据 = 目标挂了);(2)目标服务控制暴露哪些指标;(3)便于调试(可以直接访问目标的 /metrics 端点)。缺点:需要目标可达(不适合 NAT 后面的服务),配置大量目标时管理复杂。

概念五:SLI、SLO 与 SLA

SLI(Service Level Indicator,服务水平指标):衡量服务行为的某个方面的量化指标。常见的 SLI 包括:请求延迟(P50/P95/P99)、错误率、吞吐量、可用性。

SLI 的选择应该基于用户体验——用户关心什么,就度量什么。例如,对于 Web 服务,页面加载时间的 P95 是一个好的 SLI;对于 API 服务,响应延迟的 P99 是一个好的 SLI。

SLO(Service Level Objective,服务水平目标):基于 SLI 设定的目标值或范围。例如:"99.9% 的请求延迟低于 200ms"或"每月可用性不低于 99.95%"。

SLO 的设定需要平衡用户体验和技术可行性。过于宽松的 SLO 会导致用户流失,过于严格的 SLO 会导致过度工程和运维压力。Google 的 SRE 实践建议通过"错误预算"(Error Budget)来平衡可靠性和创新速度。

SLA(Service Level Agreement,服务水平协议):与客户的正式合同,定义了未达 SLO 时的后果(如退款、服务信用等)。SLA 通常比 SLO 更宽松——为 SLO 和 SLA 之间留出缓冲,避免因轻微违规而触发 SLA 惩罚。

错误预算(Error Budget):SLO 允许的不可靠程度。例如,如果 SLO 是 99.9% 可用性,则错误预算是 0.1%(每月约 43 分钟)。错误预算用于决策:当错误预算充足时,可以大胆发布新功能;当错误预算耗尽时,应该暂停发布,专注于可靠性改进。


三、深入分析与讨论

讨论一:可观测性数据的关联与统一

三大支柱(Metrics、Logs、Traces)的最大挑战是数据孤岛——它们通常存储在不同的系统中,使用不同的查询工具。当发现问题时,需要在多个系统之间切换,降低了诊断效率。

关联的关键是共享上下文:(1)Trace ID 应该出现在相关的日志和指标中;(2)日志的时间戳和 Span 的时间戳应该对齐;(3)指标的标签(如 service_name、instance)应该与 Span 的标签一致。

OpenTelemetry 通过统一的 SDK 和上下文传播机制,天然支持三大支柱的关联。每个 Span 自动携带 Trace ID 和 Span ID,日志可以通过 OpenTelemetry Logging SDK 自动关联到对应的 Trace。

讨论二:可观测性的成本管控

可观测性数据的存储和查询成本可能非常高昂——在某些组织中,可观测性成本占总云支出的 10-20%。成本管控策略包括:

(1)采样——不是所有数据都需要保留。对 Traces 进行智能采样(保留错误和慢请求),对 Logs 进行过滤(只保留 WARN 以上级别)。

(2)数据保留策略——热数据(最近 7 天)保留完整精度,温数据(7-30 天)降低精度,冷数据(30 天以上)归档或删除。

(3)指标聚合——使用 Recording Rules 预计算常用的 PromQL 查询,减少查询时的计算量。

(4)选择合适的存储——短期数据使用 Prometheus 本地存储,长期数据使用 Thanos 或 VictoriaMetrics 等长期存储方案。

讨论三:告警策略与告警疲劳

告警疲劳(Alert Fatigue)是可观测性实践中的常见问题——当告警过多、过杂或不够重要时,运维人员会逐渐忽视告警,甚至关闭告警通知。

有效的告警策略:(1)只告警需要人工干预的问题——如果可以自动修复,就不应该告警;(2)基于 SLO 告警——当错误预算消耗速度异常时告警,而非简单的阈值告警;(3)告警分级——P1(立即响应)、P2(工作时间内处理)、P3(下个迭代处理);(4)告警包含可操作信息——告警消息应该包含问题的症状、可能的原因和建议的处理步骤。


四、实践案例

案例一:Google 的分布式追踪演进

Google 是大规模分布式追踪的先驱。从早期的 Dapper(2010 年发表论文)到 Cloud Trace,Google 积累了大量实践经验。

Dapper 的核心设计:(1)低开销——追踪数据的收集对应用性能的影响控制在 0.2% 以内;(2)应用透明——尽可能通过库和中间件自动插桩,减少应用代码的修改;(3)全局唯一 ID——使用 64 位或 128 位的 Trace ID 确保全局唯一性。

Dapper 的架构包括:应用库(负责 Span 的创建和上下文传播)、收集守护进程(每个机器上运行一个,异步上传 Span 数据)、集中存储(Bigtable 存储追踪数据)、Web UI(可视化追踪数据)。

Dapper 的设计深刻影响了后续的开源项目:Zipkin(Twitter)、Jaeger(Uber)、OpenTracing、OpenCensus,最终汇聚为 OpenTelemetry。

案例二:Uber 的 Jaeger 分布式追踪系统

Uber 的 Jaeger 是 CNCF 的毕业项目,灵感来源于 Dapper,专为微服务架构设计。

Jaeger 的架构:(1)Jaeger Client——各语言的 SDK,负责 Span 的创建和上报;(2)Jaeger Agent——部署在每个主机上的守护进程,接收 Client 上报的 Span 数据并批量发送到 Collector;(3)Jaeger Collector——接收 Span 数据,进行验证、索引和存储;(4)存储后端——支持 Cassandra、Elasticsearch、Kafka(用于缓冲)等;(5)Jaeger Query——提供 Web UI 和 API 查询追踪数据。

Jaeger 的特色功能:(1)自适应采样——根据流量自动调整采样率,在低成本和高覆盖之间平衡;(2)Span 标签搜索——支持基于标签的灵活查询;(3)依赖图——自动生成服务间的依赖关系图;(4)与 OpenTelemetry 集成——Jaeger 原生支持 OTLP 协议。


五、常见误区与注意事项

将可观测性等同于监控:监控是"已知问题"的检测,可观测性是"未知问题"的探索能力。仅有监控指标是不够的,需要三大支柱协同工作。

过度采集日志:DEBUG 级别的日志在生产环境中应该默认关闭。过多的日志不仅增加存储成本,还可能泄露敏感信息。应该使用结构化日志,并通过日志级别和过滤器控制采集量。

忽视追踪的上下文传播:如果服务间没有正确传递 Trace Context,追踪链路就会断裂,无法看到完整的调用路径。需要确保所有通信协议(HTTP、gRPC、消息队列)都正确传播追踪上下文。

告警阈值设置不合理:基于绝对值(如 CPU > 80%)的告警容易产生误报。应该基于 SLO 和错误预算设置告警,关注"用户是否受到影响"而非"资源是否繁忙"。

可观测性数据的安全风险:日志中可能包含用户密码、Token、个人信息等敏感数据。需要在日志输出前进行脱敏处理,并确保可观测性数据的存储和传输加密。


六、本章小结

本章系统介绍了分布式系统的可观测性体系。可观测性的三大支柱——指标、日志、追踪——各有侧重,协同工作:指标用于发现问题,追踪用于定位问题,日志用于理解问题。

OpenTelemetry 作为统一标准,解决了可观测性领域的碎片化问题,提供了供应商无关的 API、SDK 和协议。Prometheus 作为指标监控的事实标准,通过 Pull 模型和 PromQL 提供了强大的时间序列分析能力。SLI/SLO/SLA 框架将可观测性与业务目标关联,通过错误预算平衡可靠性和创新速度。

在实际系统中,可观测性不是"锦上添花",而是构建和运维分布式系统的基础设施。没有可观测性,微服务架构就像一个黑盒——你知道它出了问题,但不知道问题在哪里、为什么发生。


七、思考题与练习

设计一个可观测性方案,覆盖指标、日志、追踪三个维度。说明你选择的工具链、数据采集方式和存储方案。

解释 W3C Trace Context 的 traceparent 头格式。为什么需要 parent-id?如果一个请求同时调用了 3 个下游服务,追踪数据的结构是什么样的?

比较 Prometheus 的 Pull 模型和 Push 模型的优劣。在什么场景下需要使用 Pushgateway?

为一个电商系统设计 SLI 和 SLO。至少覆盖以下维度:可用性、延迟、错误率、订单处理。定义错误预算的计算方式。

分析"告警疲劳"的原因和影响。设计一个告警策略,包括分级规则、通知渠道和升级机制。

实现一个结构化日志组件,支持 JSON 格式输出、自动注入 Trace ID 和 Span ID、日志级别过滤和敏感信息脱敏。

调研 Thanos 或 Cortex 的架构,说明它们如何解决 Prometheus 的长期存储和多集群查询问题。

在一个包含 20 个微服务的系统中,如果每秒有 10000 个请求,估算全量追踪的数据量。设计一个采样策略,在保证可观测性的同时控制成本。

5/6可观测性
0%