mzvideo 访问故障与性能排查复盘 (2026-07-26)

一句话:站点完全打不开 → 502 → 恢复但慢到 5 秒。根因是两起独立故障叠加 + 一个架构性性能瓶颈,排查过程本身比结论更值得借鉴。

一、概览

内容
受影响站点https://mzvideo.898311.xyz/ (个人 Quartz 知识库)
故障时间2026-07-26 下午
症状演变完全不可访问 → HTTP 502 → 恢复 200 但响应 1.8~5s
根因① tx2 服务器假死 ② 本机 Quartz 构建死循环 ③ 静态站经 frp+Node 绕路
最终状态服务恢复 200,性能问题待架构优化

二、架构拓扑(理解一切的前提)

公网用户
  │  DNS: mzvideo.898311.xyz → 49.233.210.139 (tx2, 腾讯云北京)
  ▼
tx2 (49.233.210.139, 3.4G 内存)
  │  nginx :443 (TLS 终止)
  │  proxy_pass → 127.0.0.1:15000
  ▼
tx2 frps :15000  ◄── 隧道 bj-5000 的 remotePort
  │
  │  ═══ 公网 frp 隧道 ═══
  ▼
本机 frpc (192.168.3.104, 家用宽带)
  │  localPort 5000 ↔ remotePort 15000
  ▼
本机 127.0.0.1:5000
  │  quartz.service: npm run dev = npx quartz build --serve --port 5000 (Node)
  ▼
Quartz 静态站点

关键事实:

  • mzvideo 的真实后端是本机笔记本上的 Quartz,不是某台公网服务器。
  • 公网入口 tx2 只做 nginx 反代 + frps 隧道中继,真正的页面生成在本机 Node 进程里。
  • 本机关机/重启/build 失败,mzvideo 立刻挂——这是一个强依赖本地开发机的脆弱架构。

三、故障一:tx2 整机假死(完全打不开)

现象

  • ping 通(0% 丢包,10ms)
  • TCP 握手 22/80 大概率通、6900 时通时不通(25% 失败)
  • 但所有应用层全超时:SSH banner exchange 超时、frps 连不上、HTTPS 443 不通

根因(journal 铁证,多重压力叠加)

上次 boot(7/14–7/26 18:38 假死)的死亡顺序:

  1. image_upload.service 内存泄漏(tx2 上一个 python 图片上传服务):7/26 01:15 其进程 pid 2950 RSS 飙到 1.86GB / 虚拟内存 2.9GB,触发全局 OOM,内核 oom-killer 将其 kill。
  2. tx2 内存 3.3G + 完全无 swap(Swap: 0B),没有任何缓冲,一个吃内存的进程就能把整机逼到 OOM。
  3. 7/26 15:23 frps panic 崩溃:send on closed channel 的 Go panic,frps 进程直接挂掉,frp 隧道服务中断。
  4. 同期 sshd 收到 71733 次暴破连接,叠加冲击。
  5. 结果:sshd/frps/nginx 等用户态进程先后崩溃或调度不上 CPU,内核仍能应答 ICMP/TCP 握手,但应用层全部超时 → 假死。

真正元凶是 image_upload.service 内存泄漏 + 无 swap;71733 次暴破只是放大器。systemd-udevd Worker terminated by signal 9、腾讯云 stargate agent 反复重启都是 OOM 期的连带表现。

解决

腾讯云控制台强制重启 tx2。

教训

  • “ICMP 通 + TCP 握手通 + 应用层全超时” = 服务器假死,不是网络断,也不是封禁(封禁会连 ping 一起断)。
  • 公网 22 是暴破磁石,必须 fail2ban / 禁密码登录 / 限源 IP(端口可保留,但要有自动封锁)。
  • 小内存机器别堆公网服务,配内存/CPU 告警在假死前预警。

四、故障二:mzvideo 502(tx2 恢复后)

现象

