写点什么

Vue SSR 在好大夫的落地

发布于: 2021 年 05 月 07 日
Vue SSR在好大夫的落地

​介绍


对于使用过 vue 的前端开发者来说,对 vue-ssr 应该有着或多或少的了解。那让我们简单了解一下什么是 vue-ssr?以及它的优劣势?


什么是 vue-ssr?

ssr 是 Server-Side Rendering 的缩写,即由服务端负责页面的渲染和直接输出。在服务端将 vue 组件解析渲染成 html 字符串并发送到浏览器端,在浏览器端进行渲染激活并进行相关 dom 操作。由于应用程序的大部分代码都可以在服务器客户端上运行,故也可以被认为是"同构"或"通用"。


优势

1. 更好的 SEO,服务端返回的页面是带有页面信息的 html 文档,这对于搜索引擎的爬虫是非常友好的。

2. 更快的内容到达时间,特别是对于缓慢的网络情况或运行缓慢的设备,无需等待所有的 JavaScript 都完成下载并执行后才能看到页面完整的内容。

3. 跨端同构,对于我们编写的 Vuejs 的应用程序大部分代码都是可以在服务器客户端上运行。


劣势

1. 开发条件所限,在开发 ssr 项目时,你需要知道你的代码哪些是在服务端运行,哪些是在客户端运行,因为 window、document 等这些就不能出现在初始化代码和服务的一些钩子函数中,global、process 等就不能出现客户端的钩子函数中。这就需要我们写出更加通用的代码来兼容两端;并且一些外部扩展库 (external library) 可能需要特殊处理,才能在服务器渲染应用程序中运行。

2. 更多的服务器端负载,在 Node.js 中渲染完整的应用程序,显然会比仅仅提供静态文件的 server 更加大量占用 CPU 资源 (CPU-intensive - CPU 密集),因此如果你预料在高流量环境 (high traffic) 下使用,请准备相应的服务器负载,并明智地采用缓存策略。


背景

针对于上面 vue ssr 的介绍,那么它主要解决了我们什么样的问题呢?好大夫在线为什么需要落地 Vue-SSR 这样一种技术?它给我们带来了什么样的好处呢?以及它又会带来什么问题和挑战呢?

带着这些问题,我们来看一下目前好大夫前端技术栈的一个现状。


现状

一、express+handlebars

从好大夫引入 node 的时候,采用的就是 express+handlebars,主要是获取 service 端数据,处理业务逻辑,解析视图模板,将解析后的 html 字符串发送到浏览器端。

那么有人会问,这不是典型的服务端渲染吗(SSR)?它不是已经很好地解决了 SEO 和更快的内容到达吗?没错,现有的模式的确是最典型的服务端渲染,并且在 SEO 和更快的内容到达。那么我们是不是就没有必要使用 Vue SSR 了呢?其实不然。目前的开发模式主要有以下几个问题:


1. 频繁的 dom 操作、复杂业务状态管理等,都会使得开发变得困难许多,dom 操作仿佛回到了刀耕火种的时代。那有的人就说了,我在页面引入 vue 来解决这类问题不是好了,但是对于首次需要加载的数据又如何去获取填充?那又有人说了,我们首次需要的数据我们服务端渲染,对于页面交互和异步的数据,我们通过 vue 去绑定和获取。那接下来的问题来了,对于首次获取的数据,如何进行响应式的绑定呢?那还有人说我们可以通过 vue  $mount 来强制使用激活。这种是一种方式,但是这会导致 vue 丢弃现有的 DOM 并从头开始渲染,造成重复渲染的问题。并且此种方式存在维护多套模板的情况(vue template & handlebars template),也就是无法达到代码同构从而提升开发成本。

app.$mount('#app', true)
复制代码


2.对有 SEO 和更快内容到达需求的情况下很难融合现有的前端技术栈(vue 技术栈、react 技术栈等)。

3. 对于业务模块、抽象组件客户端和服务端无法同构。  

