揭秘云原生混布资源调度器Koordinator (十二)CPU Throttle 机制

42 阅读12分钟

核心使命与设计理念

12.1 CPU Throttle 是什么?

CPU Throttle 是 Linux CFS 调度器在 Pod 超过 CPU quota 限制时采取的限流措施,通过拒绝 CPU 时间片的分配,导致进程运行被暂停(Throttle)。

核心概念

┌─────────────────────────────────────────────┐
│           CPU Throttle 的工作原理            │
├─────────────────────────────────────────────┤
│                                             │
│ CPU quota 的含义:                          │
│ ├─ 每 100ms(cfs_period)内,可使用的 CPU   │
│ ├─ 例:quota=200000 μs = 2 CPU × 100ms    │
│ ├─ 即:每 100ms 最多运行 200ms 的代码      │
│ └─ 超过就会被 throttle                     │
│                                             │
│ Throttle 的含义:                          │
│ ├─ 进程已经使用了本周期的配额               │
│ ├─ 即使 CPU 空闲,也不被调度               │
│ ├─ 进程等待下一个周期开始                  │
│ └─ 这个等待的时间称为 throttle 时间       │
│                                             │
│ 工作时间线:                                │
│ 时刻    进程状态        CPU 配额状态         │
│ ──────────────────────────────────────     │
│ 0ms    Running       配额: 200/200ms      │
│ 50ms   Running       配额: 150/200ms      │
│ 100ms  Running       配额: 100/200ms      │
│ 180ms  Running       配额: 20/200ms       │
│ 190ms  Running       配额: 10/200ms       │
│ 199ms  Running       配额: 1/200ms        │
│ 200ms  Throttled ❌   配额: 0/200ms        │
│ ...    Throttled     等待中...            │
│ 300ms  Running ✅     配额: 200/200ms (新周期)│
│                                             │
└─────────────────────────────────────────────┘

12.2 Throttle 会导致问题?

问题 1:Throttle 导致延迟增加

场景:在线 API 服务的 CPU Throttle 问题

Pod 配置:
├─ request: 2 CPU
├─ limit: 2 CPU
├─ cfs_quota_us: 200000 (2 CPU × 100ms)

时间线:

T=10:00:00  正常运行
            ├─ 请求处理耗时: 20ms
            ├─ API 响应: < 50ms

T=10:00:10  流量激增(同一时段)
            ├─ 多个请求同时到达
            ├─ 每个请求处理耗时: 20ms
            ├─ 并发: 15 个请求
            ├─ 总 CPU 需求: 300ms(在 100ms 周期内)
            └─ 但配额只有 200ms

调度流程(100ms 周期):

T=0ms      15 个请求开始运行
           ├─ 请求 1-10: 获得 CPU 时间
           ├─ 每个 20ms
           └─ 总: 200ms,用完配额

T=200ms    请求 11-15 仍在队列中
           ├─ 无法获得 CPU 时间
           ├─ 都被 Throttle 了
           └─ 等待下一个周期(100ms)

T=300ms    新周期开始
           ├─ 请求 11-15 继续执行
           ├─ 但由于等待,延迟增加了
           └─ 响应延迟: 50ms → 150ms ❌

影响:
├─ 用户感知延迟增加 3 倍
├─ API P99 延迟大幅增加
├─ SLO 违反
└─ 用户投诉

问题 2:Throttle 的冷启动问题

场景:Pod 冷启动后立即被 Throttle

Pod 创建流程:
├─ T=0: kubelet 创建容器
├─ T=100ms: 容器启动,开始初始化
├─ T=200ms: 应用启动,开始处理请求

初始化负载高:
├─ 加载配置文件
├─ 初始化数据库连接
├─ 预热缓存
├─ CPU 占用: 可能达到 150% (大于平时的 100%)

时间线:

T=0-100ms: 初始化运行,CPU 使用率 150%
          ├─ 周期 1 (0-100ms):
          │  ├─ 配额: 200ms
          │  ├─ 需求: 150ms
          │  └─ 配额充足,正常运行
          │
          ├─ 周期 2 (100-200ms):
          │  ├─ 配额: 200ms
          │  ├─ 需求: 150ms
          │  └─ 配额充足

T=100-200ms: 初始化继续,CPU 150%
            ├─ 周期 2 (100-200ms):
            │  ├─ 配额: 200ms
            │  ├─ 需求: 150ms (但前 50ms 已耗尽)
            │  └─ 实际可用: 150ms
            │
            └─ 进程每次都被 Throttle

