揭秘云原生混布资源调度器Koordinator (六)MetricCache 指标缓存机制

核心使命与设计理念

6.1 What - MetricCache 是什么?

MetricCache 是 Koordlet 中的时间序列数据库(TSDB),用于存储和查询历史指标数据。

核心职责

  1. 接收来自 MetricAdvisor 的实时指标
  2. 高效存储指标数据(内存 + 磁盘)
  3. 支持快速范围查询(时间范围、聚合函数)
  4. 支持复杂的时间序列分析操作
  5. 自动清理过期数据,维护存储空间

MetricCache 的数据视图

css 复制代码
┌─────────────────────────────────────────────────┐
│        MetricCache 中的数据组织          │
├─────────────────────────────────────────────────┤
│                                                 │
│ 存储维度:                                      │
│ ├─ Pod 级指标                                   │
│ │  ├─ Pod UUID -> [CPU 时间序列]               │
│ │  ├─ Pod UUID -> [Memory 时间序列]            │
│ │  └─ Pod UUID -> [Network 时间序列]           │
│ │                                              │
│ ├─ Node 级指标                                  │
│ │  ├─ Node Name -> [CPU 时间序列]              │
│ │  ├─ Node Name -> [Memory 时间序列]           │
│ │  └─ Node Name -> [Network 时间序列]          │
│ │                                              │
│ ├─ Container 级指标                             │
│ │  ├─ Container ID -> [CPU 时间序列]           │
│ │  └─ Container ID -> [Memory 时间序列]        │
│ │                                              │
│ └─ 聚合指标                                     │
│    ├─ Node 上所有 LSR Pod 的平均 CPU         │
│    ├─ Node 上所有 BE Pod 的总内存            │
│    └─ 集群级别的资源使用聚合                   │
│                                                 │
│ 时间精度:                                      │
│ ├─ 秒级: 最近 1 小时                          │
│ ├─ 分钟级: 最近 1 周                          │
│ └─ 小时级: 历史存档                            │
│                                                 │
└─────────────────────────────────────────────────┘

6.2 Why - 为什么需要 TSDB?

问题 1:需要查询历史数据进行分析和预测

bash 复制代码
没有 MetricCache 的方案:

QOSManager 需要做决策:
├─ 决策: 我应该现在驱逐 BE Pod 吗?
├─ 需要的信息:
│  ├─ 当前 LS Pod 的 CPU 使用: 2.5 CPU
│  ├─ 过去 5 分钟 LS CPU 的趋势: 从 1 到 2.5(快速增长)
│  ├─ 过去 1 小时 LS CPU 的模式: 每整点时突发
│  └─ 历史上相同时段的情况: 通常在 3.5 CPU
│
├─ 问题:
│  ├─ 仅看当前值 (2.5) 无法判断趋势
│  ├─ 无法区分"突发"与"正常波动"
│  ├─ 无法根据历史规律预测接下来的行为
│  └─ 决策极易出错

有 MetricCache 的方案:
├─ 查询历史: GetMetrics(pod_uid, 1h) 获取过去 1 小时的数据
├─ 分析: 计算 p50/p95/p99,看出趋势斜率
├─ 预测: 基于增长趋势,预测 10 分钟后的值
├─ 决策: 如果预测超过 limit,则提前驱逐
└─ 效果: 精准、可靠、防患于未然

问题 2:需要高效查询而不是重新采集

yaml 复制代码
没有缓存的方案:

每次做决策都重新采集:
├─ QOSManager: "我需要 Pod A 过去 1 小时的 CPU 数据"
├─ 方案: 从头采集 Pod A 的历史 cgroup 数据
│  └─ 读取 cgroup 文件 × 3600 次(每秒一次采样)
├─ 耗时: 3600 秒的数据读取 = 无法容忍的延迟
└─ 不可行!

有 MetricCache 的方案:
├─ 数据已经在内存中,无需重新读取
├─ 查询 1 小时 3600 个数据点: < 10ms
└─ 高效!

