从零构建可观测性体系:Metrics、Logs、Traces 三剑合一的工程落地与 Prometheus 高基数陷阱
从零构建可观测性体系Metrics、Logs、Traces 三剑合一的工程落地与 Prometheus 高基数陷阱一、三大支柱的各自盲区为什么单一信号永远不够可观测性领域有一条基本认知Metrics 告诉你系统有没有问题Traces 告诉你问题发生在哪里Logs 告诉你问题是什么。这三者在现实中的协同远非字面上那般丝滑。Metrics 的盲区在于高基数数据。Prometheus 的时序数据库设计天然不适合高基数标签——当标签组合数超过 100 万时内存占用呈指数增长。例如为每个 HTTP 请求加user_id作为标签一个 100 万 DAU 的应用瞬间产生 100 万个时间序列Prometheus 实例的内存从 8GB 飙升至 60GB。Prometheus 官方建议将标签基数限制在 10 个以内——这意味着你只能看到10% 的错误率却无法知道是哪些用户遇到了错误。Traces 的盲区在于采样率。Jaeger 等分布式追踪系统默认采样率 1%每 100 个请求只追踪 1 个。当错误率在 0.5% 时1% 采样下每 10 分钟才能抓到约 30 条错误 Trace这对于根因定位完全不够。高频采样 10%又会导致追踪数据量暴增——每秒 10k QPS、100% 采样的系统一天产生 864M 条 Span存储成本可能高达每月 5 万美元。Logs 的盲区在于结构化查询的成本。ELKElasticsearch Logstash Kibana生态中一条 JSON 结构化日志可以被 ES 索引的字段有限。当开发者在日志中添加了 30 个自定义字段时ES 的映射Mapping爆炸导致查询性能急剧下降。更糟糕的是非结构化日志的自由文本搜索在数据量超过 1TB/天时Kibana 的查询延迟可能达到 30-60 秒排障效率严重受损。二、Prometheus 高基数问题的工程解法Recording Rules 与 Aggregation Gateways解决高基数问题的核心思路是在数据进入 Prometheus 之前完成聚合。Aggregation Gateway如 Grafana Agent 的 Aggregation 模式是关键的中间件层——它接收来自各服务的原始 Metrics 数据在写入 Prometheus 前按粗粒度维度如service_namemethod做预聚合。原始数据中的user_id等细粒度标签在聚合层被剥离仅保留聚合后的分量值如 P50、P95、P99 分位值。这样 Prometheus 收到的始终是低基数数据内存和查询延迟都不会爆炸。另一个方案是使用 VictoriaMetrics 替代 Prometheus 做长周期存储。VictoriaMetrics 对高基数数据的处理效率比 Prometheus 高 5-10 倍——它使用倒排索引inverted index而非 Prometheus 的标签索引在高基数场景下内存占用更低。但 VictoriaMetrics 的查询语言MetricsQL与 PromQL 存在一定差异迁移时需要适配 Grafana Dashboard 的查询表达式。对于必须保留高基数数据的场景如按用户维度的业务监控推荐使用 ClickHouse 做冷数据存储。通过 Prometheus 的 remote_write 将全量数据写入 ClickHouse同时 Prometheus 只保留最近 7 天的低基数聚合数据。当需要排查特定用户的故障时在 ClickHouse 中按user_id查询——ClickHouse 的列式存储对这个场景的查询效率远高于 Prometheus TSDB。三、分布式追踪的采样策略尾部采样与强制采样采样是分布式追踪架构中最核心的工程决策。不同的采样策略对可观测性的影响差异巨大头部采样Head-based Sampling在请求入口处根据固定概率如 1%决定是否追踪。实现简单、网络开销小但致命弱点是与错误数据无关——无论错误率多少只有 1% 的错误请求能被追踪到。这是 Jaeger 的默认策略也是用户抱怨在 Jaeger 中找不到我的错误 Trace的根因。尾部采样Tail-based Sampling所有请求都记录 Span但不立即上报。请求完成后根据决策规则如错误码 5xx、延迟 500ms决定是否将 Span 提交到后端。这保证了所有异常请求 100% 被追踪但代价是所有 Span 需要在 Agent 端缓存直到请求结束——高 QPS 下缓存数据量巨大。OpenTelemetry Collector 的 Tail Sampling Processor 可以在内存中维护一个滑动窗口结合 Redis 做状态存储跨 Agent 共享决策信息。强制采样Force Sampling在请求中注入标识如 HTTP HeaderX-Force-Trace: true让所有经过的服务强制采样该请求。这在预发环境和内部测试中非常有用——测试人员可以 100% 看到自己发起请求的完整调用链。// 自适应的分布式追踪采样策略 package tracing // AdaptiveSampler 根据服务延迟和错误率动态调整采样率 // 正常状态1% 采样 → 降低追踪系统开销 // 异常状态50% 采样 → 快速捕获根因 type AdaptiveSampler struct { normalRate float64 // 正常采样率 0.01 (1%) degradedRate float64 // 降级采样率 0.50 (50%) errorThreshold float64 // 错误率阈值超过即认为异常 latencyThreshold time.Duration // P99 延迟阈值 } // Sample 决定当前请求是否采样 // 优先级错误请求 慢请求 按概率采样 func (s *AdaptiveSampler) Sample(ctx context.Context, statusCode int, latency time.Duration) bool { // 优先级 1错误请求 100% 采样 if statusCode 500 { return true } // 优先级 2异常慢请求 100% 采样 if latency s.latencyThreshold { return true } // 优先级 3根据当前系统状态选择采样率 rate : s.normalRate if s.currentErrorRate() s.errorThreshold { rate s.degradedRate // 系统异常提高采样率 } return rand.Float64() rate }四、可观测性平台的成本优化存储、索引与查询的分层架构对可观测性平台的真实 TCO 做分析存储成本通常占平台的 60%-70%。以下是经过多次迭代验证的成本优化策略日志的分层存储。将日志按热、温、冷三层划分热数据最近 3 天存在 Elasticsearch 上支持 Kibana 的全文搜索和字段聚合分析。温数据3-30 天迁移到 ClickHouse仅支持结构化字段查询日志原文压缩存储。冷数据30 天以上归档到对象存储S3/COS仅按时间范围检索。三层存储将日志系统的月成本从 $5000 降至 $1500冷数据的查询延迟从 5 秒增加到 30 秒但这对于一个月前的日志排查是可以接受的。Metrics 的降采样。Prometheus 存储的每个数据点都有独立的时间戳7 天内的每 15 秒一个点40320 个点/系列。存储 30 天需要 172,800 个点数据量增长 4.3 倍。通过 recording rules 在 7 天后对数据做降采样——聚合为 5 分钟粒度从 40320 点降至 2016 点再存储额外的 23 天。这牺牲了细粒度的历史数据分析能力但对长期趋势分析几乎没有影响。Trace 的按需存储。默认所有 Trace 保存 7 天但如果 90% 的 Trace 工作是在故障排查事后 1 小时内中使用的那么存储 7 天的浪费极大。采用 Jaeger 的 archive 机制热数据的 Trace 保存在内存/Cassandra 中 1 小时温数据在 Elasticsearch 中保存 24 小时之后自动归档到 S3。用户查询超过 24 小时的 Trace 时触发 S3 恢复——增加 2-5 分钟的恢复延迟但存储成本降低 85%。五、总结可观测性体系的建设是一个持续优化和权衡的过程。Metrics、Logs、Traces 三者各有所长但任何单一信号的缺失都将导致可观测能力的空白。在实践中需要根据问题类型选择查询入口CPU/内存问题 → Metrics → Traces 定位具体请求 → Logs 查看详细错误功能 Bug → Logs 定位错误时间线 → Traces 还原请求路径 → Metrics 确认影响范围。Prometheus 的高基数限制是工程中绕不开的约束。通过 Aggregation Gateway 在写入前剥离高基数标签结合 ClickHouse 做细粒度长周期存储既能保证核心指标的查询效率又不丢失排障所需的多维度分析能力。这个架构的额外维护成本Aggregation Gateway 和 ClickHouse 双 SQL 查询是值得的——它避免了 Prometheus OOM 级别的生产事故。分布式追踪的采样策略决定了排障效率。Tail-based Sampling 是技术上最优的方案——100% 捕获异常请求。但它的工程复杂度Agent 端 Span 缓存、跨服务上下文传递意味着不是所有团队都能立即采用。一个务实的渐进式策略Head Sampling 1% 维持全局视图 Force Sampling 内部测试 100% 覆盖 Error/Slow 请求通过中间件做 100% 强制 Sampling——在复杂度和覆盖率之间取得了最佳平衡。