ZIP 打包、快照合成、skill 整包、图片分割、画布裁剪——这些活每个都攥着大块内存,忙完必须归零。「稳态应归零」四个字就是它们的健康定义。覆盖内存盘 📦 大对象在途组 2 张面板(m106 m107)· 仪表盘 siliang-memory。
把 backend 想成一家搬运公司:ZIP 打包、快照合成、skill 整包、图片分割、画布裁剪是五个搬运工,每人干活时都要抱着一口大箱子(大块内存)。健康的标准简单粗暴——「稳态应归零」:活干完,箱子放回仓库,台账上"正在搬运"的计数回落到 0。
这两个面板就是搬运台账:m106 盯文件/打包三个工,m107 盯图像处理两个工。哪条线忙完不归零,哪个工就攥着箱子不撒手——这就是 RSS 抬升的直接嫌疑人。而且箱子比看上去重得多:一张几 MB 的 PNG,解码后在内存里要占几十 MB。
先认识五个搬运工。有人下单"帮我把整个画布打包成 ZIP"——打包工接过活,把几百张图装进一个巨型袋子(内存里的字节数组),压好、传走、放袋下班。快照工、整包工、分割工、裁剪工同理:接活 → 抱箱子 → 干完 → 放下。所以它们的"在途计数"(active)天然就该是锯齿:忙时往上跳几格,闲时稳稳归零。「稳态应归零」不是要求,是这类活的物理本性——箱子没有理由一直抱着。
再看箱子有多重。磁盘上的 PNG 是压缩过的,几 MB;程序要处理它,必须先"解码"摊开成原始像素——一张几 MB 的 PNG 摊开后能占几十 MB 内存,放大 10 倍不止。所以图像工(m107)抱的箱子格外沉:只要有一个裁剪活挂着不结束,几十 MB 就一直扣在 worker 头上;几个同时挂着,RSS 立刻冲高一截。
最后是排查姿势。内存冲高了怎么办?别翻代码猜——拿 m101 的 RSS 冲高时刻,来 m106/m107 对时间戳:某个工的在途线在同一时刻跳起来、然后一直没归零?嫌疑人就是他。在途回答"谁在搬",下一课的字节分布回答"搬的多大",两图一夹,凶手现形。
| 类比里的东西 | 系统里对应的东西 |
|---|---|
| 搬运工手里的箱子 | 在途计数 active(Gauge):此刻有几个活正在处理,每个活攥着大块内存 |
| 干完放回仓库 | 「稳态应归零」:闲时读数回 0 是这类活的物理本性,不是高标准严要求 |
| 超重的箱子 | 图像解码放大:磁盘上几 MB 的 PNG,解码摊开后在内存里占几十 MB |
| 攥着箱子不撒手 | 在途挂着不归零 = 对象没释放 = RSS 抬升的直接嫌疑人 |
| 快递公司分班组台账 | m106 三条线(ZIP/快照/skill)+ m107 两条线(分割/裁剪),各归各的零,分开看 |
# m106 口径:三条线各画一条(主图谱以 / 简写并列) max_over_time(siliang_file_batch_archive_active[1m]) max_over_time(siliang_snapshot_compose_active[1m]) max_over_time(siliang_openapi_skill_package_active[1m])
# 滞留判据(通用 PromQL):30 分钟内始终 > 0 min_over_time(siliang_file_batch_archive_active[30m]) > 0
# 估算口诀:内存 ≈ 宽 × 高 × 4 字节(RGBA) # 4000×3000 的图 ≈ 48MB —— 与磁盘上几 MB 完全两码事
# 同一 Grafana 时间轴上叠三张图: # m101(RSS 冲高)× m106/m107(谁跳起)× m109/m110(箱子多重)
# 错:sum 五条线;对:分线各自归零各自看 max_over_time(siliang_image_segmentation_active[1m])
# 一问在途,二问体重,三对 RSS——三连击 histogram_quantile(0.95, …) # 下一课的主角
# 分流判据: # 在途=0 且 deriv(RSS[1h])≈0 → 平台期,常态 # 在途>0 且 RSS 抬升 → 滞留实锤
它是什么:三条线 = ZIP 打包 / 快照合成 / skill 整包,各有多少个"正在手里的活"(Gauge)。这些活每个都攥着大块内存——打包要把大量资产装进一个巨型字节数组,快照合成要同时摊开多张图层。
回答什么问题:现在谁的手里正攥着大内存?忙完之后放没放?
✅ 忙完就归零(所以叫"稳态应归零"):三条线都是锯齿,闲时段稳稳贴 0
🚨 有活一直挂着不归零 = 大对象滞留内存 = RSS 抬升的直接嫌疑人。动作:记下 instance 与时刻,下钻代码找没释放的打包/合成任务
# 面板 PromQL(照抄主图谱口径:三线并列) max_over_time(siliang_file_batch_archive_active[1m]) max_over_time(siliang_snapshot_compose_active[1m]) max_over_time(siliang_openapi_skill_package_active[1m])
联动:主图谱 m106 ↗ · 相关课程:内存全局水位(RSS 冲高源头) · 结束原因(这些活干成了没)
它是什么:图片分割 / 画布裁剪正在处理几个(Gauge,两条线)。图像是典型的大对象来源:磁盘上几 MB 的 PNG 是压缩格式,解码摊开成原始像素后要占几十 MB 内存——箱子比标签重十倍。
回答什么问题:RSS 冲高的那一刻,是不是有图像活在处理?这些图像对象用完放没放?
✅ 忙时跳起、闲时归零的锯齿形态;与图像类请求量同步起伏
🚨 挂着不归零 = 图片对象没释放——分割/裁剪的结果图被引用拽着收不走。动作:对照 m101 冲高时刻定位,并检查结果图是否被闭包/注册表拽住
# 面板 PromQL(照抄主图谱口径:两线并列) max_over_time(siliang_image_segmentation_active[1m]) max_over_time(siliang_workspace_crop_active[1m])
联动:主图谱 m107 ↗ · 相关课程:字节分布(箱子到底多重) · 泄漏判定心法
内存冲高不要猜,三张图同一时间轴一摆,搬运工自己现形。
# 第 1 步:锁定冲高时刻(m101 口径) max_over_time(siliang_process_resident_memory_bytes{job="siliang_backend_worker"}[1m]) # 第 2 步:同一时间轴叠上在途(m106/m107 口径)——谁在那一刻跳起? max_over_time(siliang_file_batch_archive_active[1m]) max_over_time(siliang_image_segmentation_active[1m]) # 第 3 步:翻字节分布确认箱子多重(m110 口径,下一课详解) histogram_quantile(0.95, sum(rate(siliang_snapshot_compose_source_bytes_bucket[5m])) by (le)) # 判读:冲高时刻 × 在途跳起 × 箱子体量 = 闭环证据链
「稳态应归零」可以直接翻译成告警:业务空闲时段读数仍大于 0,就是滞留。
# 告警规则:大对象滞留(稳态应归零面板通用写法) groups: - name: siliang-bigobject-retention rules: - alert: BigObjectStuckInFlight expr: min_over_time(siliang_file_batch_archive_active{job="siliang_backend_worker"}[30m]) > 0 for: 0m # min_over_time 已含"持续 30 分钟"语义,不用再 for labels: severity: warning # 同款复制给 snapshot_compose / skill_package / segmentation / crop # 注意:深夜低峰触发可信度最高;白天要结合业务量排除"真在忙"
判读收尾:告警触发后先看该线此前是否锯齿归零——从"会归零"变成"不归零",就是行为变更实锤。
新面板上手第一步,是知道它健康时长什么样。
# 拉 24h 看完整业务周期(m107 口径) max_over_time(siliang_workspace_crop_active[1m]) # 对照请求量确认"忙时"(有活干 >0 是正常的) sum(rate(siliang_http_requests_total[5m])) # 基线结论应长这样:忙时 1~几个在途,闲时归 0,全天锯齿 # 偏离基线的两种坏形态:闲时不归零(滞留)/ 忙时冲到异常高的并发
怀疑图像滞留时,用在途线与字节分布互证,估算内存扣了多少。
# 在途几个活(m107 口径) max_over_time(siliang_image_segmentation_active[1m]) # 源图典型体量(m110 口径:快照源图 p95) histogram_quantile(0.95, sum(rate(siliang_snapshot_compose_source_bytes_bucket[5m])) by (le)) # 心算:p95 若 5MB(压缩态),解码后 ≈ 数十 MB/张;在途 3 个 = 上百 MB 被攥着 # 与 RSS 冲高量级一对照:对得上 = 定案
# 错: alert: archive_active > 0 # 对: alert: min_over_time(archive_active[30m]) > 0
# 错: sum(archive_active, compose_active, …) # 对: 五条线并列,谁不归零谁是嫌疑人
# 错: 3MB 文件 → 内存占用 ≈ 3MB # 对: 4000×3000 × 4B ≈ 48MB,几 MB PNG 解码后占几十 MB
# 错: active=0 且 RSS 高 → 泄漏 # 对: active=0 + deriv(RSS[1h])≈0 → 平台期,常态
# 错: rate(siliang_workspace_crop_active[5m]) # 对: sum by (outcome) (rate(siliang_workspace_crop_outcome_total[5m]))
# 错: 只查 file_batch / snapshot / skill_package # 对: 加上 image_segmentation / workspace_crop 再结案
参考答案:在途 active 数的是"此刻手里攥着几个大活"。活有明确的开始和结束,结束就该放箱子,所以闲时读数必须回 0——不归零说明有活没释放。而 RSS 是全进程的总账,干完活后 allocator 会把内存留着复用(平台期),天然可以停在高位;一个要求"闲时归零",一个允许"平台期",两者不矛盾。
参考答案:磁盘上的 PNG/JPEG 是压缩格式,程序要处理必须先解码摊开成原始像素——内存占用约等于 宽 × 高 × 4 字节(RGBA),和文件大小无关。一张 4000×3000 的图解码后约 48MB。所以图像工(m107)抱的箱子比文件工更沉,在途滞留时 RSS 反应也最猛。
参考答案:① m101 锁定冲高时刻;② 同一时间轴叠 m106/m107 的在途线,看谁在那一刻跳起、之后是否归零——在途回答"谁在搬";③ 叠下一课 m109/m110/m123 的字节分布 p95,确认箱子典型体量——回答"搬多大"。冲高时刻 × 在途跳起 × 箱子体量,三点连成证据链,凶手现形。