9月7日-8日,相约 2023 腾讯全球数字生态大会!聚焦产业未来发展新趋势! 了解详情
写点什么

如何搭建一个高可用的服务端渲染工程

  • 2019-10-09
  • 本文字数:5115 字

    阅读完需:约 17 分钟

如何搭建一个高可用的服务端渲染工程

可能大家在看到这个标题的时候,会觉得,只不过又是一篇烂大街的 SSR 从零入门的教程而已。别急,往下看,相信你或多或少会有一些不一样的收获呢。


在落地一种技术的时候,我们首先要想一想:


  1. 是否一定需要引入这种技术呢?他能解决什么问题,或者能带来什么收益?

  2. 为什么要采用这种技术选型而不是其他的?

  3. 引入了这种技术后,会带来什么问题吗(比如额外的开发成本等)?


上面三个问题思考清楚之后,才能真正地去落地。上面三个问题思考清楚之后,才能真正地去落地。而有赞教育接入服务端渲染,正是为了优化 H5 页面的首屏内容到达时间,带来更好的用户体验(顺便利于 SEO)。


说了这么多,以下开始正文。



一、后端模版引擎时代

在较早时期,前后端的配合模式为:后端负责服务层、业务逻辑层和模版渲染层(表现层);前端只是实现页面的交互逻辑以及发送 AJAX。比较典型的例子就是 JSP 或 FreeMarker 模板引擎负责渲染出 html 字符串模版,字符串模版里的 js 静态资源才是真正前端负责的东西。


而这种形式,就是天然的服务端渲染模式:用户请求页面 -> 请求发送到应用服务器 -> 后端根据用户和请求信息获取底层服务 -> 根据服务返回的数据进行组装,同时 JSP 或 FreeMarker 模版引擎根据组装的数据渲染为 html 字符串 -> 应用服务器讲 html 字符串返回给浏览器 -> 浏览器解析 html 字符串渲染 UI 及加载静态资源 -> js 静态资源加载完毕界面可交互。



那么既然后端模版引擎时代带来的效果就是我们想要的,那为啥还有以后让前端发展服务端渲染呢?因为很明显,这种模式从开发角度来讲还有挺多的问题,比如:


  1. 后端需要写表现层的逻辑,但其实后端更应该注重服务层(和部分业务逻辑层)。当然,其实也可以让前端写 JSP 或 FreeMarker,但从体验上来说,肯定不如写 JS 来的爽;

  2. 本地开发的时候,需要启动后端环境,比如 Tomcat,影响开发效率,对前端也不友好;

  3. 所赋予前端的能力太少,使得前端需要的一些功能只能由后端提供,比如路由控制;

  4. 前后端耦合。

二、SPA 时代

后来,诞生了 SPA(Single Page Application),解决了上面说的部分问题:


  1. 后端不需要关心表现层的逻辑,只需要注重服务层和业务逻辑层就可以了,暴露出相应的接口供前端调用。这种模式也同时实现了前后端解耦。

  2. 本地开发的时候,前端只需要启动一个本地服务,如:dev-server 就可以开始开发了。

  3. 赋予了前端更多的能力,比如前端的路由控制和鉴权,比如通过 SPA + 路由懒加载的模式可以带来更好的用户体验。



但同时,也带来了一些问题:


  1. 页面的 DOM 完全由 js 来渲染,使得大部分搜索引擎无法爬取渲染后真实的 DOM,不利于 SEO。

  2. 页面的首屏内容到达时间强依赖于 js 静态资源的加载(因为 DOM 的渲染由 js 来执行),使得在网络越差的情况下,白屏时间大幅上升。

三、服务端渲染

正因为 SPA 带来的一些问题(尤其是首屏白屏的问题),接入服务端渲染显得尤为必要。// 终于讲到服务端渲染这个重点了。


而正是 Node 的发展和基于 Virtual DOM 的前端框架的出现,使得用 js 实现服务端渲染成为可能。因此在 SPA 的优势基础上,我们顺便解决了因为 SPA 引入的问题:


  1. 服务端渲染的首屏直出,使得输出到浏览器的就是完备的 html 字符串模板,浏览器可以直接解析该字符串模版,因此首屏的内容不再依赖 js 的渲染。

  2. 正是因为服务端渲染输出到浏览器的是完备的 html 字符串,使得搜索引擎能抓取到真实的内容,利于 SEO。

  3. 同时,通过基于 Node 和前端 MVVM 框架结合的服务端渲染,有着比后端模版引擎的服务端渲染更明显的优势:可以优雅降级为客户端渲染(这个后续会讲,先占个坑)。