性能对比:
┌─────────────────────────────────┐
│ 查询时间对比                    │
├─────────────────────────────────┤
│ 无缓存(重新采集)  | 3600s+   │
│ 有缓存(内存查询)  | <10ms    │
│ 性能提升            | 360000x  │
└─────────────────────────────────┘

问题 3:支持复杂的时间序列分析

yaml 复制代码
常见的分析需求:

需求 1: 时间窗口聚合
└─ 计算过去 1 小时每 5 分钟的平均 CPU

需求 2: 趋势计算
└─ 计算过去 6 小时的 CPU 增长速度(/分钟)

需求 3: 异常检测
└─ 当前值是否超过历史 p95?

需求 4: 衰减权重聚合
└─ 最近的数据权重更高,旧数据权重更低

需求 5: 时间对齐
└─ 对齐多个 Pod 的 CPU 数据,计算相关性

需求 6: 跨指标组合
└─ CPU 和内存的相关性分析

直接在采集器中实现:
└─ 每个决策都重新计算 → CPU 占用高,逻辑重复

使用 MetricCache:
└─ 所有分析都在 TSDB 中完成 → 逻辑集中、高效

6.3 How - TSDB 的核心机制

时间序列存储架构

ini 复制代码
┌──────────────────────────────────────────┐
│      MetricCache 的三层存储架构      │
├──────────────────────────────────────────┤
│                                          │
│ L1: 热数据层(内存,最近 1 小时)     │
│    ├─ 数据结构: 数组(Ring Buffer)     │
│    ├─ 时间精度: 秒级                    │
│    ├─ 容量: ~3600 个数据点             │
│    ├─ 访问延迟: < 1ms                  │
│    └─ 更新: 实时推送                    │
│                                          │
│ L2: 温数据层(内存,1-24 小时)      │
│    ├─ 数据结构: 压缩时间序列(gorilla) │
│    ├─ 时间精度: 分钟级                  │
│    ├─ 容量: ~1440 个数据点             │
│    ├─ 访问延迟: 1-5ms                  │
│    └─ 压缩率: 10x(原始大小的 10%)   │
│                                          │
│ L3: 冷数据层(磁盘,1 周以上)       │
│    ├─ 数据结构: RocksDB(K-V 存储)    │
│    ├─ 时间精度: 小时级                  │
│    ├─ 容量: ~168 个数据点              │
│    ├─ 访问延迟: 10-50ms                │
│    └─ 压缩率: 100x                     │
│                                          │
└──────────────────────────────────────────┘

访问模式:

最近 1 小时: L1 (热)
    └─ 查询延迟 < 1ms
    └─ 用途: 实时决策 (QOSManager)

1 小时前 - 1 天前: L2 (温)
    └─ 查询延迟 < 5ms
    └─ 用途: 趋势分析、预测

1 天前 - 1 周前: L3 (冷)
    └─ 查询延迟 10-50ms
    └─ 用途: 历史存档、审计

查询流程:

用户请求: GetMetrics(pod_uid, from=1h ago, to=now)
    │
    ├─→ 检查 L1: [now-1h, now] 范围有数据
    ├─→ 直接返回 L1 的秒级数据
    └─→ 延迟: < 1ms

用户请求: GetMetrics(pod_uid, from=24h ago, to=now)
    │
    ├─→ [now-1h, now]: 从 L1 获取(秒级)
    ├─→ [now-24h, now-1h]: 从 L2 获取(分钟级,需要解压)
    └─→ 延迟: < 10ms

MetricCache 的完整实现

6.4 Ring Buffer - 秒级数据结构

高性能的环形缓冲区

go 复制代码
// Ring Buffer 的实现思想
type RingBuffer struct {
    data []float64      // 固定大小数组 (3600 元素)
    pos  int            // 当前写入位置
    full bool           // 是否已填满一圈
}