问题:
├─ Pod 创建后,冷启动阶段性能受限
├─ 应用启动时间延长
├─ 服务不可用时间增加
└─ 启动延迟可能达到秒级

不使用 CPUBurst 的启动时间:
├─ 初始化阶段: 200ms (受 throttle 限制)
├─ 应用启动: 500ms
├─ 首次请求: 1000ms (等待初始化完成)
└─ 总: > 1.5 秒不可用

使用 CPUBurst 的启动时间:
├─ 初始化阶段: 100ms (允许 burst,使用更多 CPU)
├─ 应用启动: 300ms (更快)
├─ 首次请求: 500ms (更早可用)
└─ 总: < 1 秒(快 33%)

问题 3:Throttle 导致的"磁吸效应"

场景:Pod 频繁达到 quota 限制

问题现象:

Pod CPU 使用率曲线:
├─ 时间 0-50ms: 快速上升到 100% CPU
├─ 时间 50-100ms: 保持 100%
├─ 时间 100ms: 瞬间下降到 0%(Throttle)
├─ 时间 100-150ms: 继续下降(等待)
├─ 时间 150ms: 恢复 100%(新周期)

├─ 结果: 锯齿波形

这种模式导致的问题:

1. 不均衡的负载:
   ├─ 周期初期: CPU 满载,争夺激烈
   ├─ 周期末期: CPU 空闲,无人运行
   └─ 缓存热度周期性变化

2. 吞吐量不稳定:
   ├─ 周期初期吞吐量高
   ├─ 周期末期吞吐量低
   └─ 平均吞吐量 < 理论最大值

3. 延迟波动大:
   ├─ 请求在周期初期: 快速处理
   ├─ 请求在周期末期:  Throttle 延迟
   └─ P99 延迟很高

原因分析:
├─ CPU quota 的"硬边界"特性
├─ CFS 调度器的实现方式
└─ Pod 的实际需求与配额的不匹配

12.3 检测和优化 Throttle

Throttle 的检测和度量

Linux CGroup 提供的 Throttle 统计:

cat /sys/fs/cgroup/cpu/kubepods/pod-xxx/cpu.stat

cpu.stat 的内容:
├─ nr_periods: 调度周期总数(通常 = 运行时间 / 100ms)
├─ nr_throttled:  throttle 的周期数
├─ throttled_time:  throttle 的总时间(纳秒)

└─ 计算 Throttle 相关指标:
   ├─ throttle_ratio = nr_throttled / nr_periods × 100%
     └─ 表示被 throttle 的周期占比
   
   ├─ throttle_duration_ratio = throttled_time / total_time × 100%
     └─ 表示被 throttle 的时间占比
   
   ├─ 平均 throttle 时长 = throttled_time / nr_throttled
     └─ 表示平均每次 throttle 持续多长
   
   └─ throttle_burst_impact = throttled_time / (request_time - throttled_time)
      └─ 表示 throttle 对吞吐量的影响

示例数据:

Pod A(配额充足,无 throttle):
├─ nr_periods: 1000
├─ nr_throttled: 0
├─ throttled_time: 0

└─ 指标:
   ├─ throttle_ratio: 0%
   └─ throttle_duration_ratio: 0%

Pod B(配额不足,频繁 throttle):
├─ nr_periods: 1000
├─ nr_throttled: 400
├─ throttled_time: 8000000000 ns = 8s

└─ 指标:
   ├─ throttle_ratio: 40%(400 个周期中 40%  throttle)
   ├─ throttle_duration_ratio: 8% (总 100s 中有 8s  throttle)
   ├─ avg_throttle_time: 20ms (8s / 400 周期)
   └─ 吞吐量影响: 8% / (100-8)  8.7%

Throttle 的优化策略

优化方向 1:提高 CPU quota

问题:Pod 的 limit 设置过低
解决:
├─ 调查 Pod 的实际需求
├─ 增加 CPU limit
├─ 例:2 CPU → 3 CPU(假设需要)

优化方向 2:允许 CPU burst

问题:短期流量激增导致 throttle
解决:
├─ 启用 CPUBurst
├─ 允许 Pod 在有空闲 CPU 时超过 limit
├─ 例:limit=2 CPU,burst_ratio=1.5,实际可用 3 CPU

优化方向 3:优化应用,减少 CPU 需求