3.1 实现

既然服务端渲染能带来这么多好处,那具体怎么实现呢?从官网给出的原理图,我们可以清晰地看出:


  • Source 为我们的源代码区,即工程代码;

  • Universal Appliation Code 和我们平时的客户端渲染的代码组织形式完全一致,只是需要注意这些代码在 Node 端执行过程触发的生命周期钩子不要涉及 DOM 和 BOM 对象即可;

  • 比客户端渲染多出来的 app.js、Server entry 、Client entry 的主要作用为:app.js 分别给 Server entry 、Client entry 暴露出 createApp()方法,使得每个请求进来会生成新的 app 实例。而 Server entry 和 Client entry 分别会被 webpack 打包成 vue-ssr-server-bundle.json 和 vue-ssr-client-manifest.json(这两个 json 文件才是有用的,app.js、Server entry 、Client entry 可以抽离,开发者不感知);

  • Node 端会根据 webpack 打包好的 vue-ssr-server-bundle.json,通过调用 createBundleRenderer 生成 renderer 实例,再通过调用 renderer.renderToString 生成完备的 html 字符串;

  • Node 端将 render 好的 html 字符串返回给 Browser,同时 Node 端根据 vue-ssr-client-manifest.json 生成的 js 会和 html 字符串 hydrate,完成客户端激活 html,使得页面可交互。


3.2 优化

按照 Vue SSR 官方文档建立起一个服务端渲染的工程后,是否就可以直接上线了呢?别急,我们先看看是否有什么可以优化的地方。

3.2.1 路由和代码分割

一个大的 SPA,主文件 js 往往很大,通过代码分割可以将主文件 js 拆分为一个个单独的路由组件 js 文件,可以很大程度上减小首屏的资源加载体积,其他路由组件可以预加载。


// router.jsconst Index = () => import(/* webpackChunkName: "index" */ './pages/Index.vue');const Detail = () => import(/* webpackChunkName: "detail" */ './pages/Detail.vue');const routes = [    {        path: '/',        component: Index    },    {       path: '/detail',       component: Detail    }];const router = new Router({    mode: 'history',    routes});
复制代码

3.2.2 部分模块(不需要 SSR 的模块)客户端渲染

因为服务端渲染是 CPU 密集型操作,非首屏的模块或者不重要的模块(比如底部的推荐列表)完全可以采用客户端渲染,只有首屏的核心模块采用服务端渲染。这样做的好处是明显的:1. 较大地节省 CPU 资源;2. 减小了服务端渲染直出的 html 字符串长度,能够更快地响应给浏览器,减小白屏时间。


// Index.vueasyncData({ store }) {    return this.methods.dispatch(store); // 核心模块数据预取,服务端渲染}mounted() {    this.initOtherModules(); // 非核心模块,客户端渲染,在mounted生命周期钩子里触发}
复制代码

3.2 3 页面缓存/组件缓存

页面缓存一般适用于状态无关的静态页面,命中缓存直接返回页面;组件缓存一般适用于纯静态组件,也可以一定程度上提升性能。


// page-level cachingconst microCache = LRU({    max: 100,    maxAge: 1000 // 重要提示:条目在 1 秒后过期。})server.get('*', (req, res) => {    const hit = microCache.get(req.url)    if (hit) { // 命中缓存,直接返回页面      return res.end(hit)   }   // 服务端渲染逻辑   ...})
复制代码


// component-level caching// server.jsconst LRU = require('lru-cache')const renderer = createRenderer({    cache: LRU({        max: 10000,        maxAge: ...    })});
// component.jsexport default { name: 'item', // 必填选项 props: ['item'], serverCacheKey: props => props.item.id, render (h) { return h('div', this.item.id) }};
复制代码

3.2.4 页面静态化

如果工程中大部分页面都是状态相关的,所以技术选型采用了服务端渲染,但有部分页面是状态无关的,这个时候用服务端渲染就有点浪费资源了。像这些状态无关的页面,完全可以通过 Nginx Proxy Cache 缓存到 Nginx 服务器,可以避免这些流量打到应用服务器集群,同时也能减少响应的时间。

3.3 降级