插入操作:
    pos 指向下一个要写的位置
    新数据覆盖最老的数据
    当 pos 达到末尾时,回到开头

特点:
    ├─ O(1) 的插入性能
    ├─ O(1) 的查询性能
    ├─ 固定内存占用(不会增长)
    └─ 自动丢弃最老的数据(无需显式 GC)

Ring Buffer 的实际使用

ini 复制代码
时间线演示:

时刻 T=10:00:00, 数据推送开始

Ring Buffer 初始状态:
    pos=0, data=[nil, nil, nil, ..., nil] (3600 个槽)

T=10:00:01: 推送数据 2.5 (CPU)
    写入: data[0] = 2.5
    更新: pos = 1

T=10:00:02: 推送数据 2.6
    写入: data[1] = 2.6
    更新: pos = 2

... (持续推送数据)

T=10:59:59: 第 3599 个数据
    写入: data[3598] = 2.4
    更新: pos = 3599

T=11:00:00: 第 3600 个数据(缓冲区满)
    写入: data[3599] = 2.3
    更新: pos = 0, full = true

T=11:00:01: 第 3601 个数据(开始覆盖)
    写入: data[0] = 2.4  ← 覆盖 T=10:00:01 的数据
    更新: pos = 1

结果:
    ├─ 保持最近 3600 秒的数据
    ├─ 最老的数据被最新的覆盖
    └─ 内存占用永远不增加

查询操作:

Query: 获取最近 1 小时(过去 3600 秒)的数据
    如果 full=false(缓冲区未满):
        └─ 返回 data[0:pos](已有的数据)
    
    如果 full=true(缓冲区已满):
        ├─ 将 [pos, 3600) 的数据作为旧部分
        ├─ 将 [0, pos) 的数据作为新部分
        └─ 按时间顺序拼接返回

性能指标:
    ├─ 插入: O(1), < 0.01ms
    ├─ 查询: O(n), n=3600, < 1ms
    └─ 内存: 固定 ~14 KB (3600 × 8 bytes float64)

6.5 Gorilla 压缩 - 分钟级数据压缩

时间序列压缩算法

ini 复制代码
为什么需要压缩?

L1 (秒级,1 小时): 3600 数据点 × 8 bytes = 28.8 KB
L2 (分钟级,24 小时): 1440 数据点 × 8 bytes = 11.52 KB × 24 = 276 KB
└─ 如果有 1000 个 Pod,总计 276 MB

问题:
└─ 随着 Pod 数量和时间增长,内存占用会线性增加

Gorilla 压缩的思想:
└─ 时间序列通常有很高的冗余
└─ 相邻数据点通常非常接近(差值小)
└─ 可以只存储"差值"而不是绝对值

压缩率对比:

原始数据(24 小时,分钟采样):
CPU 使用率: 2.5, 2.6, 2.4, 2.5, 2.3, 2.1, 2.2, 2.4, ...
字节大小: 1440 × 8 = 11.52 KB

差值存储:
首个数据: 2.5 (完整存储,32 bits)
差值序列: 0.1, -0.2, 0.1, -0.2, -0.2, 0.1, 0.2, ...
└─ 大多数差值很小,可以用 4 bits 或 8 bits 表示
└─ 压缩后大小: ~1.5 KB

压缩率: 11.52 KB / 1.5 KB = 7.68x

实际效果:
└─ 对于平稳变化的指标: 8-10x 压缩率
└─ 对于剧烈波动的指标: 2-4x 压缩率

Gorilla 算法的工作流程

ini 复制代码
编码(压缩):

输入: 时间戳序列 [T0, T1, T2, ...]
      数值序列   [V0, V1, V2, ...]

步骤 1: 时间戳增量编码
    DeltaT0 = T0  (完整存储)
    DeltaT1 = T1 - T0 = 60 秒
    DeltaT2 = T2 - T1 = 60 秒
    DeltaT3 = T3 - T2 = 61 秒
    └─ 大多数 Delta 都是 60 秒
    └─ 可以只编码"与标准 60 秒的偏差"