问题:应用逻辑效率低,CPU 占用高
解决:
├─ 优化应用代码
├─ 减少不必要的计算
├─ 改进算法复杂度
└─ 例:从 O(n²) 改为 O(n log n)

优化方向 4:使用更激进的 Suppress

问题:BE Pod 与 LS Pod 竞争
解决:
├─ 当检测到 LS Pod throttle 时
├─ 更激进地抑制 BE Pod
├─ 为 LS 预留充足空间

优化方向 5:使用 Throttle-aware 调度

问题:调度器不考虑 throttle 风险
解决:
├─ 预测 Pod 的 throttle 风险
├─ 在调度时考虑
├─ 选择配额充足的节点

Throttle 优化的生产案例

12.4 生产案例 1:API 网关的 Throttle 优化

场景:电商平台 API 网关,流量波动大

初始配置:
├─ Pod CPU request: 2
├─ Pod CPU limit: 2
├─ 副本数: 10

问题现象:

监控发现:
├─ API P99 延迟: 300ms(目标 < 100ms)
├─ throttle_ratio: 25%(高)
├─ 偶发请求超时(> 1s)

分析:

1. 检查流量模式
   ├─ 平均 QPS: 1000
   ├─ 峰值 QPS: 5000(5 倍)
   ├─ 峰值时的 CPU 需求:每个 Pod 需要 4 CPU(burst)
   └─  limit  2 CPU

2. 检查资源利用率
   ├─ 低谷时: 0.5 CPU(利用率 25%)
   ├─ 平均: 1.5 CPU(利用率 75%)
   ├─ 峰值: 3+ CPU(受 limit 限制,throttle 严重)
   └─ 问题: limit 设置过保守

优化方案 1:提高 limit