tx2 重启后返回 HTTP 502 Bad Gateway:

  • tx2 nginx error.log:upstream prematurely closed ... 127.0.0.1:15000
  • 本机 frpc 日志:[bj-5000] connect to local service [127.0.0.1:5000] error: connection refused

排查链路(分层定位法)

  1. DNS → 49.233.210.139 ✓
  2. tx2 nginx error.log → upstream 是 127.0.0.1:15000
  3. 本机 frpc 配置 → bj-5000 隧道 = 本机:5000 ↔ tx2:15000
  4. 本机:5000 无监听quartz.service 状态 activating (auto-restart)
  5. journalctl -u quartznpm run dev 反复 Main process exited status=1
  6. 手动 npx quartz build → frontmatter YAML 错误
  7. 全库检索 → 8 个文件 tags 含 @

根因

8 篇 md 的 frontmatter:

tags: [车设/语音, ..., @VoiceSearchProvider, ..., @Module(TAG_DRIVING)]

YAML 中 @ 是保留字符,flow 序列 [...] 里裸写 @token 非法,frontmatter 解析抛异常 → quartz build 崩溃退出 → systemd Restart=on-failure 又拉起 → 死循环重启,5000 永远没服务 → frp 回源 refused → 502。

涉及文件(全博客检索结果):

  • 车技车设/03-路由框架详解.md(@RouterProvider)
  • 车技车设/04-语音搜索插件详解.md(@VoiceSearchProvider)
  • 车技车设/整体架构探索/05-路由与语音搜索框架.md(@RouterProvider, @VoiceSearchProvider)
  • 车技车设/整体架构探索/模块详解/{Lock,Light,VehicleBody,Driving}-*/01-*.md
  • ai-engineering/practice/08-可迭代知识库的工程设计.md

解决

Python 脚本清除 tags 中所有 @(并去掉残留引号)→ npx quartz build 通过 → systemctl restart quartz → mzvideo 返回 200。

踩坑记录(很重要)

批量改这批中文路径 md 时,常规工具在本环境接连失效:

  • find -exec sed {} + —— 没改到文件(疑似 -exec {} + 对中文长路径传参问题)
  • bash globstar content/**/*.md —— ** 未递归,只匹配到 1 个文件
  • grep -rlE '^tags:.*@' —— 返回空(ugrep 对该组合 pattern 行为异常)

最终用 Python glob(recursive=True) + re 直接读写文件解决

教训:批量修改中文路径文件,Python 远比 sed/find/globstar 可靠;不要假设熟悉的 shell 命令在所有环境行为一致。

五、性能问题:响应慢(1.8~5s)

mzvideo 恢复后响应极慢,用 curl -w 分解各阶段耗时定位瓶颈:

链路段耗时 / 速率判定
本机 quartz 处理4ms
本机回环传输21 MB/s
frp 隧道(本机↔tx2)250ms / 63KB ≈ 2Mbps✅ 可接受
tx2 → 公网用户1.8s / 63KB ≈ 285kbps(抖动到 5s)瓶颈

瓶颈定位

  • 首页 HTML 仅 63KB,公网下载却要 1.8s,实测速率 ~285 kbps,且抖动到 5s。
  • 本机、frp 隧道都快;慢点 100% 在 tx2 → 公网用户的下行链路
  • tx2 刚从攻击+假死恢复(28 分钟前),公网网络不健康(疑似腾讯云限速/清洗或链路丢包)。

架构性问题(更根本)

静态站点本不该这样部署。当前每个请求要走:用户 → tx2 nginx → frp公网隧道 → 本机 Node serve → 原路返回,三段绕行,且:

  • 本机负载 load 4.28、quartz 进程占 63% CPU,本机一抖就拖垮站点
  • 家用宽带上行 + frp 隧道开销
  • Node dev-server 本就不是生产级服务

优化方案

治标(保持架构):

  • tx2 nginx 开 gzip/brotli(63KB HTML → ~15KB,传输快 4 倍)
  • nginx 启用 HTTP/2 + upstream keepalive
  • frp 开 tcpMux + 增大 poolCount,减少新建连接开销
  • 查 tx2 是否被腾讯云限速/清洗