步骤 2: 数值变化编码
    V0 = 2.5 (完整存储,32 bits)
    V1 = 2.6 (差值: +0.1)
    V2 = 2.4 (差值: -0.2)
    V3 = 2.5 (差值: +0.1)
    └─ 差值绝对值 < 0.5,用 4-8 bits 表示

步骤 3: 位编码
    └─ 使用变长整数编码(VInt)
    └─ 小的差值用少量 bit,大差值用多 bit

输出: 压缩后的字节流

解码(解压):

解压器按照编码顺序反向操作:
    1. 恢复时间戳增量
    2. 恢复数值差值
    3. 累加得到完整序列

性能:
    ├─ 编码: ~100 ns/point
    ├─ 解码: ~50 ns/point
    └─ 压缩到磁盘: ~0.5 KB/小时/Pod

6.6 RocksDB - 长期存储

使用 RocksDB 存储冷数据

yaml 复制代码
RocksDB 的特点:

为什么选择 RocksDB?
├─ LSM Tree 结构,顺序 I/O 高效
├─ 支持列式存储压缩
├─ 可配置的压缩算法(LZ4, ZSTD)
├─ 性能与空间的平衡
└─ 生产级别成熟度(Facebook 开源)

数据组织:

Key: {pod_uid}_{metric_name}_{timestamp_hour}
Value: 该小时的压缩时间序列

示例:
    Key: "pod-123_cpu_2025-12-30-10"
    Value: [Gorilla 压缩的该小时 CPU 数据]
    
    Size: ~50 bytes (相比原始 3600 × 8 = 28.8 KB,压缩 500+x)

存储大小估算(1000 Pod 集群,2 周存档):

原始大小(不压缩):
    ├─ 1000 Pod × 3 指标(CPU/Mem/Net)
    ├─ × 14 天 × 24 小时
    └─ = 1000 × 3 × 14 × 24 × 8 bytes ≈ 80 GB

RocksDB 压缩后:
    ├─ 时间戳增量压缩: ~7x
    ├─ Gorilla 数值压缩: ~8x
    ├─ RocksDB 列压缩: ~3x
    └─ = 80 GB / (7 × 8 × 3) ≈ 475 MB

存储成本:
    └─ 每个 Pod 每天: 475 MB / 1000 / 14 ≈ 34 KB
    └─ 整个集群: ~34 MB/天(可接受)

6.7 查询接口和使用示例

MetricCache 的查询 API

go 复制代码
interface MetricCache {
    // 单指标查询
    GetMetric(resourceID string, metric string, 
              from time.Time, to time.Time) ([]Point, error)
    
    // 聚合查询
    GetMetricAgg(resourceID string, metric string,
                 from time.Time, to time.Time,
                 aggregator AggregatorFunc) (float64, error)
    
    // 批量查询
    GetMetricsInBatch(queries []QueryRequest) ([]TimeSeries, error)
    
    // 实时最新值
    GetLatest(resourceID string, metric string) (float64, error)
}

支持的聚合函数:
    ├─ Average: 平均值
    ├─ Max: 最大值
    ├─ Min: 最小值
    ├─ Sum: 求和
    ├─ Percentile(p): 分位数
    ├─ Derivative: 导数(变化速率)
    └─ Integral: 积分(累计)

生产案例:趋势分析和预测

ini 复制代码
场景:QOSManager 需要判断是否应该驱逐 BE Pod

决策逻辑:

检查点 1: 当前状态
    current_cpu = GetLatest(pod_uid, "cpu")  // 查询最新值
    if current_cpu < 1.5 CPU:
        └─ 不驱逐,资源充足

检查点 2: 短期趋势(最近 5 分钟)
    recent_metrics = GetMetric(pod_uid, "cpu", 
                               time.Now()-5m, time.Now())
    trend = CalculateTrend(recent_metrics)
    
    if trend > 0.2 CPU/min:  // CPU 快速增长
        └─ 说明有突发,需要更激进地抑制

