🛡 防护与缓存:护栏拦了什么(内存盘 2 图)

四道安检门把「超规行李」拦在内存外面(上传预检 / 媒体下载 / XAI 生图 / provider 响应超限);一座储物柜让常用快照免搬运(读快照缓存命中)。拦截与命中,一攻一守都是内存防线。覆盖 m114 · m127 · 后端内存仪表盘「🛡 防护与缓存」组(内存盘 2 图)。

🛡 四道安检门 + 一座储物柜:超大行李拦在门外,常用行李存柜免搬 过检 合规 超规 进门行李(内存炸弹候选) ① 用户上传文件 upload_precheck 预检 ② 媒体下载流 media_download 下载中 ③ XAI 生图返回 b64 文本天然大 33%+ ④ provider 生图响应 分辨率变大 → 体积暴涨 上限安检门 ×4 逐件称重,超规就拒收 上传预检 / 媒体下载 XAI 生图 / provider 响应 合规放行 → 正常处理 超规拦截 → rejected_total +1 目的:内存峰值不被超大文件击穿 (看速率 rate,别看 _total 累计) 放行(多数请求) 内存峰值仍在设计范围内 拦截(少数超规行李) 拦截本身 = 护栏在工作 高频拦截 = 上游东西异常大 → 查源头 读快照请求 read snapshot 先查储物柜(缓存) siliang_read_snapshot_cache_total 按 cache × result 记账 命中 hit → 直接返回 省一次读取:省时又省内存 未命中 miss → 回源 首次读必 miss,冷启动低属正常 回源读取原图 读取压力与内存峰值都在这 事故现场:命中率突然崩了 = 缓存逻辑被改动 / 失效 → 读取压力与内存抖动跟着上来(先查缓存相关发布,再谈加缓存) 高频拦截同理是「上游异常」信号:把拦截速率曲线对时间戳,找上游哪天开始给的东西变大 / 变坏。 口径:拦截看速率(rate,别看 _total 累计);缓存看 hit/(hit+miss) 形态,突变即回归。

💡 一句话理解

内存防线有一攻一守两件套。守:四道安检门——上传预检、媒体下载、XAI 生图、provider 响应各设大小上限,超规行李直接拒收(rejected_total +1),保护内存不被超大文件击穿。拦截本身=护栏在工作,不是事故;但高频拦截说明上游给的东西异常大,值得查源头。攻后余粮:储物柜——读快照先查缓存,命中就省一次读取(省时省内存);命中率突然崩了=缓存逻辑被改动/失效,读取压力和内存抖动会跟着上来。

🧩 费曼拆解 讲给完全没接触过的小白

先说安检门为什么要存在。图片这东西进门时和拆开后完全两回事:一张几 MB 的 PNG,解码成像素后要占几十 MB 内存;XAI 生图如果用 b64_json 格式返回,图片会被塞进 JSON 文本里,天然比 URL 形态大 33%+。也就是说,不经检查放进来一个「小」文件,内存里可能立刻多出几十个「大」对象——这正是以前内存冲高的经典嫌疑人。所以四个入口各设一道门:用户上传什么、我们从 COS 下载什么、xAI 和 provider 回给我们什么,都先称重,超规就拒收。

看这张图最反直觉的一点:拦截曲线不是坏消息。护栏拦下了超规行李,说明防线在工作、内存保住了——拦一下是设计内的正常行为。真正的坏消息藏在「速率」里:偶尔拦一下无所谓;拦的频率突然持续升高,说明上游开始批量给异常大的东西——要么用户在上传怪文件,要么 provider 悄悄换了默认分辨率。这时候要顺着被哪道门拦的(指标名各不相同)去查源头。

再说储物柜。快照读是很高频的动作,每次都回源读原图,既慢又把读取压力和大对象内存峰值一次次拉起来。所以读快照先查缓存:命中(hit)直接返回,省一次读取;未命中(miss)回源读一次——首次读必 miss,冷启动命中率低属正常。要警惕的是形态突变:命中率一直好好的,某天突然崩了——十有八九是缓存逻辑被改动或失效(发布回归),而不是流量变了。读取压力和内存抖动会紧接着上来。

类比里的东西系统里对应的东西
四道安检门上传预检 / 媒体下载 / XAI 生图 / provider 响应超限,各一道大小上限拦截
被拒收的超规行李siliang_upload_precheck_rejected_total 等四个 rejected_total 计数器(Counter)
安检门的工作日志rate(...rejected_total[5m]):看「每秒拦几件」,不看累计总数
储物柜读快照缓存,按 cache × result 记账(siliang_read_snapshot_cache_total)
行李牌命中,直接取包hit:省一次回源读取——省时省内存
柜子被撬 = 牌子全废命中率突然崩 = 缓存逻辑被改/失效 → 读取压力与内存抖动跟着上来