4. 相对于前端 vue 或是 react,开发效率低。


二、express+vue spa

对于那些没有 SEO 需求,以及对于内容到达时间要求不高,但是有着复杂的交互和状态的流程性、功能性等页面,我们通常采用的是 vue spa 的模式进行开发。express 相当只是提供了静态页面服务,对于页面的数据通过异步接口获取填充后再渲染。其弊端我们也很清楚,对于 SEO 和更快内容到达的页面是不友好的。

针对于目前使用的技术现状,我们想解决在使用 vue 情况下,也能满足 SEO 需求和更快的内容到达,并且实现前后端视图层代码的通用性。于是我们选择了 Vue SSR。


vue ssr 落地

在一项技术的落地的过程中,肯定少不了技术选型,前期 vue ssr 方案调研中我们有对比过 nuxt.js,发现 nuxt 在我们现有的 node 项目中没办法很好的嵌入以及自定义扩展和维护的成本较高,于是我们选择了 vue 官方的方案。

现有的 node 架构流程如下:

对于页面的请求,到达 node 层,经过各种中间件处理后,会传递到路由层,路由层通过匹配的路由规则传递到 controller 层,controller 对于业务数据的获取和业务逻辑的处理,最后拿到获取的数据渲染指定页面的模板发送给客户端。对于现有的 node 架构,我们如何把 vue ssr 应用到我们现有的 node 工程中去呢?


我们提出了两种解决方案:

1. 借鉴 nuxt,我们可以单独的把 vue server renderer 当做一个 node 服务,对于页面的数据我们可以通过 axios 进行跨平台调用。此 node 服务主要服务解析路由规则,调用 asyncData,通过 axios 获取服务端数据,渲染页面数据,响应页面请求。此方案职责清晰,功能单一,容易理解。但是介于好大夫技术场景的原因,此方案并不是我们实际使用的。主要原因如下:

  • 对于 service 数据的获取问题。我们 node 是通过调用 Java、PHP 获取业务数据,但是 Java 和 PHP 的接口并不是对外暴露的,导致我们如果是在客户端渲染就会报错。

  • 增加 node 中间层 node api 层,首先对于一个业务维护两个 node 工程,维护成本增加,并且目前好大夫 node 层不对内提供服务,还有就是涉及请求链路变长,请求验证信息透传等问题。

  • 对于现有的 node 开发方式也不兼容

由于方案一存在的一些问题,我们提出了第二种能很好地结合我们现有的 node 架构的方案。


2. 在不脱离我们现有 node 架构的前提下,我们做了如下设计:

在不影响现有的 node 架构模式的前提下,我们对于新增的使用 vue ssr 做服务端页面,添加了统一的处理。在服务端渲染时我们通过 @hnpm/h-ssr-action match router 规则,调用 asyncData 方法,获取页面数据,进行服务端数据渲染,响应页面请求。对于降级的情况下我们 @hnpm/h-ssr-action 不会获取页面数据,而是直接输出无数据的模板页,在客户端去异步获取 service 数据。

