无头浏览器在线服务思考

您所在的位置:网站首页 无头游览器 无头浏览器在线服务思考

无头浏览器在线服务思考

2023-03-29 08:08| 来源: 网络整理| 查看: 265

无头浏览器就是无UI的浏览器,目前有多种技术方案,使用最广的是Puppeteer库,它提供API控制Chrome无头浏览器来执行各种任务;关于Puppeteer如何使用以及功能的资料网上有太多文章,这里重点介绍在基于无头浏览器提供对外服务时,在稳定性和服务化方案上需要思考的因素,比如某个服务调用无头浏览器进行网页内容截图、生成PDF时,如果业务对稳定性、正确性有要求,那么在设计服务时思考哪些内容

架构方案设计

先简单介绍在线服务的架构设计,这与具体运行Puppeteer任务的运行时间、并发量有关,考虑到单个任务从启动无头浏览器、打开页面耗时至少要几秒,这就决定了这类程序很难在单台机器上做到高并发,结合调用方的实际情况,这里做如下选型分析:

如果预期某个任务运行时间长,超过调用方API的超时时间,那么考虑通过异步方案;

如果大部分任务运行短、并发量可控,调用方能在内部控制任务请求的并发量,或者调用方因为对时间要求敏感只考虑同步方案,那么服务内考虑同步方案;即使后期内并发量上去,也可以考虑通过扩容来解决;如果服务能接入serverless就能保证服务在稳定性、扩缩容上很好解决

如果预期在某个时间段任务并发量比较大,调用方没有对流量进行控制,对处理时效没有强要求,那么最好通过异步方案保证,结合队列存储,保证消费任务的程序不会垮掉;进一步看,考虑到任务一般有优先级之分,所以队列选型上最好提前考虑能支持优先级的方案

结合上述分析,总结两种典型架构

image.png

任务并发处理模式

任务处理和具体浏览器实例管理有关,浏览器实例管理影响服务的稳定性和具体代码逻辑。按照处理任务请求的模式,分为三种

1、一个请求对应一个浏览器实例(单实例单页面):每次处理请求时就打开一个chrome浏览器,处理结束后再关闭浏览器;

2、一个多个请求对应一个浏览器实例,实例内通过打开多页面处理每个请求(单实例多页面):即在服务启动时事先创建号浏览器实例,然后等请求到来时通过打开页面方式处理每个请求

3、维护一个实例池,每个请求从实例池中获取实例(实例池):在程序内部上维护一个事先创建好固定个数的浏览器实例,和2类似,当任务请求到来时从实例池中获取某个空闲浏览器实例即可,任务处理结束时归还实例到实例池中;当请求多导致没有多余空闲实例时,可以启动实例池的扩容策略来增加实例数量;如果请求数少时内部启动缩容策略。这样设计使得浏览器实例管理和任务逻辑解耦

下表总结这三种模式之间的优缺点

优点缺点单实例单页面逻辑简单,稳定性好每次请求都要打开浏览器实例,性能开销比较大单实例多页面1、性能比较好,逻辑可控;2、页面间共享cookies等信息稳定性上考虑因素多,考虑当浏览器实例crash后,实例上所有任务都失败,额外重试处理的任务多实例池在性能开销、稳定上能做到兼顾,解决1、2缺点实例池的设计

目前社区也提供Puppeteer-cluster库管理实例池,该方案实现具备很好的参考价值,是个典型的master/worker架构,二者通过queue解藕,queue内部保存用户注册的job,master就是cluster本身,它的职责是

管理worker,包括worker的扩缩容、worker的状态管理 负责从queue获取job,以及可用的worker,调用worker执行job 提供三种任务并发处理模式 worker内部根据给定的浏览器Page和Job,执行具体业务逻辑

除此之外,该库的还具备以下能力

处理各种各样errors 自动管理浏览器:比如浏览器页面crash时自动重启,不过遇到浏览器本身Crash目前还没有自动重启机制 自动重试:某个job失败时自动重试,重试逻辑也简单,将job自动加入对queue来实现 提供监控工具查看任务处理进度和统计数据,这对个人排查问题有一定帮助,不过不建议在线上环境开启,毕竟这些监控数据没有存储下来,对事后定位问题帮助不大,这方面还是规范接入到公司内成熟的运维平台 任务内部交互流程

以使用Puppeteer来截图为例,通过以下代码片段介绍常见交互流程