检查点 3: 历史对比(过去 1 小时同类时段)
    hour_ago = GetMetric(pod_uid, "cpu",
                         time.Now()-1h-5m, time.Now()-1h)
    hour_ago_avg = GetMetricAgg(..., Average)
    
    if current_cpu > hour_ago_avg * 1.5:  // 比历史高 50%
        └─ 说明异常,可能需要驱逐

检查点 4: 多维度风险评分
    mem_trend = CalculateTrend(GetMetric(..., "memory", ...))
    network_trend = CalculateTrend(GetMetric(..., "network", ...))
    
    risk_score = 0
    if cpu_trend > 0.1:     risk_score += 30
    if mem_trend > 5%:      risk_score += 40
    if network_trend > 20%: risk_score += 10
    
    if risk_score > 60:
        └─ 驱逐该 BE Pod,保护 LS Pod

完整决策:
    T=10:10:00  采集新指标
    T=10:10:05  MetricCache 可用,开始查询
    T=10:10:06  获取结果:risk_score = 75
    T=10:10:07  决策:驱逐 Pod
    T=10:10:08  执行:发送驱逐信号
    T=10:10:12  验证:Pod 开始关闭,CPU 释放
    
    总耗时: 12 秒,从检测到执行完成

MetricCache 的运维和优化

6.8 缓存淘汰策略

三级数据淘汰

ini 复制代码
L1 (热数据,秒级) 淘汰:
    ├─ 保留期限: 1 小时
    ├─ 淘汰方式: Ring Buffer 自动覆盖
    ├─ 淘汰时机: 新数据进来,自动覆盖最老的
    └─ 特点: 零额外 GC 成本

L2 (温数据,分钟级) 淘汰:
    ├─ 保留期限: 24 小时
    ├─ 淘汰方式: 当 Ring Buffer 满后,不再更新
    │           旧数据保留不变
    ├─ 手动清理: 24h 后的分钟级数据转移到 L3
    └─ 触发: 定时任务(每小时检查一次)

L3 (冷数据,小时级) 淘汰:
    ├─ 保留期限: 7 天(可配置)
    ├─ 淘汰方式: TTL 淘汰
    │           └─ 读取时检查是否超过 TTL
    │           └─ 后台定时扫描删除过期 key
    ├─ RocksDB 的 Compaction:
    │           └─ 删除的 key 在 Compaction 时真正移除
    │           └─ 优化存储空间
    └─ 触发: 定时 Compaction(每天凌晨 02:00)

淘汰流程:

T=Day 0:
    L1: 最近 1 小时数据
    L2: 空
    L3: 空

T=Day 1 (1 小时后):
    └─ L1 满,最旧的秒级数据被覆盖
    └─ 将 L1 的数据聚合为分钟级,保存到 L2

T=Day 2 (24 小时后):
    └─ L2 满,最旧的分钟级数据确定
    └─ 将 L2 的数据聚合为小时级,保存到 L3
    └─ 压缩并写入 RocksDB

T=Day 8 (7 天后):
    └─ TTL 过期,标记为删除
    └─ 等待 Compaction 清理
    └─ 内存和磁盘空间释放

6.9 性能优化技巧

问题 1:查询延迟过高

markdown 复制代码
症状:GetMetrics() 耗时 > 20ms

可能原因:
├─ 数据存储在 RocksDB(冷数据)而不是内存
├─ 查询范围过大(超过 24 小时)
├─ RocksDB 的 Compaction 正在进行

诊断:
    metrics_latency = time.Since(query_start)
    if metrics_latency > 20ms:
        check location:  // L1/L2/L3?
        check range:     // 查询范围多长?
        check compaction: // Compaction 状态?

优化方案:
1. 提前预热 L1 数据
   └─ 系统启动时,预先加载最近 1 小时的数据

2. 缩小查询范围
   └─ 改为: 查询最近 1 小时(L1)+ 聚合值
   └─ 不要查询整个历史