进行优化之后,是否就可以上线了呢?这时我们想一想,万一服务端渲染出错了怎么办?万一服务器压力飙升了怎么办(因为服务端渲染是 CPU 密集型操作,很耗 CPU 资源)?为了保证系统的高可用,我们需要设计一些降级方案来避免这些。具体可以采用的降级方案有:


  • 单个流量降级 – 偶发的服务端渲染失败降级为客户端渲染

  • Disconf / Apollo 配置降级 – 分布式配置平台修改配置主动降级,比如可预见性的大流量情况下(双十一),可提前通过配置平台将整个应用集群都降级为客户端渲染

  • CPU 阈值降级 – 物理机 / Docker 实例 CPU 资源占用达到阈值触发降级,避免负载均衡服务器在某些情况下给某台应用服务器导入过多流量,使得单台应用服务器的 CPU 负载过高

  • 旁路系统降级 – 旁路系统跑定时任务监控应用集群状态,集群资源占用达到设定阈值将整个集群降级(或触发集群的自动扩容)

  • 渲染服务集群降级 – 若渲染服务和接口服务是独立的服务,当渲染服务集群宕机,html 的获取逻辑回溯到 Nginx 获取,此时触发客户端渲染,通过 ajax 调用接口服务获取数据


3.4 上线前准备

3.4.1 压测

压测可以分为多个阶段:本地开发阶段、QA 性能测试阶段、线上阶段。


  • 本地开发阶段:当本地的服务端渲染开发完成之后,首先需要用 loadtest 之类的压测工具压下性能如何,同时可以根据压测出来的数据做一些优化,如果有内存泄漏之类的 bug 也可以在这个阶段就能被发现。

  • QA 性能测试阶段:当通过本地开发阶段的压测之后,我们的代码已经是经过性能优化且没有内存泄漏之类严重 bug 的。部署到 QA 性能测试环境之后,通过压真实 QA 环境,和原来的客户端渲染做对比,看 QPS 会下降多少(因为服务端渲染耗更多的 CPU 资源,所以 QPS 对比客户端渲染肯定会有下降)。

  • 线上阶段:QA 性能测试阶段压测过后,若性能指标达到原来的预期,部署到线上环境,同时可以开启一定量的压测,确保服务的可用性。

3.4.2 日志

作为生产环境的应用,肯定不能“裸奔”,必须接入日志平台,将一些报错信息收集起来,以便之后问题的排查。

3.4.3 灰度

如果上线服务端渲染的工程是提供核心服务的应用,应该采用灰度发布的方式,避免全量上线。一般灰度方案可以采用:百分比灰度、白名单灰度、自定义标签灰度。具体采用哪种灰度方式看场景自由选择,每隔一段时间观察灰度集群没有问题,所以渐渐增大灰度比例/覆盖范围,直到全量发布。

3.5 落地

在有赞电商的服务端渲染的落地场景中,我们抽离了单独的依赖包,提供各个能力。


3.6 效果

从最终的上线效果来看,相同功能的页面,服务端渲染的首屏内容时间比客户端渲染提升了 300%+。


3.7 Q & A

Q1:为什么服务端渲染就比客户端渲染快呢?


A:首先我们明确一点,服务端渲染比客户端渲染快的是首屏的内容到达时间(而非首屏可交互时间)。至于为什么会更快,我们可以从两者的 DOM 渲染过程来对比:


客户端渲染:浏览器发送请求 -> CDN / 应用服务器返回空 html 文件 -> 浏览器接收到空 html 文件,加载的 css 和 js 资源 -> 浏览器发送 css 和 js 资源请求 -> CDN / 应用服务器返回 css 和 js 文件 -> 浏览器解析 css 和 js -> js 中发送 ajax 请求到 Node 应用服务器 -> Node 服务器调用底层服务后返回结果 -> 前端拿到结果 setData 触发 vue 组件渲染 -> 组件渲染完成



服务端渲染:浏览器发送请求 -> Node 应用服务器匹配路由 -> 数据预取:Node 服务器调用底层服务拿到 asyncData 存入 store -> Node 端根据 store 生成 html 字符串返回给浏览器 -> 浏览器接收到 html 字符串将其激活:



我们可以很明显地看出,客户端渲染的组件渲染强依赖 js 静态资源的加载以及 ajax 接口的返回时间,而通常一个 page.js 可能会达到几十 KB 甚至更多,很大程度上制约了 DOM 生成的时间。而服务端渲染从用户发出一次页面 url 请求之后,应用服务器返回的 html 字符串就是完备的计算好的,可以交给浏览器直接渲染,使得 DOM 的渲染不再受静态资源和 ajax 的限制。


Q2:服务端渲染有哪些限制?


A:比较常见的限制比如:


  1. 因为渲染过程是在 Node 端,所以没有 DOM 和 BOM 对象,因此不要在常见的 Vue 的 beforeCreate 和 created 生命周期钩子里做涉及 DOM 和 BOM 的操作

  2. 对第三方库的要求比较高,如果想直接在 Node 渲染过程中调用第三方库,那这个库必须支持服务端渲染


