generic-pool资源池管理puppeteer截图进程过多问题

原因:用puppeteer截图,每次都会产生一个chome实例进程,截图完了以后要关掉进程,如果一个接口一次访问100次截图会导致进程暂用而导致机器卡顿

1.安装

npm i generic-pool
npm i puppeteer

2.建puppeteerPool.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 = 10,
        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;

3.建screenshot.js(截图部分)

const initPuppeteerPool = require('../utils/puppeteerPool')
const onFinished = require('on-finished')
// 全局只应该被初始化一次
const pool = initPuppeteerPool({
    puppeteerArgs: {
        //设置该参数能解决网站证书无效问题(Error: net::ERR_CERT_COMMON_NAME_INVALID )
        ignoreHTTPSErrors: true,
        // 不使用 websocket
        pipe: true,
        //在linux运行要加这个参数,不加会报 Error: Failed to launch the browser process!
        args:['--no-sandbox', '--disable-setuid-sandbox'],
    }
})

async function screenshot(req, res) {

    const { url,width,height } = req.query

    if (!url||!width||!height) {
        return res.json({
            code: 1000,
            msg: "缺少参数"
        })
    }
    let isReturn = false;

    onFinished(res, () => {
        isReturn = true;
    });

    try {
        // 在业务中取出实例使用
        await pool.use(async browser=>{
            if (isReturn) {
                return;
            }
            let page;
            try {
                page = await browser.newPage();

                const viewportOptions = {
                    width:Number(width),
                    height:Number(height),
                    deviceScaleFactor: 1,//这个参数设置为1(设置为2的时候截图会比较慢,截图50000高度的页面用时差不多25秒,设置为1的时候用时8秒,而且设置为2的时候截图50000高度的页面生成出来有一半是空白)
                };

                await page.setViewport(viewportOptions);

                await page.goto(url, {
                    timeout: 60 * 1000,
                    waitUntil: 'networkidle2',
                });
                const imgBuffer = await page.screenshot();
                //返回图片
                res.set('Content-Type', 'image/png;');
                res.send(imgBuffer);
            }finally {
                //关闭页面
                if (page){
                    await page.close();
                }
            }
        })
        // 应该在服务重启或者关闭时执行
        // pool.drain().then(() => pool.clear())

    }catch (e) {
        console.error(e,"screenshot-error")
        res.json({
            code: 1002,
            msg: "截图失败"
        })
    }


}



module.exports = {
    screenshot,
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值