彻底解决Puppeteer占用内存过高的问题

2023-10-13 网络技术 0

问题描述

  1. 服务器使用的CentOS系统,使用docker安装Puppeteer容器

  2. 使用Express框架搭建服务端,调用Puppeteer库来访问某链接

  3. 调用接口比较频繁,存在并发的情况

  4. 发现运行一段时间后内存占用很高,重启后会降下来

  5. 网上说的创建固定的Browser实例数量以及修改初始化配置方案都无效

问题分析

可能是BrowserPage关闭后没有完全释放内存,导致内存可用率降低

解决方案

  1. 定时重启

使用linux的crontab定时重启Express服务,这种方案会造成短时间的连接中断及服务不可用

  1. 定时创建和摧毁Browser实例

在代码层实现Browser实例的定时创建和销毁,具体逻辑参考代码

const puppeteer = require("puppeteer");

// Browser实例
let browserList = [];

// 不用的浏览器
let unusedBrowser = [];

// 创建浏览器实例
const createBrowser = async () => {
  
  const browser = await puppeteer.launch({
    headless: true,
    args: [
      "--disable-gpu",
      "--disable-dev-shm-usage",
      "--disable-setuid-sandbox",
      "--no-first-run",
      "--no-sandbox",
      "--no-zygote",
      "--single-process",
    ],
  });

  // 记录该浏览器实例打开的页面数
  let pageCount = 0;

  // 监听到打开了一个新窗口
  browser.on("targetcreated", () => {
    pageCount++;
  });

  // 监听到窗口关闭
  browser.on("targetdestroyed", () => {
    pageCount--;

    if (pageCount === 0 && unusedBrowser.includes(browser)) {
      console.log("命中unusedBrowser");
      delBrowser(browser);
    }
  });

  browserList.push(browser);
};

// 从browserList和unusedBrowser中删除
const delBrowser = (browser) => {
  browserList = browserList.filter((item) => item !== browser);
  unusedBrowser = unusedBrowser.filter((item) => item !== browser);

  browser.close().then(() => {
    browser = null;
    console.log("删除浏览器", browserList.length, unusedBrowser.length);
  });
};

// 始终返回最后一个浏览器实例
const getBrowser = () => {
  return browserList[browserList.length - 1];
};

// 定时任务
const scheduledTask = () => {
  // 每隔20秒去创建一个新的Browser实例,并销毁第一个实例
  setInterval(() => {
    createBrowser().then(() => {
      // 把第一个浏览器置为不可用
      const firstBrower = browserList[0];
      firstBrower.pages().then((pages) => {
        console.log(`第一个浏览器实例打开的页面数为${pages.length}`);

        // 如果当前没有打开的页面,创建Browser实例时会默认初始化一个页面,所以这里判断的是<=1
        if (pages.length <= 1) {
          delBrowser(firstBrower);
        } else {
          unusedBrowser.push(browserList[0]);
        }
      });
    });
  }, 20 * 1000);
};