const browser = await puppeteer.launch(options) const page = await browser.newPage(); const result = new Promise((resolve, reject) => { await page.exposeFunction('onFinish', async result => { // 难道内部数据 resolve(result); }) await page.evaluateOnNewDocument(data => { // 传递数据 window.data = data; // 页面内触发finish事件通知结束 window.addEventListener('finish', e => window.onFinish(e.detail)) }, data) await page.goto(filePath, {timeout}) }) // const streamImg = await page.screenshot({encoding: 'stream'}) // upload upload(streamImg) 复制代码

调用puppeteer.launch启动浏览器

调用browser.newPage生成空白页面

通过page.evaluateOnNewDocument方法给页面传递数据:这里动态模板情况,也就是导航的URL页面内容动态变化。

导航到URL:调"page.goto(url)"方法导航到指定URL,并等待渲染结果;返回Promise对象;如果导航失败或者超时,返回的Promise对象被Reject

任务处理:这个阶段执行和具体Page交互流程,内部通过调用"page.evaluate(fn)"、"page.click(selector)"等API来点击页面的元素、执行Javascript代码等,那怎么判断任务处理完毕?Puppeteer提供一些状态作为判断依据,更复杂的可以Puppeteer和页面之间通过事件通信机制来实现,

在页面内部通过window提供的dispatch分发事件告诉调用者页面任务处理完毕,事件回调函数在page.exposeFunction(fn)中fn里注册事件

截图、打印PDF:调用Puppeteer提供的"page.screenshot()"获取页面指定区域截图

数据后处理:比如图片、PDF等数据一般会发送到远程存储系统存储

稳定性

根据上述服务内任务流程出发,这里从以下几个因素考虑稳定性和解决方案

任务内部crash

从上述单个任务交互流程可知,哪怕是简单的截图功能内部到多个步骤串形执行,如果某个步骤执行失败,那么整个任务就失败;根据步骤类型,抛开内部逻辑bug外,从系统角度这里主要分为两种

资源不足导致失败:由于Chrome自身内存消耗大,在机器自身内存资源受限下,如果不对Chrome实例对限制,那么很容易超过内存最大值,导致Chrome启动失败;那么具体Chrome实例要分配多少,这与上述架构、任务并发处理模式和业务处理的任务类型有关,如果不好预估,就按照保守策略,假设在64位操作系统下,每个Page内V8引擎最大内存消耗4G来算,根据机器最大内存值/4G得到结果作为当前最少能处理的实例数

异步网络通信失败:涉及网络通信的步骤包括puppeteer和Chrome之间的websocket通信、Chrome Page内前后端通信、截图内容上传等环节,只要有网络就存在断网、抖动等异常情况导致任务处理失败的情况;针对这种case简单高效处理方式是通过重试机制解决网络抖动问题

服务本身crash

假设以Node服务来运行截图在线服务,考虑Node本身可能存在内存泄漏导致Crash情况,在这种情况下,服务Crash前正在处理的请求上下文就存在丢失情况,这意味着上述三种并发处理的任务都存在因服务Crash导致任务没法响应,包括社区提供实例池内的队列方案也会存在该情况,因为该方案队列是放在内存管理,如果服务整体crash,那么此时内存数据都会消失

如果业务对响应结果失败不能忍受时,那么解决方案和具体架构有关,

同步方案:需要调用方通过重试机制来解决,因为在调用方看来这种case和网络抖动情况类似,技术上也是通过超时机制来感知服务不可用;

异步方式:在服务方内部就能解决,因为在队列请求-响应模式下,只要不响应,那么队列内任务就不会消失,在服务重启后仍会重新处理上次失败的任务

大流量控制

应该说这点主要针对同步架构而言,如果请求数量没有有效控制,那么很容易出现因Chrome资源不足导致失败情况;解决方案需要和调用方协调,在调用测根据服务最大请求数做流量控制,因为如果在服务内自己控制流量会出现因任务处理时长超过调用方超时时间,可能导致调用方不断重试,这样任务不断累加导致服务负载越来越重,因此在调用方做流控比较稳妥;

总结

本文基于无头浏览器,介绍在线服务场景下需要关注的因素,虽然代码逻辑本身比较简单,但是考虑对外提供稳定服务时,需要处理的额外逻辑还是不少;另外特别注意架构选型上,最好一开始结合业务需求和未来规划,从任务类型、调用方、并发量等角度评估具体技术方案。从选择优先级看,可以按照如下顺序选择方案

异步方案:核心是和调用方协商,对方能接受那么这是最优选择,该方案对上述稳定性问题的解决比较友好

结合云能力,考虑faas技术,每个容器内处理一个具体的任务,容器数量根据请求数动态决定;

同步方案:这是最后选择的方案,该方案内部需要处理异常case、以及对任务时效长等需要事前评估,如果后续遇到大任务场景,该方案处理就不够灵活,特别是处理时长超过调用方超时时间情况下,依赖调用方协商解决



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3