3. 调整 RocksDB 的 Compaction 时机
   └─ 改为凌晨 00:00-02:00 进行
   └─ 避免业务高峰

4. 使用缓存加速
   └─ 对常见的聚合查询(如 1h avg)进行缓存
   └─ 避免重复计算

5. 并行查询
   └─ L1 和 RocksDB 的查询可以并行进行
   └─ 取最后一个完成的结果

问题 2:内存占用过高

yaml 复制代码
症状:MetricCache 占用 > 5 GB 内存(1000 Pod 节点)

可能原因:
├─ L1 + L2 的缓冲区设置过大
├─ 没有正确转移数据到 L3
├─ 内存泄漏

诊断:
    理论值 = Pod 数 × 指标数 × 环缓冲区大小
    1000 × 3 × (3600 + 1440) × 8 bytes = ~165 MB (太小)
    
    如果实际 5 GB,说明有问题

优化方案:
1. 减少 Ring Buffer 大小
   └─ 改为 900 秒而不是 3600 秒
   └─ 减少内存 70%,仍能满足大多数查询

2. 加速 L2 到 L3 的迁移
   └─ 改为 8 小时后迁移(而不是 24 小时)
   └─ 使用压缩减少内存占用

3. 按优先级保留数据
   └─ LS Pod: 保留 7 天
   └─ BE Pod: 保留 3 天(变化快,旧数据价值小)

问题 3:数据丢失或不一致

markdown 复制代码
症状:查询同一时间段的数据,值不一样

可能原因:
├─ L1→L2 迁移时的数据丢失
├─ RocksDB 的 Compaction 过程中数据不一致
├─ 查询到的是聚合值而不是原始值

避免方案:
1. 使用 Write-Ahead Log (WAL)
   └─ 在迁移前,记录操作日志
   └─ 宕机时可以恢复

2. 双写验证
   └─ 数据同时写入 L1 和 L2
   └─ 定期对比验证一致性

3. 只保存原始精度的数据
   └─ 不要聚合,避免精度损失
   └─ 聚合操作在查询时执行

4. 版本控制
   └─ 每次修改数据时记录版本号
   └─ 支持时间点恢复

6.10 生产配置建议

yaml 复制代码
# MetricCache 配置示例
apiVersion: v1
kind: ConfigMap
metadata:
  name: koordlet-config
  namespace: koordinator-system
data:
  koordlet-config.yaml: |
    metricCache:
      # L1 - 热数据(秒级)
      hotDataBufferSize: 3600  # 1 小时的数据点
      hotDataRetention: 1h
      
      # L2 - 温数据(分钟级)
      warmDataBufferSize: 1440  # 1 天的数据点
      warmDataRetention: 24h
      warmDataCompression: gorilla
      
      # L3 - 冷数据(小时级)
      coldDataRetention: 168h  # 7 天
      coldDataCompression: zstd
      rockdbPath: /var/lib/koordlet/metric-cache
      rockdbCompactionHour: 2  # 凌晨 2:00 Compaction
      
      # 查询优化
      queryTimeout: 10s
      maxConcurrentQueries: 100
      queryCache:
        enabled: true
        ttl: 30s
      
      # 性能调优
      asyncPush: true           # 异步推送指标
      pushBatchSize: 100       # 批量推送大小
      pushInterval: 100ms      # 推送间隔

6.11 监控指标

promql 复制代码
# MetricCache 关键监控指标

# 缓存大小
koordlet_metric_cache_size_bytes{level="L1"}
koordlet_metric_cache_size_bytes{level="L2"}
koordlet_metric_cache_size_bytes{level="L3"}

# 查询延迟
koordlet_metric_cache_query_latency_milliseconds{level="L1"}
koordlet_metric_cache_query_latency_milliseconds{level="L3"}

# 数据转移
koordlet_metric_cache_migration_points{from="L1", to="L2"}

