Puppeteer + Nodejs 通用全屏网页截图方案(六)项目优化

语言: CN / TW / HK

theme: scrolls-light

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第30天,点击查看活动详情

# (一) 基本功能# (二) 常用参数实现# (三) 进阶处理# (四) 页面处理# (五) 部署与本地调试

JS实现异步任务队列

由于截图服务每次执行任务都需要占用不少时间和性能,且服务器资源有限,如果截图服务并发请求数量过高,必然会引起问题,此时就需要一个异步任务队列来对并发的浏览器数量进行控制。

js // 在前面创建的配置文件 config.ts 中加入配置 exports.maxNum = 1 // 截图队列并发数(阈值) 队列方法参考: ```ts // node-queue.ts interface Queue { Fn: Function sign?: string | number }

const { maxNum } = require('../configs.ts') const queueList: any = [] // 任务队列 let curNum = 0 // 当前执行的任务数 // 队列处理器,将超过允许并行数的任务抛进队列中等待执行 function queueRun(business: Function, ...arg: any) { return new Promise(async (resolve) => { const Fn = async () => resolve(await business(...arg)) // 核心是将业务函数封装起来 const sign = { ...arg }[2] // 标记任务的参数,后面会用到 if (curNum >= maxNum) { queueList.push({ sign, Fn }) } else { await run(Fn) } }) } // 任务执行器,将队列中等待的任务取出执行 function run(Fn: Function) { curNum++ Fn().then((res: any) => { curNum-- if (queueList.length > 0) { // 如果存在任务,继续取出执行 const Task: Queue = queueList.shift() run(Task.Fn) } return res }) }

module.exports = { queueRun, queueList } 请求服务:ts const { queueRun, queueList } = require('../utils/node-queue.ts') const screenshotFn = require(xxxxxx) // 这里引入你的截图方法 // ...... 这里使用的是一个express框架的服务,只贴出部分关键代码 ..... async printscreen(req: any, res: any) { if (queueList.length > 100) { // 限制在达到某个最大队列数量时直接暂停服务 res.json({ code: 200, msg: '任务繁忙,请稍候再试!' }) return } // 进入队列处理,最终执行的是:screenshotFn(url, { width, height ...等参数.. }) queueRun(screenshotFn, url, { width, height ...等参数.. }) .then(() => { res.setHeader('Content-Type', 'image/jpg') // 请求结果将直接返回图片 type === 'file' ? res.sendFile(path) : res.sendFile(thumbPath) }) .catch((e: any) => { res.json({ code: 500, e }) }) } ```

express超时任务销毁

有了前面的队列处理,在任务跑满的情况下基本可以将性能稳定下来,但是服务的健壮性还有所欠缺。假设现在有100个任务同时并发,每个任务的平均处理时间约为10秒,并发任务阈值为1,那么处理到第30个任务的时候已经过了约5分钟的时间了,浏览器默认超时时间大概也就4到5分钟(大部分的项目可能会手动设置一个30~60秒的请求超时)在http请求中如果浏览器超时了,服务端是不知道的,而我们的任务还在不断执行着,于是后面这几十条任务等于没办法把结果返回给前端了,因为链接早已断开。

此时我们需要约定一个超时时间,这里的超时时间最好是服务端<客户端,这样超时的时候请求还在,可以将错误信息返回给前端。

在服务端还需要做一个超时任务销毁的动作,因为超时的任务实际上已经没有存在队列里的必要了,如果不断叠加请求,队列中的无效任务一直在耗时处理,那么后面的所有任务都将会无法正常执行。

中间件 timeout 代码参考: js // timeout.ts module.exports = async (req: any, res: any, next: any) => { const { queueList } = require('../utils/node-queue.ts') const time = 30000 // 设置所有HTTP请求的服务器响应超时时间 res.setTimeout(time, () => { const statusCode = 408 const index = queueList.findIndex((x: any) => x.sign === req._queueSign) if (index !== -1) { // 如果超时任务存在队列中,则移除该任务 queueList.splice(index, 1) if (!res.headersSent) { // 如果链接还存在,则返回错误信息 res.status(statusCode).json({ statusCode, message: '响应超时,任务已取消,请重试', }) } } }) next() } 这里的 sign 在前面队列方法中也有传入,主要是为了给任务一个标记,在超时处理中才好找出对应的任务,只要是不重复的字符即可,这里简单使用时间戳来标记。 ts // 请求服务的部分关键代码 async printscreen(req: any, res: any) { const sign = new Date().getTime() + '' // 时间戳标记 req._queueSign = sign // 在此次请求中记录标记值 queueRun(screenshotFn, url, { width, height ...等参数.. }, sign) // 增加了第三个参数,对应前面的{ ...arg }[2] .then(() => { if (!res.headersSent) { // TODO 正常请求的返回动作 } }) .catch((e: any) => { res.json({ code: 500, e }) }) } js // ........ app.use(handleTimeout) // 别忘了注册中间件 并发任务阈值设为1手动请求几次看看效果: image.png 最终效果超时任务不会再执行,接口的返回也正常。

koa的超时处理方式与之类似,注意koa是洋葱模型,需要把异常捕获的中间件放在最前面