写点什么

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

评论 1 条评论

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

从 0 到 1,探究百亿流量验证下的 MVVM 框架设计

图灵教育

百度 MVVM 全栈设计

跨平台多媒体渲染引擎OPR简介

阿里巴巴文娱技术

音视频 弹幕 渲染

defi存币生息理财dapp系统开发逻辑

开发微hkkf5566

资深OpenStacker - 彭博、Vexxhost升级为OpenInfra基金会黄金成员

Geek_2d6073

大数据生态安全框架的实现原理与最佳实践(上篇)

明哥的IT随笔

大数据 hadoop hive 数据安全

技术干货 | Linkis1.0.2安装及使用指南

康月牙

开源社区 微众银行 WeDataSphere Linkis 使用实践

5年“研究”3年“实战” 之后的满分答卷

青藤云安全

网络安全 容器安全 安全服务 云原生安全

SphereEx 正式开源面向 Database Mesh 的解决方案 Pisanix

SphereEx

开源 SphereEx 云上数据库 Database Mesh Pisanix

秒云云原生信创全兼容解决方案,推动信创产业加速落地

MIAOYUN

云原生 信创 信创云

君可归烈士寻亲系统开发实战

乌龟哥哥

6月月更

618 大促来袭,浅谈如何做好大促备战

阿里巴巴云原生

阿里云 微服务 高可用 云原生

flutter系列之:Material主题的基础-MaterialApp

程序那些事

flutter 程序那些事 6月月更 widget

InfoQ 极客传媒 15 周年庆征文|手把手教你使用Python实现一键抠图,照片换背景|so easy!

迷彩

Python AI 前端 6月月更 InfoQ极客传媒15周年庆

智充推出NET ZERO SERIES储能充一体机,携手比亚迪共创净零未来

Geek_60c26d

数据产品学习-实时计算平台

第519区

实时计算 数据产品 数据开发 大数据平台

八连冠!浪潮云连续8年蝉联中国政务云市场第一位

云计算

华为云零代码开发图片压缩工具

乌龟哥哥

6月月更

谁说Redis不能存大key

华为云开发者联盟

数据库 华为云

入驻快讯|欢迎XCHARGE智充科技正式入驻 InfoQ 写作社区!

Geek_60c26d

中原银行统一日志平台

中原银行

海量数据 中原银行 日志平台

数据安全刻不容缓,国产智能化厂商首获SOC 2鉴证报告有何意义?

王吉伟频道

RPA 数据安全 机器人流程自动化 智能自动化 SOC 2

亚马逊云科技向你发出召唤——游戏开发者,集合!

亚马逊云科技 (Amazon Web Services)

react.js edge postcss

后端适用,Apifox接口文档设计和调试教程【工具篇】

Liam

Java 后端 Postman 后端开发 API文档

小程序启动性能优化实践

百度Geek说

直播预告 | 社交新纪元,共探元宇宙社交新体验

ZEGO即构

InfoQ 极客传媒 15 周年庆征文|在Flutter中自定义应用程序内键盘

坚果

InfoQ极客传媒15周年庆

快来极狐GitLab SaaS 学习全球顶级的开源项目吧

极狐GitLab

开源

CREMB Pro 后台子管理员 403 问题分析

CRMEB

音频 3A 处理实践,让你的应用更「动听」

融云 RongCloud

充电桩的B面是......不只公众号?还有智充小程序!

Geek_60c26d

低代码分析盘点:银行业低代码应用需要规避两大误区

易观分析

代码 银行

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