【架构实战】可观测性三支柱实战:Metrics、Logging、Tracing 如何统一落地

【架构实战】可观测性三支柱实战:Metrics、Logging、Tracing 如何统一落地

上午我们聊了全链路追踪,用 OpenTelemetry 把每一次调用串成一条 Trace。但单靠 Trace 是不够的------Trace 回答"这一次请求为什么慢",Metrics 回答"系统整体水位怎么样",Logging 回答"具体报了什么错"。这三者合起来,才叫可观测性(Observability)。

很多团队踩过的坑是:Prometheus 一套、ELK 一套、SkyWalking 一套,三套系统各玩各的,指标对不上、日志串不起、链路查不到根因。排查一个问题要在三个控制台之间来回跳,光"对时间"就能对半小时。

今天聊清楚:可观测性三支柱到底是什么、怎么选型、以及如何用 OpenTelemetry 把它们统一到一条技术栈里

一、三支柱:谁解决什么问题

1.1 Metrics(指标):系统健康度

Metrics 是聚合后的数值,回答"系统现在怎么样":

  • 每秒请求数(QPS)、错误率、延迟分位数(P50/P95/P99)
  • CPU、内存、磁盘、网络
  • 队列堆积量、连接池使用率、GC 暂停时间

特点:数据量小、可聚合、可告警。适合做 dashboard 和告警规则,但看不到"具体哪一次请求"。

1.2 Logging(日志):事件详情

Logging 是离散的事件记录,回答"到底发生了什么":

  • 业务日志:下单成功、支付回调、库存扣减
  • 异常日志:Exception 堆栈、SQL 执行失败
  • 访问日志:谁在什么时间调了什么接口

特点:信息最丰富、最细粒度,但数据量巨大,且非结构化(或半结构化),检索需要索引。

1.3 Tracing(追踪):调用链路

Tracing 是跨服务的调用关系记录,回答"一次请求经过了哪些服务、每步花了多久":

  • Trace ID 贯穿所有服务
  • 每个 Span 记录一个操作的耗时、状态、属性
  • 还原完整的调用树,定位最慢环节

特点:结构清晰、跨服务串联,但采样成本高,无法记录所有请求的所有细节。

1.4 三者的关系:一个经典例子

用户反馈"下单很慢"。三种工具各自怎么用:

  1. Metrics 先定位:查 Prometheus,发现下单接口 P99 从 200ms 涨到 2s,错误率 3%。确认问题存在,范围在订单服务。
  2. Tracing 找环节 :查 Trace,发现 order-service 调用 inventory-service 的 Span 耗时 1.8s,其他环节都正常。定位到库存服务。
  3. Logging 查根因 :查库存服务的日志,发现 Redis 连接池获取超时,堆栈指向 JedisConnectionException: timeout。根因是连接池配置过小 + Redis 抖动。

三者是递进关系,缺一不可。只靠日志,你得猜是哪个服务慢;只靠指标,你不知道具体哪次请求;只靠 Trace,你不知道底层报了什么错。

二、选型:主流方案对比

2.1 Metrics 领域

方案 特点 适合场景
Prometheus + Grafana 拉模式、PromQL 强大、生态最全 事实标准,K8s 环境首选
VictoriaMetrics Prometheus 兼容、单机性能强、存储省 大规模指标、长期存储
Thanos / Mimir Prometheus 高可用 + 长期存储 多集群、多租户场景
InfluxDB 写模型不同、持续查询 时序分析类需求(较少用于 K8s)

2.2 Logging 领域

方案 特点 适合场景
ELK(Elasticsearch + Logstash + Kibana) 全文检索强、生态成熟 经典方案,中小规模首选
EFK(Fluentd/Fluent Bit + ES) 采集轻量、配置灵活 K8s 环境常用
Loki + Grafana 只索引标签不索引内容、成本低 与 Prometheus 共用 Grafana 的团队
ClickHouse 自建 写入极快、压缩比高 日志量特别大的场景

2.3 Tracing 领域

方案 特点 适合场景
Jaeger OTel 原生支持、轻量 中小团队快速落地
Zipkin 老牌、简单 历史项目迁移成本低
SkyWalking 无侵入 Agent、中文文档全 Java 系、不想改代码的团队
Grafana Tempo 与 Grafana 生态深度集成 已用 Grafana 全家桶的团队