🛠 动手验证(4 步,在 Grafana 里亲手做一遍)

  1. 打开 m114 上限拦截速率——确认四道门各自的线都长期贴地(偶发脉冲正常);记住每条线对应哪道门。
  2. 找一段拦减压高的时段放大看——分辨是哪一道门在拦:是用户上传(预检),还是 provider 响应(上游变大)?来源不同,动作完全不同。
  3. 打开 m127 读快照缓存命中——按 cache × result 分线,确认 hit 是主流、miss 占比稳定;冷启动时段 miss 偏高属正常。
  4. 对一次发布——挑最近一次动过缓存/读取逻辑的发布时间点,看 hit/miss 形态在前后有没有突变。没有突变=缓存健在。

🧠 必知必会 看懂本课全部面板的地基

为什么要有护栏
图片进门小、拆开大:几 MB 的 PNG 解码后占几十 MB;b64_json 格式天然比 URL 大 33%+。不拦超规文件,内存峰值随时被一个文件击穿。
# 数字都来自主图谱字节分布卡(m107/m123 的教训)
# 一张几 MB 的 PNG → 解码后几十 MB 内存
# b64_json 塞进 JSON 文本 → 比 URL 形态大 33%+
四道门各守哪个口
上传预检(用户上传)/ 媒体下载(COS 拉流)/ XAI 生图(生图返回)/ provider 响应(各家生图响应超限)。四个指标名各不相同,按门分线。
# 四道门的计数器(主图谱口径,rate 化看速率)
siliang_upload_precheck_rejected_total
siliang_media_download_rejected_total
siliang_xai_image_response_rejected_total
siliang_provider_image_response_rejected_total
拦截 = 护栏在工作
面板原话:拦截本身=护栏在工作;但高频拦截说明上游给的东西异常大,值得查源头。别把拦截曲线当错误率报警。
# 偶发脉冲:设计内行为,不用管
# 持续抬升:上游异常信号,按门查源头
rate(siliang_upload_precheck_rejected_total[5m])   # → 哪道门在拦
Counter 必配 rate
rejected_total 是计数器(只涨不跌),直接读累计数毫无信息量;配 rate 才是「每秒拦几件」。
# 错: 直接画 _total 累计——永远上扬的直线
# 对: rate(siliang_media_download_rejected_total[5m])   # → 拦截速率
储物柜记账法
读快照缓存按 cache × result 两个维度记账:哪个缓存 × 命中还是没命中。hit=省一次读取,miss=回源一次。
# 主图谱口径:按 cache, result 拆线
sum by (cache, result) (rate(siliang_read_snapshot_cache_total[5m]))
命中率怎么看
命中率 = hit ÷ (hit+miss)。看的是形态:稳定的高位平台是健康;突然崩塌 = 缓存逻辑被改/失效,先查发布。
# 命中率(形态突变比绝对值重要)
sum by (cache) (rate(siliang_read_snapshot_cache_total{result="hit"}[5m]))
  / clamp_min(sum by (cache) (rate(siliang_read_snapshot_cache_total[5m])), 1)
miss 不是坏事
首次读必 miss;冷启动、新快照入柜前 miss 偏高都正常。怕的不是 miss 存在,而是 miss 占比突变。
# 判读口诀
# miss 稳定占比 → 正常 · miss 突然吃掉 hit → 柜子被改了

📋 逐面板精讲 2 张图一张不落

🟢 上限拦截速率

它是什么:四道安检门各自「每秒拦下几件超规行李」的速率曲线:上传预检 / 媒体下载 / XAI 生图 / provider 响应超限。它们是护栏的工作日志——护栏保护内存不被超大文件击穿,这张图记录它每次出手。

回答什么问题:超大的东西有没有在被拦住?最近上游给的东西是不是变大了、变坏了?(拦截频率就是上游尺寸的「体温计」。)

✅ 健康长啥样:四条线长期贴地,偶发脉冲(个别用户传大文件被拦)后回落;这正是「护栏在工作」的常态。

🚨 危险长啥样:某道门拦截速率持续抬升 = 上游批量给异常大的东西。动作:按门找源头——上传预检高频拦,查用户侧是不是在传怪文件;XAI / provider 门高频拦,对时间戳看字节分布面板(m123),先怀疑上游换了默认分辨率或返回格式(b64 比 URL 大 33%+)。

