写点什么

深入浅出动态化 SSR 服务(二):SSR 服务篇

  • 2020-04-02
  • 本文字数:4092 字

    阅读完需:约 13 分钟

深入浅出动态化SSR服务(二):SSR服务篇

现代 SSR 之殇

在第一篇的技术选型中我们有说到,对于 SSR 的技术选型实际上有两个思路,其分别是:


  1. HandleBars等以纯字符串渲染引擎为主的思路

  2. Vue/React等现代 UI 框架以 Virtual DOM 为主的思路


虽然我们最后贯彻 开发友好 为准则选择了Vue/React等现代 UI 框架,但实际上其弊端(性能和内存占用)相对于字符串渲染引擎来说仍然有非常大的差距。从Vue的初始化过程我们很容易分析出,由于整个过程需要首先生成大量的 Virtual DOM 对象,然后再从 Virtual DOM 对象生成模板字符串,因此相比字符串渲染引擎来说避免不了会有更多的内存占用和 CPU 消耗。


这个问题在动态化的页面可视化 SSR 服务来说会更严重,考虑到直接使用Vue CLI同构并不进行优化的场景,对于普通页面可视化 SSR 服务来说,其开发编译和执行过程如下:


  1. 我们存在一个服务的入口页面组件,其包含所有编写的组件,并通过Vue<component>进行动态初始化,同时暴露此页面组件对象给外部调用

  2. 使用Vue CLI进行打包,获得entry-server.js

  3. 启动 SSR 服务,监听端口,准备接收请求进行渲染操作

  4. 当请求到达时,我们请求需要渲染的页面数据信息,拿到当前页面所依赖的组件信息

  5. 传递对应组件信息给入口页面组件,并使用VuerenderToString方法获取得到对应的 HTML 字符串信息


从上面的过程我们可以看到,由于页面对应的组件是完全动态的,因此入口页面组件实际上需要注册所有已知的开发组件。Vue的初始化注册操作实际上是很耗时的,特别是在自身组件越来越多的情况下,而且对于某些简单的页面来说,这样的做法实际上会造成很多的性能浪费。考虑《开发工具篇》我们提到的一个场景:


D1 发布结果包含了 100 个组件,但是对于某一生成页面而言,只需要对 2 个组件进行重复渲染。


因此我们需要进行按需加载以及按需初始化注册,避免整体组件的加载及初始化注册,以此提高性能。

组件的按需加载

在《开发工具篇》中我们已经通过sis拿到了我们依赖关系表,同时也对每个组件的编译进行了拆分,因此要达到组件的按需加载需求实际上是比较容易的,如图所示:



其执行过程为:


  1. 客户端请求对应的页面数据

  2. 根据页面 ID 调用相关接口或数据库/缓存获取得到对应的页面与组件的数据信息(步骤 1-2)

  3. 根据组件的信息查找依赖关系表,获取得到组件代码的加载路径(步骤 3-4)

  4. 加载对应的加载路径,获取对应的组件代码并进行返回(步骤 5-6)

  5. 动态执行对应的组件代码,并对Vue实例进行注册,并调用renderToString方法获取对应的 HTML 信息

  6. 返回 HTML 信息给客服端


其用代码描述大致如下:



值得说明的是,由于sissis-ssr是以一个公共服务来进行地设计,各个团队对应的编译产物是存放在云端的对象存储之中。这样,其他团队使用此平台就不需要(也不应该)涉及到sis-ssr的部署及重启。因此,对于页面的组件代码获取而言是需要依靠downloader来进行本地/远程加载的。

组件代码的缓存

由于整个系统涉及到了组件代码的动态化加载,因此downloader的性能也会一定情况下影响单请求单页面的渲染信息返回速度。通过《开发工具篇》我们可以知道,使用合并优化后仍然会有一些公用依赖代码,但实际生产的运行过程中,公用代码的缓存使用率会比较高,因此这个时候我们可以在downloader内部增加缓存。另外,除了Node自身的内存缓存外,我们还可以增加一层文件缓存,尽可能的保证加载的性能(例如服务重启后的加载性能)。对于内存缓存而言,我们一般使用 LRU,以此来帮助我们主动清理 HIT 数量比较低的缓存内容,其代码如下:



当然,整个过程中你还可以使用Promise.all进行并行加载来进一步提高对应的组件代码的加载效率。

页面缓存

除了组件代码的缓存外,在正常的实际运行中,我们一般还需要增加页面的缓存,在这里我们同样使用LRU和文件缓存来达到这一目的,代码如下:


简单页面的压力测试

现在我们来对一个 Hello 页面进行简单的压力测试,其渲染的组件代码为一个简单的子组件的嵌套,如代码所示:



sis-ssr对比的实现为vue2-ssr-example, 两者依赖库/框架均为:


  • vue@2.6.11

  • vue-server-renderer@2.6.11

  • express@4.17.0


测试的服务器配置为:


  • 阿里云 - 计算型 - 4 核 8G