配置变更:
├─ Pod CPU request: 2
├─ Pod CPU limit: 4(从 2 改为 4
├─ 副本数: 10(不变)

效果:
├─ API P99 延迟: 300ms  80ms 
├─ throttle_ratio: 25%  0% 
├─ 节点总 CPU 需求: 20  40(翻倍,可能超过节点容量)

问题:
├─ 节点可能无法容纳 10  Pod(总 limit 40 CPU 但节点仅 64 CPU)
├─ 其他服务受影响
└─ 不是最优方案

优化方案 2:启用 CPUBurst

配置变更:
├─ Pod CPU request: 2
├─ Pod CPU limit: 2(保持)
├─ CPUBurst 启用: true
├─ burst_ratio: 1.5
├─ 副本数: 10

CPUBurst 的工作方式:
├─ 正常时: Pod 使用 1-2 CPU(不超过 limit)
├─ 峰值时: 检测到节点有空闲 CPU
  └─ 允许 Pod 使用 3 CPU (2 × 1.5)
  └─ 持续 10 秒(burst 时间)

效果:
├─ 低谷时: 0.5 CPU × 10 = 5 CPU 总(节点利用 < 10%)
├─ 平均时: 1.5 CPU × 10 = 15 CPU 总(节点利用 < 25%)
├─ 峰值时: 2-3 CPU × 10 = 20-30 CPU 总(峰值可突破 limit)
└─ API P99 延迟: 300ms  100ms ✅(接近目标)

优化方案 3:智能副本调整

问题:固定 10 个副本不够灵活
解决:
├─ 启用 HPA (Horizontal Pod Autoscaler)
├─ 目标 CPU 利用率: 70%
├─ Min Pod: 10, Max Pod: 20

效果:
├─ 低谷时 (0.5 CPU 需求):
  ├─ HPA 缩容: 10  Pod  5  Pod
  ├─ 节点 CPU 利用: 5 × 1.5 / 64  12%(降低)
  └─ 成本降低 50%

├─ 平均时 (1.5 CPU 需求):
  ├─ 10  Pod (1.5 × 10 = 15 CPU)
  └─ 利用率: 15 / 64  23%

└─ 峰值时 (4 CPU 需求):
   ├─ HPA 扩容: 10  Pod  20  Pod
   ├─  CPU: 2 × 20 = 40 CPU(假设 burst 后平均 2 CPU)
   └─ 利用率: 40 / 64  62%

综合效果:

指标                初始配置    优化后
─────────────────────────────────────
P99 延迟            300ms      90ms
throttle_ratio      25%        < 1%
CPU 利用率 (平均)   30%        23%
Pod  (平均)       10         8(HPA 缩容)
成本                100%       75%(因副本减少)

最终方案:
└─ 启用 CPUBurst + HPA 结合
└─ 既保障性能(P99 延迟达标)
└─ 又降低成本(副本减少)

12.5 生产案例 2:后台任务的 Throttle 优化

场景:离线数据处理任务,可容忍延迟

初始配置:
├─ 任务类型:数据导出、ETL
├─ CPU limit: 4
├─ 运行周期: 8 小时
├─ 目标:在 8 小时内完成

问题现象:

监控发现:
├─ throttle_ratio: 60%(非常高!)
├─ 实际任务时间: 8 小时 + 2 小时(= 10 小时,超期)
├─ 原因:BE Pod 被频繁抑制,实际 CPU 不足

分析:

时间线:
├─ T=0-2h: 与 LS Pod 竞争,被大幅抑制,throttle 60%
├─ T=2-8h: LS 负载下降,抑制减轻,throttle 30%
├─ T=8h: 应该完成,但只完成了 80%
└─ T=8-10h: 继续运行完成剩余 20%

原因:
└─ BE Pod 的 CPU 配额被动态调整
└─ 峰值时被限制到 1.6 CPU(60% throttle)
└─ 无法在规定时间内完成

优化方案 1:增加副本并行处理

配置变更:
├─ 从 1 个 Pod(4 CPU)改为 4 个 Pod(每个 2 CPU)
├─ 并行处理不同的数据块
├─ 总 CPU: 8(从 4 增加)

效果:
├─ 虽然总 CPU 增加
├─ 但分散的 Pod 被抑制的程度降低
├─ throttle_ratio: 60% → 40%
├─ 任务时间: 10h → 7h (提速 43%)

优化方案 2:调整任务优先级

使用 Koordinator 的 Priority:
├─ 给数据导出任务设置高 Priority (-10)
├─ QOSManager 看到优先级,减轻抑制
├─ 允许高优先任务获得更多 CPU

效果:
├─ throttle_ratio: 60% → 35%
├─ 任务时间: 10h → 6.5h
├─ 同时不影响 LS Pod(LS 有更高的 QoS)

优化方案 3:错开运行时间(最经济)

配置变更:
├─ 原方案: 全天候运行(与 LS 竞争)
├─ 新方案: 仅在低峰期运行(晚上 10 点到早上 6 点)
├─ 运行时间: 8 小时(集中在低峰)
├─ 不竞争,无 throttle

效果:
├─ throttle_ratio: 60% → 0% ✅
├─ 任务时间: 8h(无 throttle,正常)
├─ CPU 利用率: 完全利用,无浪费
├─ 成本: 最低(仅需 1 个 Pod,无并行开销)

综合对比:

方案            Pod 数   CPU    完成时间  throttle  成本
─────────────────────────────────────────────────────
初始方案        1       4      10h      60%      4×10h=40
增加副本        4       8      7h       40%      8×7h=56
调整优先级      1       4      6.5h     35%      4×6.5h=26
错开时间        1       4      8h       0%       4×8h=32

最优方案:
└─ 错开时间(成本最低,性能最好)
└─ 或调整优先级(平衡性能和成本)

生产调优指南

12.6 Throttle 监控和告警

# Throttle 监控配置
apiVersion: monitoring.coreos.com/v1
kind: PrometheusRule
metadata:
  name: cpu-throttle-alerts
spec:
  groups:
  - name: cpu-throttle
    interval: 30s
    rules:
    # 警报 1: 高 Throttle 比率
    - alert: HighCPUThrottleRatio
      expr: |
        (container_cpu_cfs_throttled_seconds_total / 
         container_cpu_cfs_periods_total * 100) > 20
      for: 5m
      annotations:
        summary: "Pod {{ $labels.pod }} 有高 throttle 比率"
        description: "Throttle 比率 {{ $value }}%,高于 20% 阈值"
    
    # 警报 2: Throttle 时间过长
    - alert: HighCPUThrottleTime
      expr: |
        rate(container_cpu_cfs_throttled_seconds_total[5m]) > 0.1
      for: 5m
      annotations:
        summary: "Pod {{ $labels.pod }} throttle 时间过长"
        description: "每秒 throttle {{ $value }}s"
    
    # 警报 3: P99 延迟与 Throttle 相关
    - alert: HighLatencyWithThrottle
      expr: |
        (histogram_quantile(0.99, api_latency) > 100) and
        (container_cpu_cfs_throttled_seconds_total > 10)
      annotations:
        summary: "高延迟且有 throttle,需要调整 CPU 配置"

12.7 调参指南

对于延迟敏感的服务(LS Pod)

# 方案:避免 Throttle,使用充足的 CPU limit
spec:
  containers:
  - name: app
    resources:
      requests:
        cpu: "2"
      limits:
        cpu: "3"  # 比 request 高 50%,留缓冲
    
    # 启用 CPUBurst
    env:
    - name: KOORDINATOR_CPU_BURST_ENABLED
      value: "true"
    - name: KOORDINATOR_CPU_BURST_RATIO
      value: "1.5"  # 可以 burst 到 4.5 CPU

对于吞吐优先的任务(BE Pod)

# 方案:接受 throttle,但需要时间足够完成任务
spec:
  containers:
  - name: batch-job
    resources:
      requests:
        cpu: "2"
      limits:
        cpu: "4"  # BE 可以用完 limit
    
    # 增加运行时间预算
    env:
    - name: JOB_TIMEOUT
      value: "10h"  # 从 8h 增加到 10h,考虑 throttle

12.8 常见问题排查

问题 1:Throttle 比率突然升高

诊断:

1. 检查 CPU limit 是否变化
   $ kubectl get pod -o jsonpath='{.items[].spec.containers[].resources.limits.cpu}'

2. 检查节点 CPU 压力
   $ kubectl top nodes
   $ kubectl top pods

3. 检查 LS Pod 的流量变化
   $ promql: sum(rate(request_total[5m])) by (pod)

4. 检查是否启用了新的 QOSManager 策略
   $ kubectl logs koordlet-xxx | grep -i suppress

原因可能:
├─ 节点 CPU 整体压力增加
├─ LS Pod 流量激增
├─ 新的限流策略生效
└─ Pod 的实际需求增加

解决:
├─ 如果是流量增加,可考虑增加副本
├─ 如果是 CPU 配置不合理,调整 limit
├─ 如果是限流太激进,调整 suppress 参数
└─ 监控 throttle 的趋势,而不是绝对值

问题 2:应用性能下降但监控看不出原因

诊断:

1. 首先检查 throttle 统计
   $ cat /sys/fs/cgroup/cpu/kubepods/pod-xxx/cpu.stat
   
   看是否有高 throttle 比率

2. 检查 CPU 使用率是否接近 limit
   $ promql: container_cpu_usage_seconds_total
   
   如果 CPU 使用率 ≈ limit,说明 throttle 是原因

3. 对比应用的响应时间分布
   $ promql: histogram_quantile(0.99, latency)
   
   如果 P99 大幅高于 P50,可能是 throttle 导致

解决:
├─ 检查 limit 设置是否合理
├─ 查看是否被 BE Pod 抑制
├─ 考虑启用 CPUBurst
└─ 调整应用配置,减少 CPU 需求

12.9 监控指标

# CPU Throttle 关键指标

# Throttle 比率(最重要)
koordlet_container_cpu_throttle_ratio{pod=""}
  = increase(cpu.stat[throttled_time]) / increase(cpu.stat[total_time])

# Throttle 次数
koordlet_container_cpu_throttle_count_total

# Throttle 导致的延迟增加
koordlet_container_cpu_throttle_latency_impact_ms

# 与 throttle 相关的性能下降
koordlet_qos_throttle_related_slo_violations_total

总结 - 章节要点汇总

12.10 关键概念速查

概念含义影响
CPU Throttle超过配额被限制运行延迟增加
Throttle Ratio被 throttle 的周期占比> 20% 需要优化
CPUBurst在有空闲时超过 limit改善冷启动
Suppress主动降低 BE 的 limit保护 LS Pod

12.11 Throttle 优化决策树

场景 1: LS Pod (延迟敏感)
├─ 监测到 throttle > 10%?
│  ├─ 是 → 增加 CPU limit
│  └─ 否 → 启用 CPUBurst 作为保险
│
└─ P99 延迟 > SLO?
   ├─ 是 → 立即增加 CPU
   └─ 否 → 继续监控

场景 2: BE Pod (吞吐优先)
├─ 任务能按时完成?
│  ├─ 是 → 保持现状,充分利用资源
│  └─ 否 → 增加并行副本或错开运行时间
│
└─ 成本是否可接受?
   ├─ 否 → 减少副本,延长运行时间
   └─ 是 → 增加副本加速任务