智慧作业最近上线「个性化手册」(简称个册)功能,一份完整的个性化手册分为三部分:
【资料图】
个册三个部分的PDF数据来源不同,生产逻辑独立由不同的服务生产,最终将三份PDF合并为一份,还要支持班级所有学生批量生产和压缩打包,所以这个功能在技术角度最主要的特征就是环节多、耗时长:
环节多意味着在各个服务之间存在较多的网络通信和数据交互,核心挑战在于如何设计低耦合、高可用的服务架构;耗时长一方面体现在多个环节的总耗时,另一方面体现在三个PDF生产服务各自的加工耗时。基于以上业务特征,PDF加工服务架构设计的一个大方向就是将长耗时任务异步处理,各服务之间逻辑解耦,通过消息队列进行数据交互。技术选型服务端生成PDF通常有两种方案:
第一种是使用 pdfkit 之类的工具通过代码绘制,这种方案最大的问题是可渲染的内容类型有限,定制化不足;第二种是创建 headless browser用html渲染后截取pdf,这种方案的架构相对复杂,但是可以支持所有web端的内容类型。个册第一部分学情分析的某一页长这个样子:仅这一页就涵盖文本、表格、图表以及各种自定义图案,内容类型多样并且后续迭代可能增加更多定制化内容,第一种方案的局限性很难满足需求,所以最终选定 headless browser 方案。
具体到 headless browser 的技术选型就非常有限了,可选的无非就是 Selenium/PhantomJS 这类老招牌,或者 Puppeteer/Playwright 这类新玩家。
严格来说Selenium只是一种类似按键精灵的工具,可通过代码在浏览器中模拟人的操作,本身并不是浏览器,所以需要搭配第三方浏览器使用,比如PhantomJS。Selenium/PhantomJS 的最大的优点就是生态健全,支持多种编程语言,有相对繁荣的技术社区;缺点就是稳定性和性能较差,Selenium的稳定性出了名的糟糕,PhantomJS五年前就停止维护了。这哥俩通常用在对稳定性要求不是很高的场景,比如爬虫。
与之形成鲜明对比的,Puppeteer/Playwright 最大的优点就是稳定性高,性能更优;缺点就是对编程语言的支持有限,生态和技术社区相对没那么健全。
个册的业务特征一是对稳定性和性能要求很高;二是不要求跨浏览器(Playwright支持浏览器类型更丰富)。最终综合考虑API易用性、稳定性、性能、社区、风险等因素,在 Puppeteer 和 Playwright 之间选择了 Puppeteer。既然选定了 Puppeteer,配套的自然就是 Node.js了。
实现方案Puppeteer 和 Playwright 的对比可以参考这篇文章:Playwright vs Puppeteer: Core Differences。这个需求是我第一次使用Puppeteer,还没完全摸透,下文涉及到Puppeteer相关的方案如果有问题,欢迎讨论指点。
智慧教育的分层架构如下:
Node.js PDF服务是本次需求新增的,为了方便分离部署和优化,PDF服务单独建立一个服务,不涉及Node.js接入层的改动。下图是个册PDF加工的完整流程:
每个环节的具体流程不细讲,Node.js PDF加工服务的细节下文详解。与Node.js PDF服务相关最关键的是与Java后端的数据交互流程。Java后端与Node.js PDF服务通过 RabbitMQ 消息队列进行数据交互,建立两个队列:
队列 | 生产者 | 消费者 | 说明 |
---|---|---|---|
任务队列 | Java后端 | Node.js PDF服务 | Java 向队列中发送个册渲染数据,Node.js 消费 |
回传队列 | Node.js PDF服务 | Java后端 | Node.js 向队列中发送pdf加工结果数据,Java 消费 |
这部分没啥好讲的,Node.js与Java之间按照约定的数据规范组装数据即可,下面详细介绍一下Node.js加工pdf的具体逻辑。
单份PDF加工流程这一版个册的第一部分学情分析控制在3页,早期规划的个册PDF大约25页左右,技术调研和架构设计都是基于这个预期进行的,所以现在这套模式多少有点杀鸡用牛刀的意思,不过前期打好基础给后续迭代留些空间也是好事。
为了更方便理解,在介绍pdf加工流程之前,有必要先简要一下Node.js PDF服务的架构,以及与PDF加工逻辑最相关的 worker角色。
Node.js PDF服务架构最核心的三个角色:
Scheduler:负责轮询调度,发起任务;Executor:负责任务前置和后置相关逻辑,包括worker pool管理、worker 调度、MQ任务队列消息拉取、MQ回传队列消息发送等;Worker:负责实质执行任务,包括pdf渲染、生产、上传OSS;三者的关系如下所示:Scheduler和 Executor的具体逻辑以及三个角色之间的调度逻辑下文再详解,PDF文件的实质生产逻辑都集中在 Worker中,流程如下:
预启动图中「发送消息至MQ回传队列」实质是由 Executor执行,此处画出方便理解完整流程。
图中虚线部分的预启动是在启动 Node.js 服务之前执行的逻辑,预启动完成之后 Node.js 服务被拉起,所以预启动的耗时是一次性的。
预启动过程执行两个动作:
读取磁盘中的html文件内容,写入内存,为后续环节「加载网页」提供数据;创建 Puppeteer browser 实例。冷启动(废弃)上图中只画出pdf加工逻辑相关的预启动工作,实际上预启动还包含一些其他逻辑,比如建立 MQ 连接信道。
虽然冷启动在后来开发过程中被废弃,但通过这个事情发现自己的不足,还是值得记录一下的。最初之所以设想冷启动环节,是因为尝试用 worker 模拟多线程。每个worker会创建一个browser实例和多个page实例(目前是3个),如下所示:
这样做的目的是将每个worker的负载上限固定,便于服务器资源规模预估,避免服务器某个节点负载过高,进而也可以避免k8s集群pod的纵向伸缩。
链接/创建browser实例创建page实例另外增加一个标识位_mounted代表冷启动是否完成,代码如下:k8s纵向伸缩的取舍见仁见智,我个人不太建议使用。如果任务队列长时间为空会触发缓存清理逻辑,销毁browser和page实例以节省服务器资源,再次发起任务会触发冷****启动。冷启动执行两件事情:
public async run(){ if(!this._mounted){ // 触发冷启动 this._mount(); } // ...其他逻辑}private async _mount(){ if(!this._browser?.isConnected()){ // 链接browser this._browser = await puppeteer.connect({ browserWSEndpoint: this._wsEndpoint }); } // 创建page实例 if(isEmpty(this._pages)){ for(let i =0;i
乍看起来似乎没啥问题,但实际跑一跑代码会发现,在任务调度密集的时候,run函数短时间内被调用多次(具体的调度策略下文讲解),worker会触发多次冷启动,虽然不影响业务逻辑,但会引起服务器资源暴涨,这是因为冷启动会创建新的browser和page实例,但是旧实例并没有被清理,仍然在执行任务。冷启动被调用多次的根本原因是Node.js不是多线程,如下图所示,假设冷启动耗时20ms,在此期间再次调用run函数,标识位_mounted还未被设置为true,就会又触发一次冷启动。
有没有解法?
当然有。多线程编程解决竞态最常用的就是:加锁。既然想模拟多线程那就彻底一点,把锁逻辑也加上呗。
worker本身是有“锁”的,每个worker有3个page实例,只有当存在空闲实例(busy为false)时run函数才可以执行,但是这个锁机制并不能避免多次冷启动问题,因为冷启动完成之前page实例还未被创建。
可能会有人说,那就加个限制,page实例不存在时也不让run函数执行不就得了?这么做的话run函数永远都不会被执行啊大聪明。
既然worker已有的锁不行,那就再加个冷启动锁,冷启动之前锁定,冷启动之后解锁。这么做当然是可以的,但是会增加逻辑复杂度,worker有两种锁,对后期迭代维护无疑是埋雷。
其实之所以有冷启动无非就是为了省点内存,用时间换空间,一个browser实例+3个空白page实例总共100m左右的内存,这年头内存这么便宜,为了省这点空间把逻辑搞那么复杂完全得不偿失。什么叫过度设计,这就是过度设计。
所以后来索性把冷启动过程干掉了,browser和page实例的创建放在worker初始化逻辑里。
public async init() { /** * 尽量禁用掉不需要的功能,提高性能 */ this._browser = await puppeteer.launch({ headless: true, args: [ "--incognito", "--disable-gpu", "--disable-dev-shm-usage", "--disable-setuid-sandbox", "--no-first-run", "--no-sandbox", "--no-zygote", "--single-process" ] }); this._wsEndpoint = this._browser.wsEndpoint(); // _mount函数逻辑不改动,调用_mount函数放在初始化逻辑中 await this._mount();}
加载网页网页通过page.setContent(html)函数加载本地html文件,与通过page.goto(url)加载远程URL相比,既节省了部署网页的服务器资源,同时速度也更快:
时间消耗 执行时机 性能瓶颈 其他 远程URL DNS耗时下载耗时解析html耗时 运行时 网络IO 异步下载html引用的静态资源会增加额外耗时 本地html 读磁盘耗时解析html耗时 预启动阶段 文件IO+常驻内存
上文提到过,本地html文件在预启动阶段提前从磁盘读取存放于内存,运行时无需实时读取。所以文件IO的耗时不算在pdf加工逻辑总耗时中,而加载远程URL只能在运行时执行,会增长pdf加工的总时长。
另外,加载的本地html文件中不能存在静态资源引用,比如js和css必须全部以行内
关键词:
- Puppeteer+RabbitMQ:Node.js 批量加工pdf服务架构设计与落地
- 子流形几何中积分形式的移动平面法_每日短讯
- 港股异动 | 吉林长龙药业(08049)早盘飙涨逾50%创6年来新高 一季度归母溢利同比增长55.8% 快资讯
- 世界快看点丨天津周杰伦演唱会门票2023
- 华如科技:智能模型库(AIModels)着力将人工智能技术应用于军事仿真建模中
- 旅泰大熊猫“林惠”去世-最新
- 产教融合 实训实学
- 世界快播:学国画有前途吗_学国画
- 世界焦点!深蓝汽车第二款新车亮相:深蓝S7定位中型SUV,提供三种动力选择
- 金观平:中国经济有信心有能力行稳致远_全球热议
- 【天天快播报】看日全食与日环食的双重震撼:罕见日全环食20日上演
- 2023山东济宁市鱼台县事业单位(教育类)招聘80人报名入口 观速讯
-
Puppeteer+RabbitMQ:Node.js 批量加工pdf服务架构设计与落地
2023-04-20 14:46:31
-
当值华为轮值董事长后首次亮相,孟晚舟在分析师大会说了什么?(附演讲全文)
2023-04-20 14:42:39
-
世界微动态丨孟晚舟当值华为轮值董事长后首次亮相:谈华为数字化战略
2023-04-20 14:40:36
-
子流形几何中积分形式的移动平面法_每日短讯
2023-04-20 14:47:11
-
山东泗水两辆重型半挂牵引车发生碰撞 致7死10伤 世界即时
2023-04-20 14:51:26
-
意大利“洋媳妇”在广西:喝喜酒赶庙会 乐享中国乡村生活
2023-04-20 14:38:50
-
嫩草_世界今头条
2023-04-20 14:42:54
-
广州必去的景点排名天下第一_广州必去的景点排名
2023-04-20 14:49:05
-
港股异动 | 吉林长龙药业(08049)早盘飙涨逾50%创6年来新高 一季度归母溢利同比增长55.8% 快资讯
2023-04-20 14:20:20
-
快易花借款逾期六年多久会上征信系统
2023-04-20 14:23:23
-
键盘第三个灯亮了怎么关不了 键盘第三个灯亮了怎么关
2023-04-20 14:24:42
-
新东方的压力给到了东方甄选 快讯
2023-04-20 14:07:38
-
商汤绝影三度亮相上海车展 AI大模型打造智能汽车量产落地新范式 每日看点
2023-04-20 14:06:39
-
医保部门今起展开种植牙价格综合治理 单颗种植牙价格减半
2023-04-20 14:02:32
-
【世界播资讯】东银国际控股(00668):东锐与重庆嘉望订立保理协议
2023-04-20 13:47:08
-
中国生态旅游(01371)成功中标系统和投注站管理服务项目
2023-04-20 13:45:11
-
碧桂园文商旅与签约浙江丽水松阳瓦窑头文商旅综合体项目_全球视讯
2023-04-20 13:43:34
-
巴比食品:一季度净利润4103.2万元 同比增长27倍-每日关注
2023-04-20 13:34:30
-
世界快看点丨天津周杰伦演唱会门票2023
2023-04-20 13:17:56
-
华如科技:智能模型库(AIModels)着力将人工智能技术应用于军事仿真建模中
2023-04-20 13:14:33
-
《人生路不熟》“吹牛牪犇”预告发布 田雨演绎“人生赢家” 每日焦点
2023-04-20 13:16:16
-
神龙汽车屈洪宇:价格早已经打下来,缺的是关注
2023-04-20 13:09:53
-
2023上海车展:长城6×6皮卡领衔 这些新车有两把刷子
2023-04-20 13:03:03
-
当前简讯:蜜雪冰城魔性营销,怎么什么圈子都混啊?
2023-04-20 13:07:08
-
小儿止咳糖浆说明书_葵花小儿止咳糖浆说明书_全球今头条
2023-04-20 13:05:31
-
旅泰大熊猫“林惠”去世-最新
2023-04-20 12:30:23
-
产教融合 实训实学
2023-04-20 12:49:55
-
索要1.4亿肥约!勇士二当家狮子大开口,管理层请求库里出面劝说
2023-04-20 12:33:52
-
darker是什么意思_darker到底是谁-环球播报
2023-04-20 12:32:50
-
全球新资讯:北芝加哥市与Solstice合作为居民提供负担得起的社区太阳能
2023-04-20 12:28:35
Copyright @ 2015-2022 中国砍柴网版权所有 备案号: 沪ICP备2022005074号-4 联系邮箱:58 55 97 3@qq.com