.. _chapter5: ============================== 第 5 章 度量数据的聚合与展示 ============================== 本章讲述如何聚合和存储度量数据,如何进行清洗和处理, 然后重点讲解度量的可视化技术和常用的技术栈。 5.1 度量数据的聚合和存储 ======================== 5.1.1 聚合方式 -------------- 度量数据的聚合方式: - **时间聚合**: 按时间窗口聚合 (1 分钟、5 分钟、1 小时) - **空间聚合**: 按维度聚合 (按服务、按实例、按区域) - **层次聚合**: 从底层到高层逐步汇总 .. code-block:: text 原始数据 (每秒) │ ▼ 时间聚合 1 分钟粒度 │ ▼ 时间聚合 5 分钟粒度 │ ▼ 降采样 1 小时粒度 │ ▼ 降采样 1 天粒度 5.1.2 时序数据库 ---------------- 度量数据本质上是 **时间序列数据 (Time Series Data)**, 推荐使用专门的时序数据库: .. list-table:: 时序数据库对比 :header-rows: 1 :widths: 20 20 30 30 * - 数据库 - 语言 - 特点 - 适用场景 * - InfluxDB - Go - 类 SQL 查询、易用 - 中小规模度量 * - Prometheus - Go - Pull 模型、告警集成 - Kubernetes 生态 * - TimescaleDB - C (PostgreSQL 扩展) - SQL 兼容、成熟 - 需要关系查询的场景 * - VictoriaMetrics - Go - 高压缩、高性能 - 大规模度量存储 * - ClickHouse - C++ - 列式存储、极高吞吐 - 分析型查询 5.1.3 数据保留策略 ------------------ .. code-block:: text 保留策略示例: 高精度数据 (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)** .. code-block:: python 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 (实时流处理)** .. code-block:: 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 查询示例** .. code-block:: text # 计算 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 常用图表类型 ------------------ .. list-table:: 度量可视化图表 :header-rows: 1 :widths: 20 35 45 * - 图表类型 - 适用场景 - 示例 * - 折线图 (Line Chart) - 趋势变化 - CPU 使用率随时间的变化 * - 柱状图 (Bar Chart) - 分类比较 - 各服务的错误数比较 * - 面积图 (Area Chart) - 累积量变化 - 各状态码请求的堆叠分布 * - 饼图 (Pie Chart) - 占比分布 - 错误类型分布 * - 热力图 (Heatmap) - 二维分布 - 请求延迟的时间-频率分布 * - 仪表盘 (Gauge) - 单一指标 - 当前 CPU 使用率 * - 表格 (Table) - 精确数值 - Top N 慢查询 5.3.2 仪表盘设计原则 -------------------- 一个好的度量仪表盘应该: 1. **一目了然**: 关键指标一眼可见 2. **层次清晰**: 从总览到详情逐层钻取 3. **实时更新**: 数据自动刷新 4. **可交互**: 支持时间范围选择、维度筛选 5. **告警集成**: 异常指标高亮显示 .. code-block:: text ┌─────────────────────────────────────────────────────┐ │ 系统总览 Dashboard │ ├─────────────┬─────────────┬─────────────────────────┤ │ 可用性 │ 错误率 │ 平均响应时间 │ │ 99.95% │ 0.3% │ 125ms │ │ ●(绿) │ ●(绿) │ ●(绿) │ ├─────────────┴─────────────┴─────────────────────────┤ │ │ │ ┌──────────────────────────────────────────────┐ │ │ │ 请求量趋势 (折线图) │ │ │ │ ~~~~~~~~~~~~~~~~~~~~~~~~ │ │ │ └──────────────────────────────────────────────┘ │ │ │ │ ┌──────────────────┐ ┌────────────────────────┐ │ │ │ 延迟分布 (热力图) │ │ 错误类型分布 (饼图) │ │ │ │ │ │ │ │ │ └──────────────────┘ └────────────────────────┘ │ └─────────────────────────────────────────────────────┘ 5.4 常用度量聚合与展示方案 ========================== 5.4.1 TIG 技术栈 ----------------- **Telegraf + InfluxDB + Grafana** .. code-block:: text ┌──────────┐ ┌───────────┐ ┌──────────┐ │ Telegraf │───>│ InfluxDB │───>│ Grafana │ │ (采集) │ │ (存储) │ │ (展示) │ └──────────┘ └───────────┘ └──────────┘ **Telegraf 配置示例:** .. code-block:: toml # 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** .. code-block:: text ┌──────────┐ ┌──────────┐ ┌───────────────┐ ┌──────────┐ │ Filebeat │───>│ Kafka │───>│ Logstash │───>│ Elastic │ │ (采集) │ │ (缓冲) │ │ (处理/转换) │ │ search │ └──────────┘ └──────────┘ └───────────────┘ └────┬─────┘ │ ▼ ┌──────────┐ │ Kibana │ │ (展示) │ └──────────┘ **Logstash 配置示例:** .. code-block:: ruby # 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** .. code-block:: text ┌──────────────────────────────┐ │ 微服务集群 │ │ ┌─────┐ ┌─────┐ ┌─────┐ │ │ │svc-1│ │svc-2│ │svc-3│ │ │ │/met │ │/met │ │/met │ │ │ └──┬──┘ └──┬──┘ └──┬──┘ │ └─────┼───────┼───────┼──────┘ │ │ │ Pull ▼ ▼ ▼ ┌──────────────────────────┐ │ Prometheus │ │ (抓取、存储、查询) │ └──────┬──────────┬────────┘ │ │ ▼ ▼ ┌──────────┐ ┌──────────────┐ │ Grafana │ │ Alertmanager │ │ (展示) │ │ (告警) │ └──────────┘ └──────────────┘ **Prometheus 配置示例:** .. code-block:: yaml # 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 三大信号统一收集: .. code-block:: text ┌──────────────────────────────────────┐ │ 应用程序 │ │ ┌──────────────────────────────┐ │ │ │ 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 .. code-block:: yaml # 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.6 本章小结 ============ 本章介绍了度量数据从采集到展示的完整流程: - 度量数据的聚合方式和时序数据库选型 - 数据清洗和处理的常用方法 - 度量可视化的图表类型和仪表盘设计原则 - 四种主流的度量技术栈: TIG、ELKK、Prometheus、OpenTelemetry .. note:: **关键要点:** - 时序数据库是度量数据存储的首选 - 使用百分位数而非均值来展示延迟 - 仪表盘应该层次清晰、一目了然 - OpenTelemetry 正在成为可观测性的统一标准