2026-09-12-上传并发卡死根因与修复.md

上传并发导致「卡死」:根因定位与修复


一、卡死的真正来源(代码级定位)

#位置问题后果
1handleUploadComplete 合并落盘fs.readFileSync(64MB) + fs.writeSync 逐块全同步合并合并 6.9GB 文件 = 6.9GB 同步读写,事件循环被独占十几秒;多个文件同时 complete 叠加成数分钟完全无响应
2合并后的 fs.rmSync(dir,{recursive:true})同步递归删除含 GB 级 .part 的会话目录每个文件合并后都要阻塞一次,量大时持续卡顿
3无并发闸门45 个文件同时上传,合并/删除/写盘互相叠加峰值压力不可控,磁盘 IO 与内存被抢
4server.requestTimeout 默认 300s合并排队时响应可能超 5 分钟请求被 Node 误杀(408)
5keepAliveTimeout 默认 5s几十个大文件反复重建连接连接churn,加重握手与调度负担

二、修复措施

措施实现效果
流式异步合并createReadStream → createWriteStream(pipe(..., {end:false}))替代 readFileSync/writeSync事件循环不再被合并独占
合并闸门同时最多 2 个合并(DL_MERGE_CONCURRENCY,默认 2),其余排队消除多文件并发合并风暴
异步清会话fs.promises.rm 替代 fs.rmSync删除 GB 级目录不阻塞
存活探针GET /healthz:不触碰磁盘,返回 pid/uptime/rss/mergeActive/mergeQueued供巡检与人工判活
探活巡检dl-healthcheck.timer 每 30s 请求 /healthz,连续 2 次失败 → 自动重启卡死约 60 秒内自愈(进程活着但卡死时唯一有效手段)
事件循环滞后监控单次阻塞 >5s 记 [warn] 事件循环阻塞 Xms再发生卡死可追溯现场(DL_LAG_WARN_MS 可调)
顶层异常日志uncaughtException(记日志后 exit(1) 交 systemd 拉起)/unhandledRejection(记日志)不再出现「日志只剩启动行、无法定位」
超时放宽requestTimeout=30min、headersTimeout=60s、keepAliveTimeout=30s合并排队不被误杀 + 连接复用
前端并发闸门同时最多 3 个文件在传(UP_PARALLEL),其余显示「排队中…」服务端压力可控,单个文件更快传完

踩坑:Node 无法实现 systemd 的 sd_notify

原计划用 Type=notify + WatchdogSec(原生看门狗,能精确发现事件循环卡死)。实测 Node 的 dgram 只支持 udp4/udp6,不支持 unix_dgram,dgram.createSocket('unix_dgram') 直接抛 ERR_SOCKET_BAD_TYPE;异常被 try/catch 吞掉后 READY=1 永不送达,systemd 报 start operation timed out 启动失败。 → 改为外部探活巡检:效果等价(事件循环卡死时 /healthz 必然超时)、零额外依赖,故最终采用 Type=simple + 巡检定时器双层兜底(systemd Restart=always 负责崩溃重启,巡检负责卡死重启)。

三、验证结果

  1. 并发合并压测(3 个 160MB 文件同时 complete):
    • 3 个合并 419ms 全部完成,HTTP 200,200,200;
    • 合并期间 /healthz 探测 16 次:最大延迟 3ms、平均 1.4ms(旧实现会阻塞整个合并时长);
    • 观察 mergeActive 峰值 = 2、且出现排队 → 闸门生效;
    • 3 个产物与源数据字节级一致 ✅
  2. 卡死自愈实测:kill -STOP 主进程模拟事件循环永久卡死
    • 卡死期间 /healthz → HTTP 000(超时,符合预期)
    • 22:08:22 巡检第 1 次失败 → 22:08:55 第 2 次失败并重启 → 22:09:00 恢复(约 67 秒),MainPID 2489497 → 2490400 ✅
  3. 功能回归:上传全套 23 项测试 23/23 通过(跳过去重、改名保存、分块断点续传、子目录落盘、页面注入)✅
  4. 前端:页面内联 JS 语法校验通过,并发闸门与跳过复选框均已生效 ✅
  5. 在传资产无损:45 个分块会话、65GB 全部在册;已完整落盘 13 个模型 ✅

四、日常运维

systemctl status dl-server                      # 服务状态
systemctl status dl-healthcheck.timer           # 巡检定时器
curl -s --noproxy '*' http://127.0.0.1:8899/healthz   # 健康检查(含合并队列)
tail -f ~/Downloads/dl-server.log               # 日志(现为 append;关注 [warn] 事件循环阻塞 / [fatal])

可调参数(写在 /etc/systemd/system/dl-server.service 的 Environment=):

五、相关文件

下载此文件