看内存图不看单点数值,看形状——形状只有三种,只有一种是病。这是内存盘所有 RSS 面板的阅读方法,也是 m101(backend RSS)/ b9(biz RSS)/ c7(后台任务哨兵) 这几张图的共同判读口径。
看内存图和看体检报告一样:不看单点数值,看曲线形状。形状只有三种——锯齿(涨跌交替)是健康,用完就还;线性爬升(只涨不跌)是真泄漏,马上下钻;冲高后平台期(停在高位走平)是 Python 分配器的脾气——内存留着复用不肯还给操作系统,不是病。
两个长得很像的"高位",一个是病、一个是脾气:病是斜着的(每小时还在涨),脾气是横着的( deriv 斜率 ≈ 0)。分不清就用核心告警口径 deriv(RSS[1h]) > 5MB/h 来定量。
锯齿 = 图书馆正常借还。程序干活要借内存,干完就还。读者借了还、还了借,图书馆的"在借数"围绕某个水平上下抖——这就是锯齿。看到锯齿你该高兴:借还机制活着,没有书被塞进床底。
线性爬升 = 借书塞床底。有人每借一本书就塞床底,从不还。床底总有塞满的一天——内存被吃爆,最后内核开枪(OOM)。泄漏的本质是"对象已经没用了,但还有引用拽着它,回收机制不敢收":注册表只加不删、任务只创建不结束、全局列表无限 append。判定特征就一条:RSS 线性爬升,永不回落。
平台期 = 餐厅高峰后不辞退服务员。生意高峰雇了 20 个服务员,高峰过了你不会当天全辞退——明天可能还忙,留着排班复用。Python 的内存分配器(pymalloc / glibc malloc)就是这个脾气:用过的内存留着下次用,不着急还给操作系统。所以大流量、大文件处理过后,RSS 常常停在高位走平台——这部分内存会被复用,下次流量来不会再涨,重启才真正清零。它是策略,不是失控。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 图书馆借了还、还了借 | 锯齿形 RSS:忙时涨闲时落,用完就还,健康常态 |
| 借书塞床底,从不归还 | 线性爬升:对象被引用拽着收不走,只进不出 = 真泄漏 |
| 餐厅高峰后不辞退服务员 | 冲高后平台期:allocator 把内存留着复用不还给系统,重启才还 |
| 每天固定涨一斤的体重 | deriv(RSS[1h]) > 5MB/h:斜率持续为正 = 真累积的核心告警口径 |
| 先量体温再决定挂哪个科 | 下钻链(主图谱 triage):RSS 形态 → GC gen2 → 后台任务 → 哨兵 → 大对象在途 |
deriv(RSS[1h]) 画出来:恒为正且 > 5MB/h = 真累积,按下钻链去主图谱 #triage 查。# m101 主口径:max_over_time 抹掉抓取抖动 max_over_time(siliang_process_resident_memory_bytes[1m])
# 判锯齿:比较相邻两个波谷 # min(今天) ≈ min(昨天) → 用完就还,健康
# 判爬升:波谷都在抬高 # min 这小时 > min 上一小时,一整天如此 → 真累积
# 判平台期:冲高后水平走 + deriv ≈ 0 deriv(siliang_process_resident_memory_bytes[1h]) # ≈ 0 → 横着走
deriv(RSS[1h]) > 5MB/h 持续 = 真累积。锯齿的忽正忽负会被 1h 窗口抹平,只有真爬升恒为正。 # 核心告警口径(内存盘 chips 原文) deriv(siliang_process_resident_memory_bytes[1h]) > 5MB/h # 持续 = 抓真累积
# c7 主口径:稳态 <20 绿 / 20-50 黄 / >50 红 sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
# b9 主口径:按容器拆分,每 worker 一条线 max_over_time(siliang_biz_process_resident_memory_bytes[1m])
# 主图谱「backend 内存涨 / 怀疑泄漏」一行: # m101 → m102 → c7/c8 → m103 → m104 → m108/m124/m125 → m106/m107
# 巡检固定项:三个哨兵都应恒 0 max_over_time(siliang_sandbox_tracked_threads[5m]) # 沙箱台账 max_over_time(siliang_minimax_h3_tracked_entries[10m]) # MiniMax 登记簿 max_over_time(siliang_director_tracked_messages[5m]) # 导演组台账
📊 内存盘 · 进程内存 RSS / VMS(m101)↗——三形态判定法的主战场。✅ 锯齿健康、😐 冲高后平台期 = allocator 不归还(常态)、🚨 线性爬升永不回落 = 真泄漏,马上下钻。
🟢 biz 盘 · 进程内存水位 RSS / VMS(b9)↗——同一套心法用在 biz 上:每台机器一张图、每 worker 一条 RSS 线(1 分钟窗口最大值)。
🔴 核心盘 · 后台生成任务数(c7)↗——内存泄漏核心监控(后厨未出餐订单)。历史教训:这些任务曾只进不出把内存吃爆。RSS 爬升时先看它有没有同涨。
📊 内存盘 · GC 各代待回收对象数(m102)↗——比 RSS 更早的预警器。以及主图谱 🚑 排查速查表 ↗ 的「backend 内存涨 / 怀疑泄漏」一行,就是本课下钻链的完整版。
每天两分钟,把 m101 的每条线过一遍。先定性(三形态),再定量(deriv),再找同涨的嫌疑人。
# 第一问:形态——每 worker 一条 RSS 线(m101 口径) max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m]) # 第二问:斜率——横着走还是斜着涨 deriv(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1h]) # 判读:|deriv| 在 0 附近抖 = 锯齿或平台期(健康/常态) # deriv 恒为正且 > 5MB/h = 真累积,进入第三问 # 第三问:同涨嫌疑人——后台任务数(c7 口径) sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"})
人眼盯不住半夜的斜线,让 deriv 替你值夜班——这是内存盘 5 条核心告警里最重要的一条。
groups: - name: siliang-memory-leak rules: - alert: BackendRSSLinearClimb # 线性爬升定量口径:每小时涨超 5MB 且持续 expr: | deriv(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1h]) > 5 * 1024 * 1024 for: 15m # 持续窗口为通用示例,线上以内存盘口径卡(m100)为准 labels: severity: warning annotations: summary: "{{ $labels.instance }} RSS 每小时涨超 5MB:线性爬升=真泄漏,按下钻链排查"
RSS 冲高后停在高位,先别慌。确认斜率归零后,去大对象面板对时间戳——冲高那一刻谁在搬大文件,一对照便知。
# 冲高时刻 t:文件/打包在途有没有活(m106 口径,稳态应归零) max_over_time(siliang_file_batch_archive_active[1m]) max_over_time(siliang_image_segmentation_active[1m]) # 大对象典型体重(m109 口径):解释冲高幅度合不合理 histogram_quantile(0.95, sum(rate(siliang_file_batch_archive_bytes_bucket[5m])) by (le)) # 判读:t 时刻在途 > 0 + 字节分布能对上冲高幅度 → 冲高合理; # 斜率随后归零 → 平台期,重启才还,不是泄漏,结案
判定为爬升后,按下钻链先查最容易"只进不出"的两样:后台任务和 asyncio 协程。
# 后台任务趋势(c8 口径):只涨不跌的斜线 = 泄漏复发 sum(siliang_chat_resume_active_generation_tasks{job="siliang_backend_worker"}) # 拆到 worker 定位:哪个 instance 在堆积 sum by (instance) (siliang_chat_resume_active_generation_tasks) # 协程便签(m103 口径):创建后忘了 cancel 越贴越多 max by (instance) (max_over_time(siliang_asyncio_tasks_active[1m])) # 两条线与 RSS 爬升同形状同起点 → 嫌链锁定
biz 是独立进程,判定心法一字不改:先形态、再斜率、最后找同涨。
# b9:biz RSS 形态(每 worker 一条线) max_over_time(siliang_biz_process_resident_memory_bytes[1m]) # 斜率定量:真累积的分界线同样是 5MB/h(b0 口径卡建议告警) deriv(siliang_biz_process_resident_memory_bytes[1h]) # 互证:流量是不是涨了(b3 口径)——先排除业务增长 sum(siliang_biz_http_requests_in_progress) by (method)
协程、任务、FD 都排除后还找不到嫌疑人?看哨兵面板——历史上修过的泄漏如果被代码回退,会在这里原样复发,非零即违规。
# 哨兵一:沙箱跟踪台账与待清理队列(m108 口径,应恒 0) max_over_time(siliang_sandbox_tracked_threads[5m]) max_over_time(siliang_sandbox_cleanup_tasks_pending[5m]) # 哨兵二:MiniMax H3 两个登记簿(m124 口径,看是否贴近 1024/4096 上限) max_over_time(siliang_minimax_h3_tracked_entries[10m]) by (kind) # 哨兵三:导演组消息跟踪台账(m125 口径,只涨不回落 = N6-4 修复被回退) max_over_time(siliang_director_tracked_messages[5m]) by (kind) # 判读:任一哨兵非零 → 按告警规则处理,对照发布记录找回退的提交
# 错: "RSS 300MB,高不高?" # → 单点无判据 # 对: "6h 窗口看形状:锯齿/平台/爬升?" # → 形态定性质
# 错: RSS 高位 → 立刻重启 # → 把脾气当病治 # 对: deriv(RSS[1h]) ≈ 0 → 平台期,观察即可 # → 横线不是斜线
# 错: 尖峰 = 泄漏 → 半夜报警 # → 被正常冲高骗了 # 对: min(今天) ≈ min(昨天) → 锯齿健康 # → 谷底不抬高就没库存
# 错: siliang_process_resident_memory_bytes # → 裸值毛刺多 # 对: max_over_time(siliang_process_resident_memory_bytes[1m]) # → 干净形态
# 错: sum(siliang_process_resident_memory_bytes) # → 稀释信号 # 对: max_over_time(…bytes[1m]) 不聚合,逐 instance 看 # → 一人一条线
# 错: deriv(siliang_process_resident_memory_bytes[5m]) # → 跟着锯齿乱跳 # 对: deriv(siliang_process_resident_memory_bytes[1h]) # → 抹平锯齿见真斜率
# 错: max_over_time(siliang_process_resident_memory_bytes[1m]) # → 可能混入 server job # 对: max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m])
# 错: 89 张图挨个点开看 # → 无 SOP,时间黑洞 # 对: m101 → m102 → c7 → m103/m104 → m108/m124/m125 → m106/m107
# 错: 只看 RSS 曲线,从不看哨兵 # → 回退了也发现不了 # 对: max_over_time(siliang_sandbox_tracked_threads[5m]) 恒 0 才放行
参考答案:锯齿(涨跌交替)= 健康,借了还、用完就还;线性爬升(只涨不跌)= 真泄漏,对象只进不出,去查在途与哨兵面板;冲高后平台期(停在高位走平)= allocator 不归还,Python 常态,内存留着复用、重启才还,不是泄漏。三种里只有爬升是病。
参考答案:看斜率。deriv(RSS[1h]):平台期是横线,deriv ≈ 0;爬升是斜线,deriv 恒为正。持续 > 5MB/h 就是内存盘核心告警口径的"真累积"。一句话:横着的和斜着的,长得像但性质完全不同。
参考答案:绝对值受业务量、机器、历史平台期影响,没有普适标准;斜率衡量的是"变化趋势"——真泄漏的本质是持续累积。锯齿的正常冲高斜率忽正忽负,会被 1h 窗口抹平;只有真爬升能让 deriv 持续为正。趋势不受基数干扰,所以拿趋势定罪。
参考答案:① m102 GC gen2 待回收——比 RSS 更早的预警器,确认"对象赖着"的时长;② c7 后台生成任务数——历史真实泄漏留下的哨兵,看它是否同涨(>20 黄 / >50 红);③ m103 asyncio 任务数 / m104 线程 FD——协程和句柄是常见的"只进不出"载体。这三步对应主图谱 triage 的固定下钻链。