# RocksDB 状态
koordlet_rockdb_compaction_duration_seconds
koordlet_rockdb_size_bytes

# 命中率
koordlet_metric_cache_hit_ratio  # 应该 > 95%

MetricCache 与其他模块的协作

6.12 QOSManager 的使用模式

典型查询模式

less 复制代码
QOSManager 的工作循环(每秒执行一次):

步骤 1:获取最新值(L1,延迟 < 1ms)
    current_metrics = cache.GetLatest(pod_uid)
    
步骤 2:分析短期趋势(L1,过去 5 分钟)
    trend = cache.GetMetricAgg(
        pod_uid, "cpu",
        time.Now()-5m, time.Now(),
        metric.Derivative  // 导数 = 变化速率
    )
    
步骤 3:与历史对比(L2,过去 1 小时相同时段)
    historical = cache.GetMetricAgg(
        pod_uid, "cpu",
        time.Now()-1h-5m, time.Now()-1h,
        metric.Percentile(95)  // 95 分位
    )
    
步骤 4:做出决策
    if current_metrics.CPU > request * 0.9:
        and trend > 0.5 CPU/min:
        and current > historical * 1.3:
        then: evict_pod(pod_uid)

性能目标:
    ├─ 步骤 1: < 1ms
    ├─ 步骤 2: < 5ms
    ├─ 步骤 3: < 10ms (L2 可能需要解压)
    └─ 总计: < 20ms (每秒 1000 个 Pod 时可接受)

6.13 PredictServer 的使用模式

负载预测

markdown 复制代码
PredictServer 通过历史数据预测未来:

输入:
    ├─ 过去 24 小时的 CPU 时间序列
    │  └─ 从 MetricCache.GetMetric() 获取
    │
    ├─ 时间特征
    │  ├─ 当前时刻(小时)
    │  ├─ 星期几
    │  └─ 是否工作日
    │
    └─ 集群事件(扩容/发布等)

算法:
    1. 周期识别
       └─ 识别 24h、7d、28d 的周期性

    2. 趋势提取
       └─ 使用 Goertz 变换提取趋势分量

    3. 异常检测
       └─ 去除离群值

    4. 回归模型
       └─ ARIMA 或 Prophet 进行预测

输出:
    ├─ 未来 1 小时的 CPU 预测(分钟级)
    ├─ 预测的置信区间
    └─ 可信度评分

使用场景:
    ├─ 提前 10 分钟预测,主动驱逐 BE Pod
    ├─ 资源预留时,参考 p99 预测值
    └─ 自动扩容时,提前准备容量

查询调用:
    future_cpu = predictor.Predict(
        pod_uid, "cpu",
        horizon=10min,  // 预测未来 10 分钟
        confidence=95
    )
    // future_cpu = 3.2 ± 0.3 CPU (95% 置信度)

总 - 生产调优指南

6.14 常见问题与解决方案

问题 1:MetricCache 导致 Koordlet 启动变慢

markdown 复制代码
症状:启动 Koordlet 需要 2-3 分钟

原因:
    └─ RocksDB 打开、读取索引、加载热数据

解决:
    1. 异步加载
       └─ L1/L2 在后台异步加载
       └─ 启动时先用空缓存,不阻塞

    2. 跳过冷数据
       └─ 只在第一次查询冷数据时加载 L3
       └─ 不要启动时全加载

    3. 预热优化
       └─ 只预热最近 6 小时的数据
       └─ 不要预热全部 7 天数据

预期效果:
    └─ 启动时间从 2-3 分钟减少到 10-20 秒

问题 2:查询性能波动很大(有时 5ms,有时 100ms)

markdown 复制代码
原因:
    └─ RocksDB 的 Compaction 正在进行
    └─ 磁盘 I/O 阻塞查询