注意,在此压力测试中sis-ssr去除了页面缓存,仅保留了组件代码的缓存,页面的组件信息获取写死在代码之中,两者的渲染执行逻辑基本一致。以pm2 start index.js -i 4的方式启动并进行压测,其最终结果为:


结果分析

我们可以看到在总共 2000 个请求 300 个并发的情况下sis-ssr相比vue2-ssr-example的整体渲染性能会好很多,甚至高于 100%!可能有读者会有疑问:“按照分析来看,难道不是应该vue2-ssr-example性能会更高或者相差不大么?”,实际上确实应该如此,其拖慢vue2-ssr-example性能的最重要原因其实是Webpack编译时的逻辑。


我们知道对于Webpack等前端编译工具打包而言,其会按照对应的配置进行对应的代码ES5.1之类的语法编译及polyfill引入的优化,而对于sis来说,我们在打包 SSR 时并没有进行相关的优化,尽可能让对应的编译的代码结果足够干净,由于sis-ssr跑的代码大部分都是 Native 的语法和原生方法,相比vue2-ssr-example产出的代码来说会有不小的提升。


假设我们将这些部分进行类似sis的优化,那么实际上压力测试结果会比较相近。在 Hello 页面的压测之中彼此的性能差距在 3%左右。


从这个例子我们可以看出,对于性能优化而言是需要从细小处做起,多个细小处做到极致合起来就能得到意想不到的提升。

一般页面的压力测试

但在实际项目中,我们往往没有那么简单的页面,反而会有更复杂的组件嵌套以及调用关系。在这个压力测试中我们引入ElementUI中几个组件来进行比较,组件如代码所示:



注意,此次压力测试中我们仍然不修改vue2-ssr-example的相关编译配置,其最终测试结果为:


结果分析

我们可以看到sis-ssr相比较于vue2-ssr-example来说仍然有 20%的性能提升,这是符合我们的预期的,因为从ElementUI编译得到的代码后我们可以看到,实际上sis引入的代码中有相当多的代码已经被预编译成ES5.1并加入了对应的polyfill了,所有的执行热点基本上是由于ElementUI自身逻辑造成的,因此sis-ssr相对vue2-ssr-example的提升就不会像 Hello 页面那么明显了。


总之,基于sissis-ssr的 SSR 服务在实际生产的页面渲染场景相对于目前大部分其他实践方案来说是有比较可观的性能收益的。


这里的vue2-ssr-example的压测结果相比 Hello 页面 测试结果来说降低得并不是特别多,其原因在于:实际上我们引入的ElementUI组件还是比较简单的组件,相比 Hello 页面 而言增加的执行逻辑并没多太多,但sis-ssr因为以上讨论原因下降会比较明显。以上内容均可以通过NodeProfile工具分析得出。

超时、限流与降级

在实际生产中,服务高性能当然是值得高兴,但如果是没有稳定性的高性能,那么实际上就没有那么让人愉悦了。在sis-ssr中为了保证对应的单机服务的稳定性,我们分别采取了三个策略,其分别是:


  • 超时策略

  • 限流策略

  • 渲染降级策略


sis-ssr中实际上会出现不少的异步请求的情况,例如downloader的远程加载组件代码。对于这些服务端的异步请求,我们一般都会强制考虑请求的超时处理,防止请求长时间被挂起造成的问题,例如:



其次,为了保证我们的单机服务不被外部流量冲击,我们也需要加入限流的策略。其中比较常用的限流策略包括:固定窗口算法、滑动窗口算法、漏桶算法以及令牌桶算法等。在Node中有存在基于redisexpress-rate-limitNPM包帮助我们完成相关的逻辑。


最后sis-ssr为了应对各种请求/流量异常的情况还做了多级的降级策略:


  1. 服务端数据请求成功且Node端渲染成功,正常返回

  2. 服务端数据请求成功但Node端渲染失败,则抛弃首屏并将相关数据写入页面,由客户端进行渲染

  3. 流量异常/系统资源占用过高,完全由客户端请求数据并进行渲染


至此由上至下进行兜底,以此保证服务的可用性。除此之外,由于Node的单进程单主线程(在这里我们排除 I/O 异步等事件循环中的子线程)且页面渲染是纯 CPU 操作的特性,其在渲染大页面时经常会出现阻塞运行时主线程的情况。因此我们可以创建包含一定数量工作线程的线程池(使用Nodeworker_thread),然后将对应的页面渲染放置在Worker工作线程之中,当线程池中无空闲工作线程时,Master 线程进行主动的页面降级渲染以此来提高对应的性能。此功能已在sis-ssr中得到了实现并取得了极好的实际效果,其压力测试结果如图所示:



此次压测的各环境不变,同时测试对象主要是 Hello 页面。从结果中我们可以看到,搭配了主动降级+Worker的渲染方式相比后两者有非常大的提升,其原因也很简单,因为大部分请求由于Worker不空闲均被降级为牺牲首屏并将数据写入页面,由客户端渲染的方式了(在上面的压力测试下,大约有 10%左右的请求是真正的Node渲染,其余的都走降级页面了)。