2.4 关键判断:到底选几套?

很多团队的误区是"Metrics 选 Prometheus、Logging 选 ELK、Tracing 选 SkyWalking,都是各自领域最强的"------然后发现三套系统的数据互相割裂

  • 告警说 QPS 暴涨,但 Trace 里查不到对应时段的数据(采样率不同)
  • 日志里有关联的 requestId,但 Tracing 用另一套 traceId,对不上
  • 三个 UI 三套查询语法,值班同学要记三本手册

正确思路是:优先选能"统一"的栈。要么全上 Grafana 系(Prometheus + Loki + Tempo),要么全上 OTel 生态(统一 exporter + collector,后端按需接)。

三、统一的关键:OpenTelemetry

OpenTelemetry(OTel)已经成了可观测性的事实标准------它用一套 SDK 统一了 Metrics、Logging、Tracing 三大信号的采集,通过 OTLP 协议发给后端。

3.1 OTel 的架构

复制代码
应用 (Java/Go/Python/Node...) 
   │   OTel SDK 采集三大信号
   ▼
OTel Collector (agent 模式 / 独立部署)
   │   OTLP / Prometheus / Jaeger / 其他协议
   ▼
后端存储 + 可视化 (Prometheus+Grafana / Tempo / Loki / ES...)

核心组件:

  • OTel SDK:打进应用里的库,自动或手动埋点,生成 Metrics、Logs、Traces 三种数据
  • OTel Collector:独立的数据管道服务,接收、处理、转发遥测数据,支持路由、采样、脱敏、批处理
  • OTLP 协议:三种信号统一走这个协议传输,一套网络、一套鉴权

3.2 三大信号如何关联

OTel 的杀手锏是通过 Context 关联三种信号

  • 每个请求生成 trace_idspan_id
  • 日志里自动注入当前 Trace 的 trace_id
  • 指标采集时带上服务名、环境等标签

这样就能实现"从指标下钻到日志,从日志跳到 Trace":

yaml 复制代码
# OTel SDK 配置示例(环境变量)
OTEL_SERVICE_NAME: order-service
OTEL_EXPORTER_OTLP_ENDPOINT: http://otel-collector:4317
OTEL_METRICS_EXPORTER: otlp
OTEL_LOGS_EXPORTER: otlp
OTEL_TRACES_EXPORTER: otlp
OTEL_TRACES_SAMPLER: parentbased_traceidratio
OTEL_TRACES_SAMPLER_ARG: "0.1"

只要配置了这些,应用启动后:

  • 自动采集 HTTP/RPC 调用指标(QPS、延迟、错误率)
  • 自动生成调用链 Span
  • 日志自动带上 trace_id

3.3 日志关联 Trace 的关键操作

日志要能和 Trace 关联,必须把 trace_id 注入到日志字段里。以 Java + Logback 为例:

xml 复制代码
<pattern>
  %d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} 
  [traceId=%X{trace_id:-}, spanId=%X{span_id:-}] - %msg%n
</pattern>

OTel Java Agent 会自动把 trace_id/span_id 放入 MDC(Mapped Diagnostic Context),Logback 里用 %X{trace_id} 就能取到。排查问题时:

  1. 从告警或 Dashboard 看到异常
  2. 日志检索 trace_id=xxx 找到那一次请求的所有日志
  3. 点开同 ID 的 Trace,看完整调用链

从"三套系统来回切"变成"一个 ID 贯穿到底"

四、落地架构实战:一套完整的可观测性平台

下面给出一套经过验证的落地方案,中小团队(5~50个服务)可以直接照抄。

4.1 整体架构

复制代码
┌─ K8s 集群 ──────────────────────────────────────┐
│  应用 Pod (OTel SDK / Agent 埋点)                │
│     │  OTLP (gRPC)                              │
│  OTel Collector (DaemonSet, 每个节点一个)         │
│     ├──→ Prometheus (指标) ──→ Grafana (看板)     │
│     ├──→ Tempo (Trace) ────→ Grafana (链路视图)   │
│     └──→ Loki (日志) ──────→ Grafana (日志检索)   │
│                     │                           │
│                     └──→ Alertmanager → 钉钉/企微 │
└─────────────────────────────────────────────────┘

