NVIDIA 初创加速计划,免费加速您的创业启动 了解详情
写点什么

深入浅出动态化 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:002676

评论 1 条评论

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

文心一言 VS 讯飞星火 VS chatgpt (134)-- 算法导论11.2 6题

福大大架构师每日一题

福大大架构师每日一题

探索向量数据库 | 重新定义数据存储与分析

-亦世凡华、

数据库 亚马逊云科技 向量数据库

正式开源!网易有道上线“易魔声”语音合成引擎

有道技术团队

人工智能 语音合成 TTS

重磅!科技感拉满!

天翼云开发者社区

人工智能 云计算

数据结构与算法 | 记忆化搜索(Memorize Search)

不在线第一只蜗牛

数据结构 算法 数据

Util应用框架基础(六)- 日志记录 - Exceptionless

何镇汐

开源 后端 软件开发

云图说|华为云主机安全新版本上线

华为云开发者联盟

华为云 华为云开发者联盟 华为云云图说

在AI时代,提升程序员竞争力的关键策略

不在线第一只蜗牛

人工智能 编程 程序员 AI

公司让我开发一个管理系统,有了它,So easy!

互联网工科生

软件开发 低代码 快速开发 JNPF

Util应用框架基础(六)- 日志记录 - File

何镇汐

开源 后端 软件开发

聚势启新,KaiwuDB 生态联盟沙龙首站落地长春

KaiwuDB

同济 MBA × 和鲸:聚焦商业数据思维培养,赋能工管人才转型升级

ModelWhale

人才培养 企业数字化转型 数智化 MBA 同济大学

崩溃的阿里云,并非是单纯的坏事?

ToB行业头条

VPC终端节点的实现架构和原理

天翼云开发者社区

VPC终端节点

特权账号管理之定期改密篇

尚思卓越

网络安全 定期改密

Kubernetes Operator可以做什么?

高端章鱼哥

kubernetes 运维

「我在淘天做技术」假如你五行属商家,如何算好账?

阿里技术

财务 算好账 财务开发

重磅!天翼云发布一站式智算服务平台“慧聚”

天翼云开发者社区

人工智能 云计算 云服务 云平台

为什么 Amazon Bedrock 中的模型只有部分可用?

花花

亚马逊云科技

Layer 2 真的为以太坊扩容了吗?

Footprint Analytics

以太坊 Layer 2

时序数据库 TDengine + 高级分析软件 Seeq,助力企业挖掘时序数据潜力

TDengine

tdengine 时序数据库

Docker 和 Kubernetes:技术相同和不同之处

EquatorCoco

Docker k8s K8s 多集群管理 kubernetes 运维

DAZ Studio for Mac(专业三维人物动画制作工具) 4.20.0.17永久激活版

mac

苹果mac Windows软件 DAZ Studio 3D造型渲染软件

Util应用框架基础(六)- 日志记录 - Seq

何镇汐

开源 后端 软件开发

在HarmonyOS上实现ArkTS与H5的交互

HarmonyOS开发者

HarmonyOS

英特尔锐炫GPU助力AI向大众用户市场普及

E科讯

紧密合作三周年,Elastic颁发腾讯云2022年杰出开源贡献奖

腾讯云大数据

ES

全域全自主建设,亚信科技AntDB数据库助力广电5G业务上线运行

亚信AntDB数据库

数据库 AntDB AntDB数据库

常见光模块的封装类型有哪些?

小魏写代码

RestCloud AppLink已支持的数据源有哪些?

RestCloud

零代码 APPlink 自动化集成

低代码平台如何提高开发效率?

高端章鱼哥

软件开发 低代码 JNPF

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