# 主图谱口径:四道门各一条速率线(Counter → rate)
rate(siliang_upload_precheck_rejected_total[5m])
  + rate(siliang_media_download_rejected_total[5m])
  + rate(siliang_xai_image_response_rejected_total[5m])
  + rate(siliang_provider_image_response_rejected_total[5m])

联动:主图谱 · 上限拦截速率 ↗ · 相关课程:字节分布(对时间戳找膨胀源头) · rate():从里程表算出速度

🟢 读快照缓存命中

它是什么:读快照先查缓存(储物柜)的记账本:按 cache × result 看命中形态——哪个缓存、命中(hit)还是没命中(miss)。命中了就省一次回源读取,省时又省内存;miss 则要回源读原图,读取压力和内存峰值都发生在那一侧。

回答什么问题:「读快照」这条高频路径还在被缓存保护着吗?缓存逻辑有没有被最近的发布悄悄改坏?

✅ 健康长啥样:hit 是主流、miss 占比稳定;冷启动或新快照集中读取的时段 miss 偏高、随后回落,属于正常波动。

🚨 危险长啥样:命中率突然崩了 = 缓存逻辑被改动/失效,读取压力和内存抖动会跟着上来。动作:第一反查最近动过缓存/读取路径的发布(这是回归信号,不是流量问题),确认后回滚或修复;别急着「加缓存」。

# 主图谱口径:按 cache × result 拆线看形态
sum by (cache, result) (rate(siliang_read_snapshot_cache_total[5m]))

联动:主图谱 · 读快照缓存命中 ↗ · 相关课程:连接池台账(互链课) · 形态突变判读心法

🏭 生产实战 real world

场景 1 · 上传预检拦截突然变密:先分清「谁在传什么」

预检门的拦截速率抬升,可能是个别用户传大文件(无害),也可能是某类客户端在批量传超规文件(要查)。分密度、分维度看。

# 预检门拦截速率(贴地=正常;持续抬升=上游异常)
rate(siliang_upload_precheck_rejected_total[5m])
# → 偶发脉冲:个别用户行为,不用管
# → 持续 > 0 的平台:批量超规,值得查源头(哪类文件/哪个入口)

# 旁证:被拦说明内存保住了,RSS 不应有对应冲高
max_over_time(siliang_process_resident_memory_bytes[1m])
# → 拦截升高同时 RSS 平稳 = 护栏在正确工作

判读:拦截升、RSS 平 = 护栏立功,去产品侧了解上传行为;拦截升、RSS 也冲高 = 有东西绕过了门,立刻查。

场景 2 · provider 生图响应膨胀:两图对时间戳

症状:内存午后开始冲高。嫌疑人之一是 provider 生图响应变大(换分辨率/换格式)。把「拦截门」和「字节分布」两张图对时间戳。

# 门 1:provider 响应门有没有在拦
rate(siliang_provider_image_response_rejected_total[5m])
rate(siliang_xai_image_response_rejected_total[5m])

# 门 2:放行进来的响应是不是也变大了(XAI 生图响应字节 P95)
histogram_quantile(0.95,
  sum(rate(siliang_xai_image_response_bytes_bucket[5m])) by (le))
# → P95 抬升 + b64 格式天然比 URL 大 33%+:先怀疑上游换格式/分辨率

判读:拦截没动但字节 P95 抬升 = 放行的东西在变大,内存冲高时间戳对得上即实锤,联系上游或调整门限。

场景 3 · 缓存命中率监控:突变即回归

命中率看形态不看绝对值:把 hit 占比画出来,崩塌点几乎总是某次发布。

# 命中率 = hit / (hit + miss),按 cache 拆
sum by (cache) (rate(siliang_read_snapshot_cache_total{result="hit"}[5m]))
  / clamp_min(sum by (cache) (rate(siliang_read_snapshot_cache_total[5m])), 1)
# → 稳定平台=健康;断崖=缓存逻辑被改/失效,第一反查发布

# 告警示例:命中率 10 分钟均值跌破基线一半(示例阈值,按基线调整)
- alert: SnapshotCacheHitRateCollapse
  expr: |
    sum by (cache) (rate(siliang_read_snapshot_cache_total{result="hit"}[10m]))
      / clamp_min(sum by (cache) (rate(siliang_read_snapshot_cache_total[10m])), 1) < 0.4
  labels: {severity: warning}

场景 4 · 内存冲高排查里的「排除法」一步

内存盘查冲高的通用链路里,这两张图是「排除嫌疑」用的:护栏拦住了吗?缓存还在省读取吗?两个都正常,就把怀疑让给大对象在途(m106/m107)。