选型理由:

  • Prometheus + Grafana:指标事实标准,Grafana 一个入口看所有
  • Tempo + Loki :同为 Grafana 系,天然联动,trace_id 一键跳转
  • OTel Collector:统一接入,未来换后端不用改应用
  • Alertmanager:统一告警,路由到钉钉/企业微信/邮件

4.2 OTel Collector 配置示例

yaml 复制代码
# 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: 1024
  memory_limiter:
    check_interval: 1s
    limit_mib: 512

exporters:
  prometheus:
    endpoint: 0.0.0.0:9091
    namespace: otel
  otlp/tempo:
    endpoint: tempo:4317
    tls:
      insecure: true
  otlp/loki:
    endpoint: loki:4317
    tls:
      insecure: true

service:
  pipelines:
    metrics:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [prometheus]
    traces:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/tempo]
    logs:
      receivers: [otlp]
      processors: [memory_limiter, batch]
      exporters: [otlp/loki]

4.3 告警规则实战

指标采进来了,不配告警等于白采。经典告警规则:

yaml 复制代码
groups:
  - name: service-alerts
    rules:
      # 错误率超过 5% 持续 5 分钟
      - alert: HighErrorRate
        expr: |
          sum(rate(http_server_duration_milliseconds_count{
            status_code=~"5.."
          }[5m])) by (service_name)
          / sum(rate(http_server_duration_milliseconds_count[5m])) 
            by (service_name) > 0.05
        for: 5m
        labels:
          severity: critical
        annotations:
          summary: "{{ $labels.service_name }} 错误率超过 5%"
          description: "当前错误率 {{ $value | humanizePercentage }}"

      # P99 延迟超过 1s 持续 10 分钟
      - alert: HighLatency
        expr: |
          histogram_quantile(0.99, 
            sum(rate(http_server_duration_milliseconds_bucket[5m])) 
              by (service_name, le)) > 1000
        for: 10m
        labels:
          severity: warning
        annotations:
          summary: "{{ $labels.service_name }} P99 延迟超过 1s"

告警分层建议:

  • P0(立即处理):错误率 > 5%、服务完全不可用、磁盘将满
  • P1(30分钟内):P99 超阈值、队列堆积、连接池打满
  • P2(当天处理):容量水位(CPU/内存 > 80%)、证书即将过期

4.4 日志采集最佳实践

日志规范是日志平台能用的前提,强烈建议强制这几条:

  1. JSON 结构化输出:别用自由文本,统一 JSON,字段固定
  2. 必带 trace_id / span_id:与 Trace 关联的前提
  3. 分级打日志:ERROR 只打可恢复/不可恢复的异常,别把异常当正常流程
  4. 别打敏感信息:手机号、身份证、密码一律脱敏(或干脆不打)
  5. 日志级别可动态调整:线上排查时能把某个服务的日志临时调到 DEBUG

五、踩坑清单(真实经验)

坑1:Metrics 和 Tracing 的采样率不一致

现象 :告警说 QPS 3000,但 Trace 里同一时段只有 300 条。

原因 :Trace 开了 10% 采样,但没意识到 Metrics 是全额统计的。

解决:明确"Metrics 全额、Trace 采样"是正常设计。需要精确排查时临时把采样率调到 100%,查完再调回。

坑2:日志和 Trace 对不上

现象 :日志里有 trace_id,但在 Tempo 里查不到。

原因

  • 异步线程没传递 Context(线程池里的任务丢了 trace_id)
  • 用了消息队列,Consumer 侧没继续传递 traceparent 头
  • 采样导致:这条日志的 Trace 被采样掉了

解决

  • 线程池用 OTel 的 Context.taskWrapping 包装任务
  • MQ 消息头里透传 traceparent
  • 日志检索时对"有 trace_id 但查不到 Trace"的情况有预期

坑3:Collector 成为瓶颈

现象 :接入 OTel 后,应用 RT 没变,但 Collector 的 CPU 飙到 90%。

原因 :Collector 单点部署,所有信号都压在一个 Pod 上;batch 配置不当,每条数据都立刻转发。

解决

  • Collector 用 DaemonSet 部署(每个节点一个,各收各的)
  • 开 batch processor,5s 攒一批再发
  • memory_limiter 防止 OOM

坑4:日志量爆炸,存储扛不住