const ssrAction = require('@hnpm/h-ssr-action');...const ssrPageRouterArr = ['/index/search',/\/*.php/,'/nsearch/*',...];router.get(ssrPageRouterArr, [...middleware], ssrAction(app));...
复制代码

@hnpm/h-ssr-action  在渲染之前解析 cdn 资源、插入页面监控代码、统一处理 error handle 和 404 页面、添加自定义请求头标识等。

这就很好的解决了我们上面面临的问题,我们 node 即提供了 renderer 能力也提供 api 接口的能力。

那么有人会问了,你是如何实现 node 自身调用的问题的?对于 node 在调用 asyncData 的问题上,我们是进行端的标识的,对于 service 端和 client 端来的请求,我们设置了自定义的请求头,如下:

// serverreq.headers['x-hdf-ssr'] = 'server';
// clientaxios.create({ ... headers: { 'x-hdf-ssr': 'client' }});
复制代码

其次我们封装了统一的请求类库 @hnpm/h-ssr-fetch。@hnpm/h-ssr-fetch  主要进行端的区分,我们对于服务端渲染的请求直接 require action 由 action 直接返回数据(通过 match 当前访问路由规则找到对应 action 文件,避免 node 自己调用自己 api 和 cookie 透传等问题),而对于客户端来的请求我们会通过 axios 发起请求走正常的 http 请求(并且对该请求做了统一的处理)。这样的话我的 baseController 层添加统一处理不同端的响应方式。


与此同时我们还封装了 @hnpm/h-ssr-server 实现客户端和服务器端路由逻辑 ,以及处理一些加载指示器、服务端降级处理、路由守卫全局配置等。


此后,我们还将 vue ssr 集成到内部的开发工具 skit 中(vue ssr cli (skit ssr)  ),实现了:

  • skit ssr -i 初始化 vue skit 工程

  • skit ssr -b 构建 vue ssr 工程

  • @hnpm/setup-dev-server  开发环境实现热更新


自动化构建方面我们也将打包 skit ssr 工程作为构建的一个环节,实现开发测试上线的一个闭环流程。


降级处理

由于 vue ssr 很大的一个缺点是更多的服务器端负载。所以在上线前我们做了一次压力测试,在 8 核 8 进程下渲染 1000 条静态数据的 qps 量大概是 2200-2300 左右。如下图:

承载现有业务的 qps 量,我们 node 服务集群完全没有问题的。但是万一出现服务器压力飙升怎么办?(因为服务端渲染是 CPU 密集型操作,很耗 CPU 资源)为了保证系统的可用和稳定性,我们设计了必要的降级方案。如下:

  • Apollo 配置渲染方式,在可预见的大流量或是系统大流量告警的情况下,及时通过平台配置使得整个 node 集群降级到客户端渲染。

  • cpu 阀值降级,当单台 node 机器中 cpu 资源占用触及阀值时主动降级。避免单台机器流量过高而导致负载过高。

除此之外,对于单个流量也存在降级的情况,也就是对于单个请求出现服务端渲染失败,也会降级到客户端渲染,保证页面的可用性。


遗留的问题

1. 目前在页面模板渲染数据时我们都是通过 vuex 获取填充,这就需要我们每次都会去从 vuex mapstate 数据,对于数据状态比较单一并且数据只是用来展示的情况下,通过从 vuex 获取数据显得有点繁琐。

2. 在 qps 压测时,我们发现相对于 handlebars 的 vue ssr 渲染的效率较低,在单一的静态数据渲染能力上 handlebars 的渲染能力要比 vue ssr 效果高出 20-30%。


未来规划

1. 参考 nuxt 把 asyncData 获取的数据的数据同步到页面组件的 data 中,方便开发时获取页面的渲染数据。

2. 添加有效的缓存策略保证 node 服务响应请求的能力,深入对比 handlebars 和 vue ssr 页面渲染机制,提高 vue ssr 渲染的效率。


------- END -------


【作者简介】

梁伟:好大夫在线前端架构师,专注于前端系统设计、流程优化等前端基础建设。

好大夫在线创立于 2006 年,是中国领先的互联网医疗平台之一。已收录国内 10496 家正规医院的 69.2 万名医生信息。其中,23 万名医生在平台上实名注册,直接向患者提供线上医疗服务。“让行医简单,看病不难” ,始终追求“成为值得信赖的医疗平台”。

发布于: 2021 年 05 月 07 日阅读数: 774
用户头像

科技创造优质医疗 2008.07.29 加入

好大夫在线创立于2006年,是领先的互联网医疗平台之一。已收录国内10496家正规医院的69.2万名医生信息。其中23万名医生已实名注册提供线上医疗服务。“让行医简单,看病不难” 始终追求“成为值得信赖的医疗平台”。

评论

发布
暂无评论
Vue SSR在好大夫的落地