# 冲高时刻 T 的两连问
# 问 1:T 附近护栏拦了没有(拦了=超大东西被挡在门外,内存不该涨)
rate(siliang_media_download_rejected_total[5m])

# 问 2:T 附近缓存崩了没有(崩了=回源读取暴增,内存抖动跟着来)
sum by (result) (rate(siliang_read_snapshot_cache_total[5m]))

# 两问都正常 → 转向大对象在途面板(文件/图像模块谁在干活)
max_over_time(siliang_file_batch_archive_active[1m])

⚠️ 常见坑 pitfalls

坑 1 · 把拦截当错误报警 — 看到 rejected_total 涨就 pager。原因:拦截=护栏在工作,是设计内行为;只有「高频持续拦截」才是上游异常信号。正解:偶发脉冲不理,持续平台才查源头。
# 错: increase(…_rejected_total[5m]) > 0 即报   # 护栏天天立功天天挨报
# 对: rate(…_rejected_total[5m]) 持续抬升才升级
坑 2 · 直读 _total 累计 — 拦截数画出来是永远上扬的直线,毫无信息量。原因:它是 Counter(只增不减),累计数不代表「现在」。正解:必配 rate 看速率。
# 错: siliang_upload_precheck_rejected_total   # 一条只涨的直线
# 对: rate(siliang_upload_precheck_rejected_total[5m])
坑 3 · 四道门混成一条线看 — 把四个 rejected 加总画一条线,涨了不知道谁拦的。原因:四道门守不同入口,源头完全不同(用户上传 vs 上游 provider)。正解:按门分线(四个指标名本就不同),对号入座查源头。
# 错: sum(rate(所有 rejected_total[5m]))          # 只知道"拦了",不知道谁拦
# 对: 每道门单独一条 rate 线,按门定位来源
坑 4 · 命中率低就加缓存 — 看到命中率掉就申请加缓存层。原因:命中率突变几乎总是逻辑被改/失效(回归),加缓存治不了回归还添复杂度。正解:崩塌先对发布时间戳,回滚/修复优先。
# 错: hit 率崩 → "再包一层缓存"
# 对: hit 率崩 → 对时间戳查发布 → 修复回归
坑 5 · 把冷启动的 miss 当故障 — 服务重启后命中率低被当成缓存坏了。原因:首次读必 miss,柜子要靠流量重新焐热。正解:重启后给焐热窗口,看 miss 占比是否逐步回落。
# 错: 重启后 10 分钟 hit 低 → 判定缓存失效
# 对: 看 miss 占比趋势:逐步回落=焐热中;横住不落=真坏了
坑 6 · 把 m114 和字节分布面板割裂 — 只看「拦了几件」,不看「放行的有多大」。原因:护栏只拦超规的,放行部分的尺寸变化(P95 抬升)才是内存冲高的主因。正解:拦截速率 × 字节分布两图对时间戳一起读。
# 错: 只看 rejected 曲线下结论
# 对: rejected 速率 + histogram_quantile(0.95, …_bytes_bucket) 对照

🎓 费曼自测 合上书,能讲出来才算懂

Q1 · 「拦截速率升高」到底是好消息还是坏消息?

参考答案:分两层。拦截本身是好消息——护栏把超规行李挡在内存外面,说明防线在工作。但拦截频率持续升高是坏消息的信号:上游开始批量给异常大的东西(用户传怪文件,或 provider 换了分辨率/格式),内存虽然这次保住了,源头值得去查。

Q2 · 为什么图片这类「进门时小、拆开后大」的东西要特别设门?

参考答案:因为内存按「拆开后」算,不按进门时算。一张几 MB 的 PNG 解码成像素后占几十 MB;XAI 用 b64_json 返回时图片被塞进 JSON 文本,天然比 URL 形态大 33%+。不设门的话,一个「看起来不大」的文件进门就能把内存峰值顶穿——四道门就是在每个入口先称重。

Q3 · 读快照缓存命中率突然崩了,你的第一反应是什么?为什么不是「加缓存」?

参考答案:第一反应是查最近动过缓存/读取逻辑的发布——命中率长期稳定后突然崩塌,几乎总是缓存逻辑被改动或失效(回归),而不是流量变化。加缓存治不了回归:柜子本身坏了,再买一个柜子还是坏的。正确顺序是修复/回滚,确认命中率回到平台期。

← 上一课:🕸 WS 会话内存:登记簿与背包 📚 课程目录 下一课:👷 RAG / Review Worker 体检单 →