现象 :Loki 磁盘 3 个月满了,查询越来越慢。

原因 :DEBUG 日志全量采集;日志没分级;保留策略没配。

解决

  • 生产只采 INFO 以上,DEBUG 按需开
  • 按环境分流:dev/test 日志保留 7 天,生产保留 30~90 天
  • Loki 配 retention,ES 配 ILM 冷热分层

坑5:告警风暴

现象 :凌晨 3 点,钉钉被刷了 200 条告警,值班同学直接静音。

原因 :告警规则没配 for(持续时间),抖动一下也告警;没做聚合。

解决

  • 所有告警加 for: 5m,持续 5 分钟才触发
  • sum by (service_name) 聚合,别每条实例都发
  • 配 Alertmanager 的 group_wait / group_interval 抑制重复告警

六、渐进式落地路线

别想着一步到位,建议按这个节奏推进:

第一阶段(1~2周):指标先行

  • 部署 Prometheus + Grafana
  • 服务接入 OTel SDK,采集 HTTP 指标
  • 配置 5 条核心告警(错误率、延迟、CPU、内存、磁盘)
  • 产出:值班同学有"系统健康总览"了

第二阶段(2~4周):Trace 补上

  • 部署 Tempo(或 Jaeger)
  • 所有服务开启 Trace 导出,先开 10% 采样
  • 打通"从 Grafana 面板点进 Trace"的路径
  • 产出:慢请求能定位到具体服务了

第三阶段(1~2月):日志统一 + 三信号联动

  • 部署 Loki(或迁移 ELK)
  • 日志接入 OTel,注入 trace_id
  • 实现"指标 → 日志 → Trace"一键下钻
  • 产出:完整可观测性闭环

第四阶段(按需):深化

  • SLO 管理(错误预算)
  • 链路拓扑自动生成(服务依赖图)
  • 日志异常检测(AI 辅助,可选)

七、总结

一句话:Metrics 告诉你"哪里病了",Logging 告诉你"病根是什么",Tracing 告诉你"病是怎么传染的"。三者结合才是完整的可观测性。

落地要点回顾:

  1. 选型看生态:优先选能统一的栈(Grafana 系或 OTel 系),别三套系统割裂
  2. 标准看 OTel:一套 SDK、一套协议(OTLP)采三种信号,未来换后端不改应用
  3. 关联靠 trace_id:日志注入 trace_id,才能实现三信号联动下钻
  4. 告警要克制:for 时长 + 聚合 + 分级,别让告警风暴毁掉告警体系
  5. 落地要渐进:先指标、再链路、后日志,每一步都能立刻产生价值

可观测性建设是典型的"越早做越省":等系统出过两次"查了 3 小时没定位到根因"的事故,团队的决心就有了。与其等事故,不如现在就把三支柱搭起来------你不需要最好的工具,你需要的是"一套能用、能联动、查得到底"的体系

相关推荐
Jucai_in_AI2 小时前
企业培训场景下的个性化课程推荐系统设计与实现:混合推荐架构的工程实践
架构
caimouse2 小时前
ReactOS 图形系统分析(53):系统库存对象 — stockobj.c
c语言·开发语言·spring
代码方舟3 小时前
零信任架构实战:基于天远企业年报信息核验构建自动化高并发尽调网关
运维·人工智能·架构·自动化
ZJU_统一阿萨姆3 小时前
【推理优化进阶】通信关键路径:NCCL、RDMA 与计算通信重叠
开发语言·人工智能·语言模型·架构·系统架构
m0_587383003 小时前
智慧场馆解决方案实战指南:从系统架构到落地部署全解析
java·spring boot·架构·系统架构
我不是疯子是傻子3 小时前
Qt 程序打包部署全攻略:从 windeployqt 到 Inno Setup 的静默安装与版本迭代实战
开发语言·qt
ZYJCSZKJ4 小时前
AI数字人直播系统的技术架构与多方言交互实现方案——基于广西区域商业场景的工程实践
人工智能·架构·交互
Keystone_Onion4 小时前
跨境电商架构演进:基于美国海外仓的中大件分布式仓网路由优化方案
分布式·架构
城管不管4 小时前
重生——第十次面试之开源中国一面挂
java·linux·开发语言·算法·面试·职场和发展·开源