在这种情况下,由于 Google 已经支持同步的前端渲染页面的收录,所以降级渲染请求并不会影响到 SEO。那么对于内容到达时间(time-to-content)呢?我们可以做一个粗糙的计算(实际请以数据日志为主),在我们 只关注于渲染时间且单机单进程 情况下,假设并发 10 个请求,Node 端每次渲染耗时 100ms,那么完全以 Node 端渲染来说,最后一个用户的内容到达时间为 1s。若考虑降级,降级页面的渲染时间为 50ms,则最后一个用户获得页面 HTML 的时间为 350ms,若静态资源加载加上同步的前端渲染页面耗时小于 650ms,则相对于完全 Node 端渲染的方案有提升。同时我们需要注意,随着并发数的增加,此提升会越来越高。若并发数提升为 20 时,同样的计算后我们可以得到完全 Node 渲染的方式最后一个用户的内容到达时间为 2s,而降级页面最后一个用户获得页面 HTML 的时间为 750ms,若静态资源加载加上同步的前端渲染页面耗时小于 1250ms 则具有对应的提升。

总结与期待

在本章我们较为详细的探讨了sis-ssr的一些内部逻辑,同时与vue2-ssr-example进行了单机的压力测试的比较并分析了对应的原因。同时我们也讲述了sis-ssr是如何保证生产环境的高可用性的。在下一篇中我们会从这些细节脱身,从更全局和整体的架构角度来看待整个动态化 SSR 服务。敬请期待《深入浅出动态化 SSR 服务(之三) - 架构篇》。


相关阅读


https://www.infoq.cn/article/SlgQEvW8VGt8EEiTeXEd


2020-04-02 10:002916

评论 1 条评论

发布
用户头像
对页面的缓存,如果再有登录态或者其他状态的情况下会不会有问题?
2021-12-07 10:08
回复
没有更多了
发现更多内容

ChatGPT与传统搜索引擎的区别:智能对话与关键词匹配的差异

天津汇柏科技有限公司

搜索引擎; ChatGPT

IO模型介绍(select、poll、epoll)

京东科技开发者

XPath攻略:从入门到精通,告别查找困难!

测试人

软件测试

网络空间测绘系统的商业应用

郑州埃文科技

网络空间测绘技术

咸鱼翻身啦!咸鱼之王完美内购版架设教程

echeverra

咸鱼之王

发掘非结构化数据价值:AI 在文档理解领域的现状与未来

Baihai IDP

程序员 AI 非结构化数据 白海科技

【京东云新品发布月刊】2024年3月产品动态

京东科技开发者

百度智能云千帆,产业创新新引擎

百度Geek说

AI 百度智能云 千帆大模型平台

solana链土狗抢单机器人

区块链技术

java线程池原理浅析

京东科技开发者

2024南京国际自动驾驶技术展览会

AIOTE智博会

自动驾驶展 智能驾驶展

Python数据库编程全指南SQLite和MySQL实践

华为云开发者联盟

Python MySQL 开发 华为云 华为云开发者联盟

京东商品列表数据接口

tbapi

京东 京东API接口 京东商品列表数据接口

大模型时代下的“金融业生物识别安全挑战”机遇

中关村科金

安全 金融 防伪 生物识别

"线程池中线程异常后:销毁还是复用?"

京东科技开发者

大型IM工程重构实践:企业微信Android端的重构之路

JackJiang

即时通讯;IM;网络编程

独立站如何做好移动端的优化处理呢?

技术冰糖葫芦

API 接口

探索华为云CCE敏捷版金融级高可用方案实践案例

华为云开发者联盟

开发 华为云 容器集群 华为云开发者联盟 华为云CCE

助力水下潜行:浮力调节系统仿真

DevOps和数字孪生

XPath攻略:从入门到精通,元素查找不再难

霍格沃兹测试开发学社

火山引擎VeDI:A/B测试开放平台正式上线,企业个性化平台一键定制

字节跳动数据平台

大数据 A/B测试 企业号 3 月 PK 榜 对比试验 数字化增长

火山引擎发布ByteHouse性能白皮书,揭秘OLAP性能突破的关键技术

字节跳动数据平台

数据库 大数据 云原生 数仓 企业号 3 月 PK 榜

vivo 消息中间件测试环境项目多版本实践

vivo互联网技术

RocketMQ 消息中间件 多版本环境隔离

展示大屏-24小时天气预报

叫练

抢开盘、抢土狗、批量抢买、卖出机器人工具使用说明

区块链技术

【论文速读】| 越狱:大语言模型安全训练是如何失败的?

云起无垠

AI能代替软件工程师吗?

小魏写代码

深入浅出动态化SSR服务(二):SSR服务篇_数据库_赵洋_InfoQ精选文章