You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Docker环境下基于Node.js与Puppeteer的并发PDF生成服务虚拟内存持续增长问题求助

解决Node.js + Puppeteer生成PDF时内存占用过高的问题

我来帮你拆解这个内存持续飙升的问题,结合你提供的代码和容器监控数据,核心问题和优化方案如下:

问题根源分析

从你的代码和top输出来看,主要问题集中在这几点:

  1. 每个请求新建完整Chromium实例:generatePDFFile里每次调用都启动一个全新的Chromium进程,而Chromium本身就是内存大户,8个实例同时运行必然导致内存爆炸。
  2. 启动参数配置不合理:--single-process和--no-zygote会强制所有页面共享一个进程,反而更容易引发内存泄漏,且旧版headless模式的资源利用率不如新版。
  3. 页面/实例清理逻辑有瑕疵:原generatePDF函数里try块和finally块重复调用page.close(),虽然不致命,但可能导致异步清理异常。

核心优化方案:复用浏览器实例(而非每个请求新建)

这是最有效的内存优化手段——把浏览器实例全局复用,用多个页面(Page)处理请求,而不是每次启动全新浏览器。

1. 修改浏览器实例管理逻辑

// 全局维护一个复用的浏览器实例
let sharedBrowser;

// 初始化/获取浏览器实例的工具函数
async function getSharedBrowser() {
  if (!sharedBrowser || !sharedBrowser.isConnected()) {
    // 销毁失效的实例
    if (sharedBrowser) {
      await sharedBrowser.close().catch(err => console.log("清理失效浏览器实例失败:", err));
    }
    // 启动新实例
    sharedBrowser = await launchPuppeteer(puppeteerParams);
  }
  return sharedBrowser;
}

async function generatePDFFile(id) {
  let pdfFile;
  let errorGeneration = "";
  try {
    const browser = await getSharedBrowser();
    pdfFile = await generatePDFPage(browser, id);
    console.log(`PDF生成完成,ID:`, id);
  } catch (error) {
    errorGeneration = error.message;
    console.log(`生成PDF失败,ID: ${id},错误:`, error);
    // 如果浏览器断开连接,重置实例
    sharedBrowser = null;
  }
  // 不再关闭浏览器,只在页面层面清理
  return { id, pdf: pdfFile, error: errorGeneration };
}

2. 修正页面清理逻辑

确保页面能被正确关闭,避免内存泄漏:

async function generatePDF(browser, num) {
  let page;
  try {
    page = await browser.newPage();
    const pdfUrl = pdfPathURL(num);
    await page.goto(pdfUrl, { 
      waitUntil: ["domcontentloaded", "networkidle0"], // 无需重复指定load,networkidle0已足够
      timeout: 300000, 
    });
    const buffer = await page.pdf({ 
      format: "A4", 
      printBackground: true, 
      landscape: true, 
      preferCSSPageSize: true, 
    });
    return buffer.toString("base64");
  } catch (error) {
    console.log(`生成PDF出错,编号: ${num}`, error);
    return "";
  } finally {
    // 确保页面被关闭,即使中途报错
    if (page) {
      await page.close().catch(err => console.log(`关闭页面失败,编号: ${num}`, err));
    }
  }
}

优化Puppeteer启动参数

调整参数,使用更轻量的新版headless模式,并添加内存限制:

const puppeteerParams = {
  headless: "new", // 新版headless模式,资源占用比旧版降低30%+
  args: [
    "--disable-gpu",
    "--disable-dev-shm-usage",
    "--disable-setuid-sandbox",
    "--no-sandbox",
    "--font-render-hinting=none",
    "--disable-extensions", // 禁用扩展,减少内存占用
    "--disable-background-timer-throttling",
    "--disable-renderer-backgrounding", // 禁止后台页面休眠,避免异常
    "--max-old-space-size=2048", // 限制V8内存上限(根据容器内存调整,比如2G)
    "--window-size=1920,1080"
  ],
};

⚠️ 移除--single-process和--no-zygote:这两个参数会破坏Chromium的进程隔离机制,反而容易引发内存泄漏和稳定性问题。

其他辅助优化手段

  1. 限制并发数:你的容器总内存约15G,同时处理8个请求压力过大,建议把并发数降到4个以内,避免内存过载。
  2. Docker资源限制:给容器设置明确的内存上限,比如docker run --memory=12g --memory-swap=16g ...,防止容器无限制占用宿主机内存。
  3. 定期重启实例:如果长期运行后仍有缓慢内存增长,可以设置定时任务,每生成N个PDF后重启一次浏览器实例。
  4. 排查页面内存泄漏:如果生成的PDF页面包含大量动态内容,检查页面是否存在未清理的事件监听器、全局变量,避免页面关闭后内存无法释放。

验证优化效果

优化完成后,你会看到容器内的Chrome进程数量大幅减少(只有1个主进程+若干页面子进程),VIRT内存占用会从1124G降到合理范围,物理内存也会稳定在可接受的水平。

内容的提问来源于stack exchange,提问作者Danubio

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.27 21:32:28