引言
可观测性是一笔"沉默的预算":没人会为它开发票,但每个季度它在账单上悄然膨胀。一套"标准"的微服务可观测性栈——Prometheus 指标 + 集中式日志 + 全链路 Tracing + 云端 APM——在每天千万级请求的系统上,年成本很容易达到数百万美元量级。日志是最贵的(体积最大、保存最久)、Tracing 次之(高基数 span 存储),指标反而相对便宜,但高基数标签同样会让成本失控。
成本治理的目标不是"省到看不见",而是在保持 SRE 排障能力的前提下,把无效数据挡在门外,把长尾数据放进便宜的地方。可观测性三大支柱的基线实践见 https://plumephp.com/observability-logging-metrics-tracing/;链路追踪与 OpenTelemetry 的具体集成见 。本文将聚焦成本维度:采样、降噪、生命周期、存储优化与告警收敛。
目录
- 1. 可观测性成本构成
- 2. 指标成本治理:基数控制与聚合
- 3. 日志降噪与分级
- 4. 链路追踪采样策略
- 5. 数据生命周期管理
- 6. 存储成本优化
- 7. 告警收敛
- 8. 预算管理与成本归因
- 9. 总结:降本路线图
- 延伸阅读
1. 可观测性成本构成
1.1 成本大头分布
| 数据类 | 相对体积 | 单位成本 | 成本占比(典型) | 主要驱动 |
|---|---|---|---|---|
| 日志 | 最大 | 中 | 40-60% | 全量采集 + 长期保留 |
| Tracing | 中 | 高 | 20-30% | span 数量 × 高基数标签 |
| Metrics | 最小 | 中 | 10-20% | 时间序列数量 × 基数 |
| 其他(告警、仪表盘、导出) | 小 | 低 | 5-10% | 调用次数 |
1.2 成本公式
成本 ≈ Σ(数据量 × 存储单价 × 保留时长) + Σ(查询/导出 × 调用单价) + 高基数惩罚
其中高基数是隐藏炸弹:user_id、request_id、pod_ip 一旦作为标签进入指标,时间序列数量会呈组合爆炸。
1.3 治理优先级
降本行动按"投入产出比"排序:
- Tracing 采样(投入小,见效快,可省 50-90% span 量)
- 日志分级与丢弃(规则简单,可省 30-60% 日志量)
- 指标基数治理(需要审计,但能根治高基数问题)
- 生命周期与冷热分层(长期收益,架构调整)
2. 指标成本治理:基数控制与聚合
2.1 高基数的代价
一个标签 user_id 有 100 万取值,则该指标瞬间变成 100 万条时间序列:
# ❌ 反模式:请求耗时按 user_id 分桶 → 百万序列
http_request_duration_ms{user_id="12345"} 0.1
http_request_duration_ms{user_id="67890"} 0.2
# 100 万用户 = 100 万条时间序列 = 存储与查询爆炸
2.2 标签治理规范
| 标签 | 是否适合做指标标签 | 替代方案 |
|---|---|---|
endpoint、method、status | ✅ 低基数 | — |
deployment、version | ✅ 低基数(可控) | — |
tenant_id(数百个) | ⚠️ 中基数(有上限) | 按需开启,单独 metric |
user_id、order_id | ❌ 高基数 | 放到日志/追踪的 attribute |
request_id | ❌ 极高基数 | 只进日志与 trace |
pod_ip | ⚠️ 变化快 | 用 pod 标签替代 |
2.3 高基数指标降维
把高基数诉求从 Metrics 迁移到 Logs/Traces 的 attribute 中,指标只保留低基数维度:
// ❌ 不推荐:把 user_id 写入指标标签
histogram.WithLabelValues(userID, endpoint).Observe(dur)
// ✅ 推荐:指标只保留低基数维度
// 用户维度的耗时分析交给 tracing(span attribute 携带 user_id)
histogram.WithLabelValues(endpoint).Observe(dur)
2.4 聚合与降采样
Prometheus 的 Recording Rule 把高频原始数据聚合为低频预计算指标:
# prometheus rules:原始数据保留 15 天,预聚合保留 1 年
groups:
- name: service_aggregation
interval: 1m
rules:
- record: job:http_requests:rate5m
expr: sum(rate(http_requests_total[5m])) by (job)
- record: job:http_request_duration:p95_5m
expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le, job))
3. 日志降噪与分级
3.1 日志成本 = 体积 × 保留 × 处理
一条 1KB 的 INFO 日志,如果一天 1 亿条,一天就是 100GB。大多数日志从未被读取——治理的第一步是别让无价值的日志进入昂贵的存储。
3.2 分级丢弃规则
| 日志级别 | 默认策略 | 说明 |
|---|---|---|
| DEBUG | 直接丢弃(除非显式开启) | 生产环境几乎不需要 |
| INFO | 按业务规则裁剪 | 保留低频业务事件,丢弃高频心跳 |
| WARN | 全部保留(可聚合) | 可疑但不致命 |
| ERROR | 全部保留 + 告警 | 最高价值 |
# vector / fluent-bit 处理管道示例
transforms:
filter_low_value:
type: filter
inputs: [logs]
condition:
type: or
conditions:
- type: level_selector
level: warn
operator: gte
- type: message_selector # 业务白名单,如订单完成事件
match: "^order_(created|paid|shipped):"
3.3 结构化 + 丢弃低价值字段
# 丢弃高体积低价值的字段
remap:
drop_fields:
- "user_agent" # 高基数且可重建
- "request_headers"
- "debug_stack"
convert:
- from: "duration_ns"
to: "duration_ms"
type: float
3.4 日志抽稀(Sampling)
高频日志按概率或速率采样:
transforms:
sample_healthcheck:
type: sampler
inputs: [logs]
rate: 0.01 # 只保留 1%
key_field: "log_key" # 按 log_key 保持一致性(同 key 同采样决策)
原则:同一次业务请求的多条日志要么全保留要么全丢弃(按 trace_id 做 key 采样),避免只看到半个请求。
4. 链路追踪采样策略
4.1 为什么必须采样
全量 Tracing 意味着每个请求产生几十~几百个 span。千万级 QPS 的系统中,全量 Tracing 的 span 量比全量日志还惊人。而绝大多数 span 的黄金价值在于"异常时有完整链路"。
4.2 Head-based 采样(基于头采样)
在入口处按请求决策是否采样,所有下游共享决策:
// OpenTelemetry 按比例采样
tp := sdktrace.NewTracerProvider(
sdktrace.WithSampler(
sdktrace.ParentBased(
sdktrace.TraceIDRatioBased(0.1), // 10% 采样率
),
),
)
4.3 Tail-based 采样(基于尾采样)
Tail-based sampling 是成本治理的进阶方案:先全量接收 span,由采样器在尾部统一决策——只保留错误链路 + 慢链路 + 代表性样本,其余丢弃:
请求 ──▶ Collector(全量接收) ──▶ Tail Sampling 决策 ──▶ 保留/丢弃
│
├─ 有 error span → 保留
├─ 延迟 > P99 阈值 → 保留
└─ 正常链路 → 按 1% 保留
# OpenTelemetry Collector tail sampling 配置
processors:
tail_sampling:
decision_wait: 10s # 缓冲窗口,等待整条 trace 到达
policies:
- name: errors
type: status_code
status_code: { status_codes: [ERROR] }
- name: slow
type: latency
latency: { threshold_ms: 500 }
- name: random-sample
type: probabilistic
probabilistic: { sampling_percentage: 1 }
4.4 三种采样策略对比
| 策略 | 实现复杂度 | 错误链路覆盖率 | 成本节省 |
|---|---|---|---|
| 固定比例 Head | 低 | 与比例相同 | 中等 |
| 动态比例 Head | 低 | 受比例限制 | 中等 |
| Tail-based | 高 | 100%(错误全保留) | 最高 |
4.5 采样率建议基线
| 服务类型 | 建议采样率 | 备注 |
|---|---|---|
| 核心支付/交易 | 100%(全量) | 业务可接受成本 |
| 普通业务服务 | 10-25% | Head + 错误全保 |
| 高频只读服务 | 1-5% | 加 Tail-based 兜底错误 |
| 批处理/后台任务 | 1% | 以错误为主 |
5. 数据生命周期管理
5.1 生命周期阶段
热数据(可交互查询) → 温数据(低延迟检索) → 冷数据(归档对象存储) → 过期删除
| 阶段 | 典型保留 | 存储介质 | 查询能力 |
|---|---|---|---|
| 热 | 0-7 天 | SSD / 热存储 | 秒级交互查询 |
| 温 | 7-90 天 | 标准存储 | 分钟级查询 |
| 冷 | 90 天-1 年 | 对象存储(压缩) | 按需解冻 |
| 过期 | 超期 | — | 删除 |
5.2 ClickHouse TTL 分层示例
-- ClickHouse 表级 TTL:热→温→冷→删
CREATE TABLE app_logs (
ts DateTime64(3),
level LowCardinality(String),
trace_id String,
service LowCardinality(String),
message String
)
ENGINE = MergeTree
PARTITION BY toYYYYMM(ts)
ORDER BY (service, ts)
TTL ts + INTERVAL 7 DAY TO VOLUME 'hot',
ts + INTERVAL 90 DAY TO VOLUME 'warm',
ts + INTERVAL 365 DAY DELETE;
5.3 索引与检索字段收窄
日志检索若对每个字段建索引,成本成倍增长。只对关键字段建索引:
| 字段 | 是否索引 | 理由 |
|---|---|---|
trace_id | ✅ | 排障核心 |
service | ✅ | 常用过滤 |
level | ✅ | 常用过滤 |
message | ⚠️ 全文检索需按需 | 体积大、索引贵 |
payload.* | ❌ | 按需提取 |
6. 存储成本优化
6.1 压缩比
集中式日志存储(ELK / Loki / ClickHouse)都有压缩能力,但配置不同差距巨大:
| 存储方案 | 默认压缩 | 说明 |
|---|---|---|
| Elasticsearch | best_compression(需开启) | 索引压缩与 doc_values 取舍 |
| Loki | 块级压缩(gzip/snappy) | 按 chunk 压缩,省大量体积 |
| ClickHouse | LZ4 / ZSTD | ZSTD 压缩比高、CPU 略高 |
6.2 Loki 配置优化
chunk_store_config:
chunk_target_size: 1536000 # 增大块,提升压缩比
chunk_encoding: gzip # gzip 压缩比优于 snappy
compactor:
compaction_interval: 10m
retention_enabled: true
retention_period: 168h # 7 天热数据
6.3 高基数 label 治理(Prometheus)
# Prometheus 丢弃高基数 label(当心:会影响查询,需谨慎)
scrape_configs:
- job_name: app
metric_relabel_configs:
- source_labels: [user_id]
regex: ".*"
action: drop
6.4 对象存储归档
超过 90 天的数据从查询引擎迁到对象存储(S3/OSS),体积成本降低约 10 倍:
ClickHouse → S3 冷数据表(可随时 ATTACH 查询)
Elasticsearch → snapshot to S3(按需恢复)
Loki → 块存储生命周期自动归档
7. 告警收敛
7.1 告警疲劳的成本
告警不只是"噪音"问题,背后是真金白银:每条告警触发通知 → 值班人起来 → 误报排查 → 时间成本与信任损耗。告警收敛能直接降低运维人力成本,且减少高频率告警对后端(通知渠道、报警系统)的调用开销。
7.2 收敛手段
| 手段 | 做法 | 效果 |
|---|---|---|
| 去重 | 相同告警 N 分钟只发一次 | 抑制风暴 |
| 聚合 | 多个实例同类型告警合并为一条 | 信息密度提升 |
| 抑制 | 根因告警出现时抑制衍生告警 | 聚焦根因 |
| 静默 | 维护窗口自动静默 | 避免夜间误报 |
| 分级 | 按影响分级(P1/P2/P3) | 按级别路由 |
7.3 Alertmanager 聚合示例
# Prometheus Alertmanager
route:
group_by: ['alertname', 'cluster'] # 按告警名+集群聚合
group_wait: 30s
group_interval: 5m
repeat_interval: 4h
receiver: ops-critical
routes:
- match:
severity: critical
receiver: oncall-page
- match:
severity: warning
receiver: oncall-slack
inhibit_rules:
- source_match: { severity: critical }
target_match: { severity: warning }
equal: ['cluster'] # 同一集群 critical 抑制 warning
7.4 告警质量指标
告警准确率 = 真实告警 / 全部告警 (目标 > 80%)
重复告警占比 = 重复触发的告警 / 全部 (目标 < 20%)
MTTA(平均响应时间)、MTTR(平均恢复时间) (验证收敛效果)
关于 SLI/SLO 与错误预算驱动的告警设计,见 https://plumephp.com/service-level-objectives-slo-guide/。
8. 预算管理与成本归因
8.1 成本归因维度
按团队(team) 按服务(service) 按环境(prod/staging)
按数据类(logs/traces/metrics) 按保留期
统一给数据打上标签(如 team=payments),成本账单按标签分摊:
// OpenTelemetry resource 标签
res := resource.NewWithAttributes(
semconv.SchemaURL,
attribute.String("service.name", "payment-service"),
attribute.String("team", "payments"),
attribute.String("env", "prod"),
)
8.2 预算看板与告警
可观测性成本月报:
- 总成本 vs 上月:+12%(超预算 → 触发治理)
- 按数据类 Top 3:logs 62%, traces 25%, metrics 13%
- 按团队 Top 3:payments 40%, order 30%, search 15%
- 单序列成本:$0.003/月 → 百万序列 = $3000/月
8.3 成本治理委员会节奏
| 频率 | 动作 |
|---|---|
| 每周 | 追踪成本看板,检查异常突增(如采样率被误改) |
| 每月 | 审查采样率与保留期,按业务调整 |
| 每季度 | 清理废弃指标/仪表盘/告警规则,做基线上限设定 |
| 每半年 | 架构级评审(是否引入更便宜的存储、是否关闭高成本 APM) |
9. 总结:降本路线图
9.1 三阶段路线图
第一阶段(第 1-2 周):快速止血
开启 Tracing 采样(10%)+ 日志丢弃 DEBUG + 告警去重聚合
预期:成本 -30~40%
第二阶段(第 1-3 月):结构治理
指标基数审计与降维 + 日志分级规则 + 生命周期 TTL 分层
预期:累计 -50~60%
第三阶段(持续):架构演进
Tail-based sampling + 冷数据归档 + 成本归因看板 + 委员会节奏
预期:累计 -70% 且能力不降
9.2 不可触碰的底线
| 红线 | 说明 |
|---|---|
| 错误链路不降采样 | Tail-based 保证 100% 错误 span 保留 |
| 核心支付全量 | 高风险业务不做比例采样 |
| 合规保留不可删 | 审计、合规要求的数据不因省钱删除 |
| 排障链路不破坏 | 降噪后必须仍能通过 trace_id 关联全链路 |
9.3 治理前后的能力对比
| 维度 | 治理前 | 治理后 |
|---|---|---|
| 月日志量 | 100TB | 30TB(分级+采样) |
| 采样率 | 100%(全量) | 核心 100%,普通 10%,错误兜底 100% |
| 保留策略 | 一律 1 年 | 热 7 天 / 温 90 天 / 冷 1 年归档 |
| 告警量 | 2000/周 | 200/周(聚合收敛) |
| 月成本 | 100% 基线 | 30-40% |
可观测性成本治理的本质是用"数据价值"而非"数据量"来决定采集与存储策略。三大支柱的采集基线不变,但每一份进入昂贵存储的数据,都要回答"这个数据是否值得"这一问题。最终目标是在 https://plumephp.com/service-level-objectives-slo-guide/ 定义的可用性目标下,用最小的成本维持最强的排障能力。
延伸阅读
- OpenTelemetry Sampling 官方文档
- Tail Sampling Processor(Collector)
- Prometheus Recording Rules
- Grafana Loki 存储与保留
- Honeycomb 关于高基数与采样的实践
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。