Q3:如果我的需求只是生成文案类的静态页面,需要用到服务端渲染吗?


A:像这些和用户状态无关的静态页面,完全可以采用预渲染的方式(具体见 Vue SSR 官方指南),服务端渲染适用的更多场景会是状态相关的(比如用户信息相关),需要经过 CPU 计算才能输出完备的 html 字符串,因此服务端渲染是一个 CPU 密集型的操作。而静态页面完全不需要涉及任何复杂计算,通过预渲染更快且更节省 CPU 资源。


本文转载自公众号有赞 coder(ID:youzan_coder)


原文链接


https://mp.weixin.qq.com/s?__biz=MzAxOTY5MDMxNA==&mid=2455760048&idx=1&sn=141917823352a1566799784f67d6b58c&chksm=8c686a95bb1fe383115f8e8207eeff32d7ccb72ff409ecbc7492b894326915579e8fa763a21b&scene=27#wechat_redirect


活动推荐:

2023年9月3-5日,「QCon全球软件开发大会·北京站」 将在北京•富力万丽酒店举办。此次大会以「启航·AIGC软件工程变革」为主题,策划了大前端融合提效、大模型应用落地、面向 AI 的存储、AIGC 浪潮下的研发效能提升、LLMOps、异构算力、微服务架构治理、业务安全技术、构建未来软件的编程语言、FinOps 等近30个精彩专题。咨询购票可联系票务经理 18514549229(微信同手机号)。

2019-10-09 08:002705

评论

发布
暂无评论
发现更多内容

Android实现无序树形结构图,类似思维导图和级联分层图(无序,随机位置)

芝麻粒儿

android 7月月更

【萌新解题】三数之和

面试官问

面试 LeetCode

为什么加工数据指标

奔向架构师

数据仓库 7月月更

mysql数据表查询

乌龟哥哥

7月月更

关于目前流行的 Redis 可视化管理工具的详细评测

宁在春

redis 7月月更 Redis 可视化工具

Block 的分类

NewBoy

ios 前端 移动端 iOS 知识体系 7月月更

SDL图像显示

柒号华仔

7月月更

CodeTON Round 1 (Div. 1 + Div. 2, Rated, Prizes)(A-C)

KEY.L

7月月更

通过Dao投票STI的销毁,SeekTiger真正做到由社区驱动

股市老人

LeetCode 242:有效的字母异位词

武师叔

7月月更

带领全网朋友,完成粉笔登录加密分析,再次换种玩法

梦想橡皮擦

Python 爬虫 7月月更

【Docker 那些事儿】容器网络(上篇)

Albert Edison

Docker Kubernetes 容器 云原生 7月月更

Android ANR和OOM

沃德

android 程序员 7月月更

JavaScript DOM编程艺术笔记

程序员海军

前端 DOM 7月月更

通过Dao投票STI的销毁,SeekTiger真正做到由社区驱动

威廉META

通过Dao投票STI的销毁,SeekTiger真正做到由社区驱动

鳄鱼视界

数据库的主从分离

ES_her0

7月月更

zookeeper-认识watcher

zarmnosaj

7月月更

常见链表题及其 Go 实现

宇宙之一粟

链表 7月月更

软核微处理器

贾献华

7月月更

队列的链式表示和实现

秋名山码民

算法 7月月更

初学者如何快速的上手Linux命令,这34条新手必会的命令一定得会!

wljslmz

Linux 7月月更

分库分表

ES_her0

7月月更

面试官:工作两年了,这么简单的算法题你都不会?

掘金安东尼

程序员 面试 算法 前端 7月月更

深度学习-多维数据和tensor

AIWeker

7月月更 多维数据

解读《深入理解计算机系统(CSAPP)》第12章并发编程

小明Java问道之路

Java 后端 并发 csapp 7月月更

还在为处理事务烦恼吗,要不试试Spring是如何处理业务的

Java学术趴

7月月更

Android热更新调研汇总

沃德

android 程序员 7月月更

值得收藏的ArkUI框架三方组件【系列1】

坚果

HarmonyOS Open Harmony 7月月更

jQuery 操作元素

Jason199

jquery js 7月月更

函数初认识-上

芒果酱

C语言 7月月更

  • 扫码添加小助手
    领取最新资料包
如何搭建一个高可用的服务端渲染工程_语言 & 开发_墨影_InfoQ精选文章