酒店类比:VMS = 登记 100 间(虚高只参考)· RSS = 真住 3 间(进程面板主口径)· working_set = 地板上正在摊的(内核 OOM 判定口径)。覆盖 内存盘 m101 / m100 · biz 盘 b9 · 容器盘 k1 / k2。
你去酒店办入住,说「我们团可能要 100 间房」。前台在本子上记下 100 间——这只是登记(VMS,虚拟内存),一间都没真给你;你们实际住了 3 间——这是真占用(RSS,常驻内存);而酒店消防检查看的既不是登记簿也不是入住总数,是你房间地板上摊开正在用的东西(working_set)——地板满到没地方下脚(顶到限额),才会被赶出去(OOM)。
三种口径各管一摊:看进程内存问题(m101、b9)主看 RSS 的形态;判断容器会不会被 OOM 杀(k1、k2)只认 working_set;VMS 永远虚高好几倍,只做参考、绝不报警。混用口径的代价是:要么被虚高的数吓一跳(误报),要么真出事时盯错了表(漏报)。
先说「登记 100 间」的 VMS。操作系统给每个进程一个巨大的虚拟地址空间,程序申请内存、映射文件,都只是先在账本上记账,真正的物理内存要等真的读写那块地址才分配——就像酒店前台先记下「可能要 100 间」,实际一间没给。所以 VMS 几乎永远比真实占用大得多,动辄是 RSS 的好几倍。看图时它是那条高高的、没什么起伏的参考线:看看就好,别拿它说事。
再说「真住 3 间」的 RSS——真正被加载进物理内存、此刻正被进程使用的部分。操作系统说「这个进程占了多少内存」,说的就是它。内存泄漏、内存冲高、涨没涨回没落,看的都是它。内存盘 m101、biz 盘 b9、worker 体检单 m128,主线全是 RSS。看它也不是看单点数值,而是看形态:锯齿(用完就还)健康、线性爬升(只进不出)是病、冲高后平台期是分配器留着复用的常态脾气。
最后是「地板」working_set。Linux 会把最近读过的文件顺手缓存在内存里(page cache),像你把菜谱贴满冰箱门——需要时随时能撕,不算真占。容器视角的 usage_bytes 把这些也算了进去,所以虚高;而 working_set 是减掉「可回收部分」之后的值,和内核 memcg 的 OOM 判定口径完全一致。一句话:「容器会不会被枪毙」这个问题,只有 working_set 有资格回答。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 登记要 100 间(空头支票) | VMS:虚拟地址空间,申请了不等于真给了,虚高只做参考 |
| 真住着 3 间房 | RSS:物理内存真实占用,进程面板(m101/b9/m128)的主口径 |
| 地板上摊开正在用的东西 | working_set:最近用过、扔不掉的部分,内核 OOM 就看它(k1/k2) |
| 冰箱门上的菜谱贴纸(可随时撕) | page cache:可回收的文件缓存,急缺时第一个被扔 |
| 柜子加地板全算上 | usage_bytes:含可回收 cache 所以虚高,别拿来判 OOM |
| 消防按地板判定超载 | memcg OOM 按 working_set 判定:顶到限额就 SIGKILL(下一课) |
# m101 主线:每 worker 的 RSS(1 分钟窗口最大值) max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m])
# m101 副线:只做参考对照,永不设告警 max_over_time(siliang_process_virtual_memory_bytes[1m]) # 别拿它判问题
# k1 口径:每个容器实际装了多少 vs 盒子多大 sum by (service) (container_memory_working_set_bytes) + sum by (service) (container_spec_memory_limit_bytes) # 一实一虚两条线
# 错:拿 usage_bytes 判 OOM → page cache 冲高就误报 # 对:判 OOM 只认 container_memory_working_set_bytes
# k2 口径:容器内存使用率 container_memory_working_set_bytes / clamp_min(sum by (service) (container_spec_memory_limit_bytes), 1)
# 形态定量:每小时涨多少字节(内存盘核心告警口径) deriv(siliang_process_resident_memory_bytes[1h]) # 持续 > 5MB/h = 真累积
# 进程视角(每 worker 一条线) siliang_process_resident_memory_bytes{job="siliang_backend_worker"} # 容器视角(整盒统计)——两套口径,别相加互证 container_memory_working_set_bytes # by (service)
# k 盘暂无数据的原因(口径卡 k0 写明) # cAdvisor 未部署 → container_memory_working_set_bytes 无采集源
① 进程内存 RSS / VMS(m101)——两种口径同屏:RSS 主线看形态,VMS 副线只做参考——「登记 100 间 vs 真住 3 间」的现场:📊 进程内存 RSS / VMS ↗
② 监控口径与告警规则卡(m100)——内存盘使用说明书:instance 规则、两张卡用法、5 条核心告警(含 deriv(RSS[1h]) > 5MB/h)都在这:📖 监控口径与告警规则(先读我)↗
③ biz 进程内存水位(b9)——biz 版 RSS 主线,按容器拆分、每 worker 一条线,形态判读方法完全一样:🟢 biz 进程内存水位(RSS/VMS)↗
④ 容器内存使用 vs 限制(k1)——working_set 实线 vs 限额虚线,一实一虚:使用线贴上限制线 = memcg OOM 倒计时:🟡 容器内存使用 vs 限制 ↗
⑤ 内存使用率(k2)——working_set ÷ limit 的百分比版,80% 黄 / 90% 红,rag(LibreOffice 前科)是头号盯防对象:🟡 内存使用率(working_set / limit)↗
巡检容器的健康度不是看绝对值,是看「装了多少 ÷ 盒子多大」。使用率超 80% 该留意,超 90% 持续 5 分钟就该告警——那是 OOM 前的最后窗口。
# k2 口径:按 service 算容器内存使用率 sum by (service) (container_memory_working_set_bytes) / clamp_min(sum by (service) (container_spec_memory_limit_bytes), 1) # 绝对值余量:还差多少字节到限额(k1 口径的减法版) sum by (service) (container_spec_memory_limit_bytes) - sum by (service) (container_memory_working_set_bytes) # 判读:余量除以限额小于 10% 且 deriv 为正 → 立即处理,别等枪响
90% 是最后防线:给 5 分钟持续时间,滤掉正常冲高的毛刺,抓「爬上去就不下来」的真威胁。
groups: - name: siliang-container rules: - alert: ContainerMemNearLimit expr: | sum by (service) (container_memory_working_set_bytes) / clamp_min(sum by (service) (container_spec_memory_limit_bytes), 1) > 0.9 for: 5m # 持续 5 分钟才响(口径以 k0 建议为准) labels: severity: critical annotations: summary: "{{ $labels.service }} working_set 超 90%:OOM 倒计时"
接到「backend 是不是泄内存了」的反馈,第一动作不是翻 heap dump,是看 RSS 三形态定调:锯齿别慌、平台期是脾气、爬升才立案。
# 第一步:抹抖动看形态(m101 口径) max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m]) # 第二步:deriv 定量(真累积 = 持续大于 5MB/h,口径以 m100 为准) deriv(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1h]) # 判读:deriv 在 0 附近抖 = 呼吸;持续为正且大 = 立案,进在途/哨兵排查链
两者相减就是可回收的 page cache。大文件搬运后这个差值会瞬间拉大——知道它可回收,就不会被 usage 的冲高吓到。
# 可回收部分 = usage - working_set(读文件缓存为主) sum by (service) (container_memory_usage_bytes) - sum by (service) (container_memory_working_set_bytes) # 判读:usage 高但差值大 = 冰箱贴纸多,撕了就回来,不用动手; # working_set 本身高 = 真占用,才需要按限额距离决策
新人常把 VMS 的高位截图发群里喊泄漏。一条查询并排两条线,自己看一遍就终身免疫。
# RSS 与 VMS 同屏对照(m101 的两条线) max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m]) max_over_time(siliang_process_virtual_memory_bytes{job="siliang_backend_worker"}[1m]) # 预期:VMS 恒为 RSS 的数倍且几乎水平 = 登记单,正常; # 该报警的是 RSS 的 deriv,不是任何一条 VMS 线
# 错: 告警 expr 用 siliang_process_virtual_memory_bytes > 8GB # 对: deriv(siliang_process_resident_memory_bytes[1h]) > 5MB/h 持续
# 错: container_memory_usage_bytes / limit > 0.9 就报 # → 误报 # 对: container_memory_working_set_bytes / limit > 0.9 # → 与内核同口径
# 错: 4 个 worker RSS 加起来 6G < 10G → 「不可能 OOM」 # 对: 看 container_memory_working_set_bytes 对 limit 的距离(k1)
# 错: 「k1 没数据 = 不会 OOM」 # → 没电表不等于没用电 # 对: cAdvisor 未部署 → 过渡期盯 m101/m128 的 RSS + deriv
# 错: 看到 34359738368 以为要爆炸 # → 这是 32GiB 的字节数 # 对: 34359738368 / 1024^3 = 32 GiB → 再对 10G 限额判断
# 错: working_set / sum by (service) (limit) # → 分母可为 0 # 对: working_set / clamp_min(sum by (service) (limit), 1)
# 错: 拿 m101 的 RSS 之和去对 k1 的 working_set 找「差值 bug」 # 对: 两把尺子各量各的:RSS 管进程,working_set 管盒子
参考答案:VMS 是在前台登记「可能要住 100 间」——登记不等于给房,虚高只参考;RSS 是实际住着的 3 间房——真实占用,进程面板主口径;working_set 是房间地板上摊开正在用的东西——最近用过、扔不掉的部分,消防(内核 OOM)按它判定超不超载。
参考答案:因为 usage_bytes 把 page cache 也算进去了——系统顺手缓存的文件内容,急缺内存时第一个被扔掉,根本不算真占用。拿 usage 判 OOM 会因缓存冲高天天误报。working_set 减掉了「可回收部分」,和内核 memcg 决定杀不杀容器用的是同一把尺子,所以只有它有资格回答这个问题。
参考答案:完全正常,不用处理。操作系统给每个进程一个巨大的虚拟地址空间,malloc、mmap 都只是先在账本上记账,真正分配要等真的读写——所以 VMS 几乎永远比 RSS 大数倍。它那条高高的水平线就是个「登记单」,看看就好;该报警的是 RSS 的形态和 deriv,跟 VMS 没关系。
参考答案:先问「这是哪个口径的 90%」。进程面板(m101/b9)的数字是 RSS,90% 本身不直接等于危险,要看形态和 deriv(是不是在持续爬);容器面板(k2)的 90% 是 working_set ÷ limit,那才是 OOM 倒计时,超过且持续 5 分钟就要动手。口径不同,同样的数字含义完全不同。