第 5 章 度量数据的聚合与展示#
本章你将学到
度量数据的聚合方式:时间聚合、空间聚合、层次聚合
时序数据库的选型:InfluxDB、Prometheus、TimescaleDB
数据清洗与降采样策略
Grafana 仪表盘设计的最佳实践
常用技术栈:TIG、ELKK、Prometheus、OpenTelemetry
开篇故事#
老板问:"上周我们的系统可用性是多少?" 我打开 Grafana,面对 50 个面板,不知道该看哪个。 有 CPU 使用率、有内存趋势、有网络流量…… 但就是没有一个面板直接回答"可用性是多少"这个问题。
那天我学到了一个教训:仪表盘不是越多越好, 而是要能回答业务关心的问题。
本章讲述如何聚合和存储度量数据,如何进行清洗和处理, 然后重点讲解度量的可视化技术和常用的技术栈。
5.1 度量数据的聚合和存储#
5.1.1 聚合方式#
度量数据的聚合方式:
时间聚合: 按时间窗口聚合 (1 分钟、5 分钟、1 小时)
空间聚合: 按维度聚合 (按服务、按实例、按区域)
层次聚合: 从底层到高层逐步汇总
原始数据 (每秒)
│
▼ 时间聚合
1 分钟粒度
│
▼ 时间聚合
5 分钟粒度
│
▼ 降采样
1 小时粒度
│
▼ 降采样
1 天粒度
5.1.2 时序数据库#
度量数据本质上是 时间序列数据 (Time Series Data), 推荐使用专门的时序数据库:
数据库 |
语言 |
特点 |
适用场景 |
|---|---|---|---|
InfluxDB |
Go |
类 SQL 查询、易用 |
中小规模度量 |
Prometheus |
Go |
Pull 模型、告警集成 |
Kubernetes 生态 |
TimescaleDB |
C (PostgreSQL 扩展) |
SQL 兼容、成熟 |
需要关系查询的场景 |
VictoriaMetrics |
Go |
高压缩、高性能 |
大规模度量存储 |
ClickHouse |
C++ |
列式存储、极高吞吐 |
分析型查询 |
5.1.3 数据保留策略#
保留策略示例:
高精度数据 (1s 粒度) → 保留 7 天
中精度数据 (1min 粒度) → 保留 30 天
低精度数据 (1h 粒度) → 保留 1 年
汇总数据 (1d 粒度) → 永久保留
5.2 度量数据的清洗和处理#
5.2.1 数据清洗#
原始度量数据可能包含噪声和异常,需要清洗:
去重: 去除重复的度量数据点
补值: 填充缺失的数据点 (线性插值、前值填充)
去噪: 使用移动平均或中位数滤波去除噪声
异常检测: 识别并标记异常数据点
5.2.2 数据处理#
常用的数据处理方法:
移动平均 (Moving Average): 平滑数据波动
指数加权移动平均 (EWMA): 近期数据权重更高
差分 (Derivative): 计算变化率
积分 (Integral): 计算累积量
百分位计算: 计算 P50, P90, P95, P99
Python (Pandas)
import pandas as pd
# 移动平均
df['latency_ma'] = df['latency'].rolling(window=5).mean()
# 指数加权移动平均
df['latency_ewma'] = df['latency'].ewm(span=5).mean()
# 百分位计算
p50 = df['latency'].quantile(0.50)
p90 = df['latency'].quantile(0.90)
p99 = df['latency'].quantile(0.99)
Go (实时流处理)
// Go 中使用滑动窗口计算 P99
type LatencyTracker struct {
mu sync.Mutex
window []float64
maxSize int
}
func (t *LatencyTracker) Record(latency float64) {
t.mu.Lock()
defer t.mu.Unlock()
t.window = append(t.window, latency)
if len(t.window) > t.maxSize {
t.window = t.window[1:]
}
}
func (t *LatencyTracker) Percentile(p float64) float64 {
t.mu.Lock()
sorted := make([]float64, len(t.window))
copy(sorted, t.window)
t.mu.Unlock()
sort.Float64s(sorted)
idx := int(float64(len(sorted)-1) * p)
return sorted[idx]
}
PromQL 查询示例
# 计算 P99 延迟
histogram_quantile(0.99, rate(http_request_duration_seconds_bucket[5m]))
# 计算错误率
sum(rate(http_requests_total{status=~"5.."}[5m]))
/ sum(rate(http_requests_total[5m]))
# 计算 QPS
sum(rate(http_requests_total[1m]))
5.3 度量数据的可视化#
5.3.1 常用图表类型#
图表类型 |
适用场景 |
示例 |
|---|---|---|
折线图 (Line Chart) |
趋势变化 |
CPU 使用率随时间的变化 |
柱状图 (Bar Chart) |
分类比较 |
各服务的错误数比较 |
面积图 (Area Chart) |
累积量变化 |
各状态码请求的堆叠分布 |
饼图 (Pie Chart) |
占比分布 |
错误类型分布 |
热力图 (Heatmap) |
二维分布 |
请求延迟的时间-频率分布 |
仪表盘 (Gauge) |
单一指标 |
当前 CPU 使用率 |
表格 (Table) |
精确数值 |
Top N 慢查询 |
5.3.2 仪表盘设计原则#
一个好的度量仪表盘应该:
一目了然: 关键指标一眼可见
层次清晰: 从总览到详情逐层钻取
实时更新: 数据自动刷新
可交互: 支持时间范围选择、维度筛选
告警集成: 异常指标高亮显示
┌─────────────────────────────────────────────────────┐
│ 系统总览 Dashboard │
├─────────────┬─────────────┬─────────────────────────┤
│ 可用性 │ 错误率 │ 平均响应时间 │
│ 99.95% │ 0.3% │ 125ms │
│ ●(绿) │ ●(绿) │ ●(绿) │
├─────────────┴─────────────┴─────────────────────────┤
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ 请求量趋势 (折线图) │ │
│ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │
│ └──────────────────────────────────────────────┘ │
│ │
│ ┌──────────────────┐ ┌────────────────────────┐ │
│ │ 延迟分布 (热力图) │ │ 错误类型分布 (饼图) │ │
│ │ │ │ │ │
│ └──────────────────┘ └────────────────────────┘ │
└─────────────────────────────────────────────────────┘
5.4 常用度量聚合与展示方案#
5.4.1 TIG 技术栈#
Telegraf + InfluxDB + Grafana
┌──────────┐ ┌───────────┐ ┌──────────┐
│ Telegraf │───>│ InfluxDB │───>│ Grafana │
│ (采集) │ │ (存储) │ │ (展示) │
└──────────┘ └───────────┘ └──────────┘
Telegraf 配置示例:
# telegraf.conf
[agent]
interval = "10s"
round_interval = true
# 输入: 系统指标
[[inputs.cpu]]
percpu = true
totalcpu = true
[[inputs.mem]]
[[inputs.disk]]
ignore_fs = ["tmpfs", "devtmpfs"]
# 输入: Docker 容器指标
[[inputs.docker]]
endpoint = "unix:///var/run/docker.sock"
# 输出: InfluxDB
[[outputs.influxdb]]
urls = ["http://influxdb:8086"]
database = "telegraf"
5.4.2 ELKK 技术栈#
Elasticsearch + Logstash + Kibana + Kafka
┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌──────────┐
│ Filebeat │───>│ Kafka │───>│ Logstash │───>│ Elastic │
│ (采集) │ │ (缓冲) │ │ (处理/转换) │ │ search │
└──────────┘ └──────────┘ └───────────────┘ └────┬─────┘
│
▼
┌──────────┐
│ Kibana │
│ (展示) │
└──────────┘
Logstash 配置示例:
# logstash.conf
input {
kafka {
bootstrap_servers => "kafka:9092"
topics => ["app-logs"]
codec => json
}
}
filter {
if [type] == "access_log" {
grok {
match => { "message" => "%{COMBINEDAPACHELOG}" }
}
date {
match => [ "timestamp", "dd/MMM/yyyy:HH:mm:ss Z" ]
}
mutate {
convert => { "response" => "integer" }
convert => { "bytes" => "integer" }
}
}
}
output {
elasticsearch {
hosts => ["elasticsearch:9200"]
index => "app-logs-%{+YYYY.MM.dd}"
}
}
5.4.3 Prometheus 技术栈#
Prometheus + Grafana + Alertmanager
┌──────────────────────────────┐
│ 微服务集群 │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │svc-1│ │svc-2│ │svc-3│ │
│ │/met │ │/met │ │/met │ │
│ └──┬──┘ └──┬──┘ └──┬──┘ │
└─────┼───────┼───────┼──────┘
│ │ │ Pull
▼ ▼ ▼
┌──────────────────────────┐
│ Prometheus │
│ (抓取、存储、查询) │
└──────┬──────────┬────────┘
│ │
▼ ▼
┌──────────┐ ┌──────────────┐
│ Grafana │ │ Alertmanager │
│ (展示) │ │ (告警) │
└──────────┘ └──────────────┘
Prometheus 配置示例:
# prometheus.yml
global:
scrape_interval: 15s
evaluation_interval: 15s
rule_files:
- "rules/*.yml"
alerting:
alertmanagers:
- static_configs:
- targets: ['alertmanager:9093']
scrape_configs:
- job_name: 'potato-server'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['potato-server:9003']
- job_name: 'potato-scheduler'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['potato-scheduler:9002']
5.4.4 OpenTelemetry (新增)#
OpenTelemetry 是 CNCF 的统一可观测性框架, 将 Metrics、Traces、Logs 三大信号统一收集:
┌──────────────────────────────────────┐
│ 应用程序 │
│ ┌──────────────────────────────┐ │
│ │ OpenTelemetry SDK │ │
│ │ ┌────────┬───────┬────────┐ │ │
│ │ │Metrics │Traces │ Logs │ │ │
│ │ └────────┴───────┴────────┘ │ │
│ └──────────────┬───────────────┘ │
└─────────────────┼────────────────────┘
│ OTLP
▼
┌──────────────────────────────────────┐
│ OTel Collector │
│ ┌─────────┬──────────┬───────────┐ │
│ │Receivers│Processors│ Exporters │ │
│ └─────────┴──────────┴───────────┘ │
└──────┬──────────┬──────────┬─────────┘
│ │ │
▼ ▼ ▼
┌──────────┐ ┌────────┐ ┌────────┐
│Prometheus│ │ Jaeger │ │ Loki │
│(Metrics) │ │(Traces)│ │ (Logs) │
└──────────┘ └────────┘ └────────┘
5.5 土豆微服务的度量聚合与展示#
在土豆微服务中,我们使用以下方案:
系统指标: Telegraf 采集 → InfluxDB 存储 → Grafana 展示
应用日志: Filebeat 采集 → Kafka 缓冲 → Logstash 处理 → Elasticsearch 存储 → Kibana 展示
业务度量: Micrometer → InfluxDB → Grafana
# docker-compose.yml (度量相关组件)
services:
influxdb:
image: influxdb:2.7
ports:
- "8086:8086"
volumes:
- influxdb-data:/var/lib/influxdb2
grafana:
image: grafana/grafana:10.0
ports:
- "3000:3000"
depends_on:
- influxdb
telegraf:
image: telegraf:1.27
volumes:
- ./telegraf.conf:/etc/telegraf/telegraf.conf
depends_on:
- influxdb
5.5.1 AI 应用的度量聚合特殊考量#
AI 应用的度量数据与传统微服务有几个显著差异,需要特殊处理:
1. 高基数标签问题
LLM 应用中,Prompt 内容、模型版本、用户 ID 等维度组合可能产生极高的基数, 导致时序数据库存储爆炸。
❌ 错误: llm_request{prompt="帮我写一篇关于..."} — 每个 Prompt 一个时间序列
✅ 正确: llm_request{model="gpt-4o", task_type="writing"} — 按任务类型聚合
2. 评估数据的异步采集
LLM 输出质量评估(Faithfulness、Relevancy 等)通常需要异步计算, 不能在请求路径上同步完成:
请求路径 (同步): 延迟、Token 数、错误码 → Prometheus
评估路径 (异步): 采样请求 → 评估队列 → LLM-as-Judge → 评估结果 → Prometheus
3. 成本度量的多维聚合
LLM 成本需要按模型、用户、功能、时间等多个维度聚合:
# Grafana 面板: LLM 成本分析
# 按模型的日成本
sum by (model) (increase(llm_cost_dollars_total[24h]))
# 按功能的月成本
sum by (task_type) (increase(llm_cost_dollars_total[30d]))
# 单次请求平均成本
rate(llm_cost_dollars_total[1h]) / rate(llm_requests_total[1h])
5.6 本章小结#
本章介绍了度量数据从采集到展示的完整流程:
度量数据的聚合方式和时序数据库选型
数据清洗和处理的常用方法
度量可视化的图表类型和仪表盘设计原则
四种主流的度量技术栈: TIG、ELKK、Prometheus、OpenTelemetry
备注
关键要点:
时序数据库是度量数据存储的首选
使用百分位数而非均值来展示延迟
仪表盘应该层次清晰、一目了然
OpenTelemetry 正在成为可观测性的统一标准
💡 避坑指南:仪表盘设计的 3 个原则
原则 1:每个面板回答一个问题
不要把所有指标堆在一个图上。每个面板应该能回答一个明确的问题, 比如"当前错误率是多少?"或"过去 24 小时的延迟趋势如何?"
原则 2:从上到下,从概览到细节
面板顶部放 Stat 面板(关键数字),中间放趋势图,底部放详细表格。 让人一眼就能看到系统的整体状态。
原则 3:颜色有含义
绿色 = 正常,黄色 = 警告,红色 = 严重。不要随意使用颜色。 设置阈值让面板自动变色,比手动检查数值更高效。
🔬 动手实验:搭建 Prometheus + Grafana 监控栈
目标:用 Docker Compose 在 10 分钟内搭建一个完整的监控栈。
步骤:
创建
docker-compose.yml,包含 Prometheus + Grafana + Node Exporter配置 Prometheus 抓取 Node Exporter 指标
在 Grafana 中导入 Node Exporter 面板 (Dashboard ID: 1860)
观察 CPU、内存、磁盘、网络的实时数据
完整配置:见 GitHub 仓库 examples/ch5-monitoring-stack/
📝 思考题
你的团队目前使用什么监控技术栈?它的优缺点是什么?
如果让你设计一个"一屏看全局"的仪表盘,你会放哪 5 个面板?
度量数据应该保留多久?保留策略如何平衡存储成本和分析需求?