generic-pool资源池优化puppeteer 并发

前言:在之前的文章中我们记录了puppeteer将h5页面转pdf下载的功能实现。在部署到Linux后我们发现pup很消耗内存,因为是一个chrome。考虑到并发量大给服务端带来压力。需要用到generic-pool来管理控制puppeteer创建的实例。下面的代码都是网上可搜索到的。我这里仅作一下记录。

puppeteer-pool.js

const puppeteer = require("puppeteer");
const genericPool = require("generic-pool");

/**
 * 初始化一个 Puppeteer 池
 * @param {Object} [options={}] 创建池的配置配置
 * @param {Number} [options.max=10] 最多产生多少个 puppeteer 实例 。如果你设置它,请确保 在引用关闭时调用清理池。 pool.drain().then(()=>pool.clear())
 * @param {Number} [options.min=1] 保证池中最少有多少个实例存活
 * @param {Number} [options.maxUses=2048] 每一个 实例 最大可重用次数,超过后将重启实例。0表示不检验
 * @param {Number} [options.testOnBorrow=2048] 在将 实例 提供给用户之前,池应该验证这些实例。
 * @param {Boolean} [options.autostart=false] 是不是需要在 池 初始化时 初始化 实例
 * @param {Number} [options.idleTimeoutMillis=3600000] 如果一个实例 60分钟 都没访问就关掉他
 * @param {Number} [options.evictionRunIntervalMillis=180000] 每 3分钟 检查一次 实例的访问状态
 * @param {Object} [options.puppeteerArgs={}] puppeteer.launch 启动的参数
 * @param {Function} [options.validator=(instance)=>Promise.resolve(true))] 用户自定义校验 参数是 取到的一个实例
 * @param {Object} [options.otherConfig={}] 剩余的其他参数 // For all opts, see opts at https://github.com/coopernurse/node-pool#createpool
 * @return {Object} pool
 */
const initPuppeteerPool = (options = {}) => {
    const {
        max = 6,
        min = 2,
        maxUses = 2048,
        testOnBorrow = true,
        autostart = false,
        idleTimeoutMillis = 3600000,
        evictionRunIntervalMillis = 180000,
        puppeteerArgs = {},
        validator = () => Promise.resolve(true),
        ...otherConfig
    } = options;

    const factory = {
        create: () =>
            puppeteer.launch(puppeteerArgs).then((instance) => {
                // 创建一个 puppeteer 实例 ,并且初始化使用次数为 0
                instance.useCount = 0;
                return instance;
            }),
        destroy: (instance) => {
            instance.close();
        },
        validate: (instance) => {
            // 执行一次自定义校验,并且校验校验 实例已使用次数。 当 返回 reject 时 表示实例不可用
            return validator(instance).then((valid) =>
                Promise.resolve(valid && (maxUses <= 0 || instance.useCount < maxUses))
            );
        },
    };
    const config = {
        max,
        min,
        testOnBorrow,
        autostart,
        idleTimeoutMillis,
        evictionRunIntervalMillis,
        ...otherConfig,
    };
    const pool = genericPool.createPool(factory, config);
    const genericAcquire = pool.acquire.bind(pool);
    // 重写了原有池的消费实例的方法。添加一个实例使用次数的增加
    pool.acquire = () =>
        genericAcquire().then((instance) => {
            instance.useCount += 1;
            return instance;
        });
    pool.use = (fn) => {
        let resource;
        return pool
            .acquire()
            .then((r) => {
                resource = r;
                return resource;
            })
            .then(fn)
            .then(
                (result) => {
                    // 不管业务方使用实例成功与后都表示一下实例消费完成
                    pool.release(resource);
                    return result;
                },
                (err) => {
                    pool.release(resource);
                    throw err;
                }
            );
    };
    process.on("SIGINT", () => {
        pool
            .drain()
            .then(() => pool.clear())
            .then(
                () => {
                    process.exit(0);
                },
                () => {
                    process.exit(1);
                }
            );
    });
    return pool;
};

module.exports = initPuppeteerPool;
 

2pdf.js

const initPuppeteerPool = require('./puppeteer-pool.js')

const pool = initPuppeteerPool({
    puppeteerArgs: {
        args: ['--no-sandbox'],
        headless: true
    }
})

module.exports.handlePdf = async function (accessToken,dateStr,reportType) {
    return (async () => {
        var url = ``
        const page = await pool.use(async (instance)=>{
            const newpage = await instance.newPage()
            await newpage.goto(url, { waitUntil: "networkidle0" }); //监听H5页面执行渲染完成通知的函数4
            let renderdoneHandle = await newpage.waitForFunction("window.renderdone", {
                timeout: 15000, //毫秒
            });
            const renderdone = await renderdoneHandle.jsonValue();
            if (typeof renderdone === "object") {
                console.log(`加载页面失败`);
                console.log(renderdone.message + url);
                throw new Error(renderdone.message); //抛出异常
            } else {
                console.log("页面加载成功");
                const options2 = {
                    format: "A4", //这个会让height失效
                    scale: 1,
                    pageRanges: "1-5",
                    printBackground: true, // 是否打印背景颜色
                    "-webkit-print-color-adjust": "exact",
                };
                const pdf = await newpage.pdf(options2);
                //关闭页面
                if (newpage){
                    await newpage.close();//完成后关闭页面相当于关闭浏览器tab,会杀死页面进程
                }
                return pdf;
            }
        })
        return page
    })();
};
 

重启node服务时注意调用清理池pool.drain().then(()=>pool.clear())

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值