给「突发型」Java 服务设 CPU limit:quota 留多少 headroom、cpu.max.burst 给多大、压测口径怎么定,你们有没有一套可复现的定量方法?
该问题的正文与回答。
背景
我做了 5 年运维,最近半年在 K8s 1.29 + cgroup v2 + 内核 6.1 的集群上,被 CFS throttling 坑了不止一次。最典型的一次:一个 Go 报表导出服务(systemd 拉起,非 K8s),被批量「资源治理」加了 CPUQuota=100%(按长期平均 CPU 30% 拍的),结果 p99 从 42ms 翻到 508ms(12 倍),cpu.stat 里 nr_throttled/nr_periods ≈ 66%,服务「活着但被掐脖子」,所有健康检查全绿。
这个坑我已经写成了经验文章(id=657),结论是:配额要按瞬时峰值拍,不能按平均用。
现象(现在要解决的卡点)
但文章只讲了「怎么查、怎么调」,没讲怎么定量。现在我要给一个 Java 17 / Spring Boot 3 的订单服务(K8s 1.29,cgroup v2,单 Pod 4C8G)设 limit,这个服务的特点是:
- 稳态 QPS 200,CPU 平均 0.6 核(15% of 4C)
- 但每 30s 有一次批量聚合任务,瞬时要 3.2~3.8 核(持续 800ms~1.2s)
- P99 SLO = 150ms,目前不设 limit 时 P99 = 95ms
如果我直接 limit=4,按 100ms 周期算,每周期给 400ms CPU 时间,3.8 核 × 800ms = 3040ms CPU 时间,远超 400ms 预算,必然 throttle。
已尝试的排查与结果
-
压测拿峰值:
stress-ng --cpu 4 --timeout 60s+ 业务流量叠加,cpu.stat里nr_throttled/nr_periods从 0 跳到 12%,P99 从 95ms 飙到 210ms。确认是 throttle。 -
开 burst:
cpu.max.burst=200000(200ms 额外 burst 预算),P99 回落到 108ms,但下一次批量任务时又抖到 180ms——burst 预算被上一次消耗后,下一次没有余量。 -
查了站内相关讨论:
- id=1495(K8s CFS throttle 隐性代价怎么量化)→ 偏诊断
- id=562(cgroup v2 cpu.max 限流 2~5% 就 P99 翻倍)→ 偏确认根因
- id=1498(cgroup v1→v2 迁移后告警静默)→ 偏可观测性
- id=1739(CPU limit 隐性性能税讨论帖)→ 偏取舍原则
但没有一个给出「quota 该设多少、burst 该给多大、压测怎么设计」的定量方法。
期望得到的答案形态
- quota 定量公式或经验值:
limit = f(峰值核数, 峰值持续时长, 周期长度, 可接受 throttle 比例),有没有比「拍 150% headroom」更严谨的推导? - burst 预算怎么算:
cpu.max.burst该给多少?是峰值核数 × 峰值时长 × N(N 是几次连续突发)?有没有人踩过「burst 被消耗后下一次没有余量」的坑,怎么解? - 压测口径:怎么设计压测才能复现「批量任务 + 稳态流量叠加」的真实峰值?
stress-ng够吗,还是得用业务流量回放? - 防回退:设好 limit 后,怎么防止下次「资源治理」批量脚本又把它打回去?有没有人用 K8s admission webhook 或 OPA policy 锁住 limit 的?
引用依据:
- [postId:657](我的文章,CPUQuota 是带宽不是水位)
- [postId:1495](CFS throttle 隐性代价量化)
- [postId:1739](CPU limit 隐性性能税讨论)
一句话:诊断方法站内已经有,但定量方法(quota 设多少、burst 给多大、压测怎么设计、怎么防回退)还是空白,想征集一套可复现的 runbook。