解决:
    1. 使用后台 Compaction
       └─ 不要同步等待,异步进行

    2. 限制 Compaction 的 I/O
       └─ rate_limiter = 100MB/s
       └─ 不要让 Compaction 阻塞用户查询

    3. 时间隔离
       └─ 在业务低谷进行 Compaction
       └─ 例:凌晨 00:00-02:00

    4. 使用 SSD
       └─ HDD 导致查询慢
       └─ SSD 可以将 RocksDB 查询从 50ms 降到 5ms

问题 3:MetricCache 占用的磁盘空间太大(> 100 GB)

markdown 复制代码
原因:
    ├─ RocksDB 存储的冷数据过多
    ├─ Compaction 没有真正删除数据
    └─ 压缩算法设置不当

解决:
    1. 减少保留期限
       └─ 改为 3 天而不是 7 天
       └─ 大多数分析用不到 7 天前的数据

    2. 强制 Compaction
       └─ 手动触发 RocksDB 的全量 Compaction
       └─ 可以回收 20-30% 的磁盘空间

    3. 选择更好的压缩算法
       └─ 改为 ZSTD(代替 LZ4)
       └─ 压缩率从 10x 改为 20x
       └─ 代价:CPU 多消耗 10%

    4. 分片存储
       └─ 不同类型的 Pod 数据分别存储
       └─ 可以选择性删除 BE Pod 的冷数据

6.15 最佳实践

为不同的查询范围选择合适的数据层(L1/L2/L3)定期检查 RocksDB 的磁盘占用,及时 Compaction在业务低谷进行 Compaction,避免影响实时决策使用分位数而不仅看平均值,掌握数据的真实分布监控查询延迟,保持在 < 10ms


总结 - 章节要点汇总

6.16 关键概念速查

概念 含义 保留期 查询延迟
L1 Ring Buffer,秒级 1h < 1ms
L2 Gorilla 压缩,分钟级 24h < 5ms
L3 RocksDB,小时级 7d 10-50ms
Gorilla 时间序列压缩算法 - 编解 < 100ns
TTL 淘汰 自动删除过期数据 可配置 -

6.17 三层存储的选择指南

scss 复制代码
查询需求                        推荐数据层
─────────────────────────────────────────────
实时决策(最新值)              L1 (热)
短期趋势(最近 1 小时)         L1 (热)
日常分析(最近 24 小时)        L2 (温)
历史对标(过去 1 周)           L3 (冷)
长期审计(过去 1 个月)         归档存储

本章核心收获

  • 理解 TSDB 的三层架构设计和每层的应用场景
  • 掌握 Ring Buffer、Gorilla 压缩和 RocksDB 的工作原理
  • 学会如何高效查询历史数据支持决策
  • 理解缓存淘汰和存储优化的实践方法
  • 学会监控和优化 MetricCache 的性能
相关推荐
冬天的风滚草2 小时前
揭秘云原生混布资源调度器Koordinator (一)Koordinator 整体架构设计
云计算
可爱又迷人的反派角色“yang”3 小时前
docker(五)
linux·运维·网络·docker·容器·云计算
EMQX3 小时前
大规模使用 AWS IoT Core 的成本困境:EMQX 如何削减 80% 开支
物联网·mqtt·云计算·aws
weixin_307779133 小时前
通过AWS Transfer Family集成Active Directory实现安全SFTP文件访问
安全·云计算·aws
木子欢儿3 小时前
阿里云系统磁盘总读BPS突然增长很高,导致网站502 Bad Gateway
阿里云·云计算·gateway
翼龙云_cloud4 小时前
亚马逊云渠道商:AWS Lightsail 极速部署演示环境搭建指南
运维·服务器·云计算·aws
微爱帮监所写信寄信4 小时前
微爱帮技术实践:阿里云短信接口的高可用优化方案
开发语言·网络协议·阿里云·云计算·php
iconball17 小时前
个人用云计算学习笔记 --37 Zabbix
运维·笔记·学习·云计算·zabbix
通义灵码18 小时前
从 Vibe Coding 到云端部署:Qoder + 阿里云 ECS 实战
阿里云·云计算