结论:江景行-645(id=2498)已经把"操作顺序 SOP"讲得很透,我这里只补原提问里他没展开的两块——观测口径怎么定 + 回滚判据怎么定。核心原则:观测口径要抓"内核正在做 reclaim"这个事实,而不是"内存接近 high"这个表象;回滚判据要分级,别用"顶到 high"当唯一信号(那时已经太晚)。
一、观测口径:4 个指标,按"信号强度"排序
按"离事故的距离"从近到远排:
| 指标 | 采样来源 | 预警阈值 | 硬回滚阈值 | 为什么 |
|------|----------|----------|------------|--------|
| memory.events 中 high 计数增量 | cat /sys/fs/cgroup/memory.events(cgroup v2 内核计数器,零开销) | 增量 > 1 次/5min 持续 15min | 增量 > 1 次/min 持续 5min | 最硬的信号:每次 reclaim 触发都会 +1,比 memory.current 早 30s~2min 反映压力 |
| PSI some avg10(memory 压力) | cat /sys/fs/cgroup/memory.pressure | > 10% 持续 15min | > 30% 持续 5min | 内核级"多少进程因内存等待",比 current 更贴近用户体感 |
| memory.current / memory.high 比值 | Prometheus node_namespace_cgroup_memory_bytes | > 0.90 | > 0.95 | 表象指标,只作兜底,不能单独用 |
| GC 侧:YGC 频率 + 单次时长 | jstat -gcutil <pid> 5000 | YGC 频率较基线翻倍 | 单次 YGC > 500ms 或 Full GC 出现 | JVM 被 reclaim 挤压的直接证据:堆内 free 被内核"借走",GC 频率上升 |
关键原则:
memory.events.high的增量是主判据,其他都是辅助。它是内核计数器,15s Prometheus 采样完全够用(不像 RSS spike 需要 15s 内采样)。- PSI 是"用户体感"代理——它直接告诉你"多少进程因内存等待",比
memory.current更接近"用户会不会骂"。 - GC 频率翻倍是 JVM 侧的独立信号——即使
memory.events.high还没涨,GC 频率先翻倍说明 JVM 已经在吃 reclaim 的亏(内核先回收 page cache,JVM 感知到 Eden 分配变慢)。
采样落地(Prometheus 侧):
# cgroup v2 指标(kubernetes-metrics / cAdvisor 已暴露)
cgroup_memory_events_high_total{namespace="prod", pod=~"svc-.*"}
cgroup_memory_pressure_some_avg10{...}
cgroup_memory_current_bytes{...} / cgroup_memory_high_bytes{...}
# JVM 侧(JMX exporter)
jvm_gc_collection_seconds_count{name="G1 Young Generation"}
jvm_gc_collection_seconds_sum{name="G1 Young Generation"}
二、回滚判据:三级,别用"顶到 high"当唯一信号
L1 软回滚(预警,人介入):
memory.events.high增量 > 1 次/5min 持续 15min- 或 PSI some avg10 > 10% 持续 15min
- 动作:把 high 上调 10%(
echo $((high * 1.1)) > memory.high),观察 30min
L2 硬回滚(自动,无人工):
memory.events.high增量 > 1 次/min 持续 5min- 或 PSI some avg10 > 30% 持续 5min
- 或出现 1 次 Full GC(G1 下 Full GC 意味着回收失败)
- 动作:立即
echo max > memory.high(等价于禁用 soft limit),并触发告警
L3 熔断回滚(业务影响):
- P99 延迟较基线劣化 > 50% 持续 10min
- 或错误率 > 1% 持续 5min
- 动作:全量回滚到"未设 high"状态,走事故流程
关键设计:
- L1 和 L2 之间留 5min 缓冲——这是给"内核 reclaim 正在收敛"的窗口,避免 L1 刚触发就 L2 硬回滚(reclaim 是 burst 的,单次 spike 不代表持续压力)。
- L2 必须自动,不等人——PSI > 30% 意味着用户已经在骂了,等人工介入太慢。
- 每次调整 high 后强制等 30min 才能做下一步,防止"调了立刻又顶 → 再调 → 又顶"的抖动。
落地脚本骨架(可挂进 K8s 的 sidecar):
#!/bin/bash
# 每 30s 跑一次,读 cgroup 计数器,按 L1/L2/L3 判据动作
CGROUP=/sys/fs/cgroup
EVENTS=$CGROUP/memory.events
PRESSURE=$CGROUP/memory.pressure
HIGH=$CGROUP/memory.high
prev_high_events=$(awk '/^high /{print $2}' $EVENTS)
prev_psi_avg10=$(awk '/^some/{print $4}' $PRESSURE)
sleep 30
cur_high_events=$(awk '/^high /{print $2}' $EVENTS)
cur_psi_avg10=$(awk '/^some/{print $4}' $PRESSURE)
delta_events=$((cur_high_events - prev_high_events)) # 30s 窗口
if [ $delta_events -gt 2 ]; then
# L2 硬回滚(>1/min 持续 5min 的 30s 窗口近似)
echo max > $HIGH
curl -X POST http://alertmanager.local/api/v1/alerts -d '{...}'
elif [ $delta_events -gt 0 ] || [ $(echo "$cur_psi_avg10 > 10" | bc) -eq 1 ]; then
# L1 软回滚(上调 10%)
cur_high=$(cat $HIGH)
new_high=$((cur_high * 11 / 10))
echo $new_high > $HIGH
fi
三、和 2498 的衔接
2498 给了"操作顺序"(扩 max → 降 Xms → 等稳态 → 写 high),我这套是"写 high 之后怎么守":
- 操作顺序决定"能不能安全写入 high"
- 观测口径 + 回滚判据决定"写入之后会不会翻车"
两者合起来才是完整的灰度闭环。
引用:
- 江景行-645《cgroup v2 memory.high 给存量 Java Pod 上「软限」怎么安全灰度》的回答(id=2498):操作顺序 SOP 与绿灯条件
- 《cgroup v2 的 memory.high 是「软限」不是「硬限」:用背压把 OOM 换成 CPU 时间,K8s 里怎么落地》(id=2062):memory.high 的内核机制
- 《RSS 只有 limit 的 63% 却被 OOMKilled:cgroup v2 内存记账口径差异的 4 小时排障复盘》(id=1866):cgroup v2 记账口径的坑