治本(强烈推荐)——Quartz 静态部署:

quartz build  →  public/ 纯静态文件  →  rsync 到 tx2  →  tx2 nginx 直接 serve

收益:

  • 响应 <100ms(只剩 tx2 带宽这一关)
  • 本机关机/重启/build 失败都不影响 mzvideo(彻底消除本次故障的根因)
  • 去掉 frp 隧道 + Node serve 两层开销
  • 配 git hook:push 后自动 build + rsync,保持实时性

六、经验与方法论(可借鉴)

6.1 分层排查法

网络问题从下往上逐层验证,每层用最小工具: dig(DNS) → ping(ICMP) → </dev/tcp/host/port>(TCP 握手) → curl -v(应用层) → 业务日志。 永远先量化每一跳,不要靠猜。

6.2 “ICMP通 + TCP握手通 + 应用层全超时” = 服务端假死

内核在线但用户态进程卡死(OOM/过载)。重启可恢复,但要查 OOM 根因防复发。不是封禁(封禁 ping 一起断)。

6.3 frp 内网穿透排障套路

  • 看本机 frpc 日志:能否连 frps(bind 端口)、隧道是否 start proxy success
  • 看 frp-panel 控制面:能否拉到配置(认证握手是否成功)
  • frp 链路问题典型表现:502 / connection refused / prematurely closed
  • 关键区分:frps 服务端挂(连不上 bind 端口) vs 本机后端没起(连得上 frps 但 localPort refused)——后者日志会明确报 connect to local service ... refused

6.4 systemd 死循环重启

Restart=on-failure + 内容错误 = 无限重启空耗 CPU。务必加:

StartLimitIntervalSec=300
StartLimitBurst=3

systemctl status + journalctl -u <svc> 定位退出码和原因。

6.5 frontmatter / YAML 规范

  • tag 不能用 @ 开头(YAML 保留字符),也不建议含 #!:{}[] 等保留符
  • 含特殊字符的 tag 用引号 "..." 或 block style
  • 一个坏 frontmatter 会拖垮整站构建——提交前务必 npx quartz build 校验,建议加 pre-commit hook

6.6 静态站部署铁律

  • 静态产物用 nginx 直接 serve,不要用 Node dev-server 暴露到公网
  • 不要经 frp 反向代理(家用宽带上行 + 隧道开销 + 本机依赖)
  • 公网服务器直接托管静态文件:最快、最稳、不依赖开发机在线

6.7 工具可靠性备忘

  • 批量改中文路径文件:Python > sed/find/globstar
  • 定位慢点:curl -w '...%{time_starttransfer}...' 分解 TTFB / total
  • 不要轻信”首次慢是正常”,量化每个阶段;静态站正常应 <500ms

七、待办清单

  • quartz.serviceStartLimitIntervalSec=300 / StartLimitBurst=3 ✅ (2026-07-26)
  • 🔴 tx2 加 4G swap ✅ (2026-07-27) —— OOM 有缓冲,不再零容错假死
  • 🔴 image_upload.serviceMemoryMax=1024M ✅ (2026-07-27) —— 泄漏超 1G 只杀自身(systemd 自动重启),不再触发全局 OOM 蔓延
  • 🟠 根治 image_upload Flask app 内存泄漏(/opt/image_upload/app.py,限流是兜底,代码层才是根因)
  • 🟠 查 frps send on closed channel panic(7/26 15:23 崩溃),考虑升级 frps 或加自动重启
  • tx2 装 fail2ban(22 保留供本机访问,但自动封暴破 IP)
  • tx2 配内存/CPU 告警,OOM 前预警
  • mzvideo 改静态部署到 tx2(根治响应慢 + 不依赖本机)
  • content 加 pre-commit hook 跑 npx quartz build 校验 frontmatter
  • 短期:tx2 nginx 开 gzip + HTTP/2