关于ssr:vivo-商城架构升级SSR-实战篇

26次阅读

共计 5382 个字符,预计需要花费 14 分钟才能阅读完成。

一、前言

在后面几篇文章中,置信大家对 vivo 官网商城的前端架构演变有了肯定的理解,从稳步推动前后端拆散到小程序多端摸索实际,团队不断创新尝试。

在本文中,咱们来分享一下 vivo 官网商城在 Node 服务端渲染(Server Side Rendering, SSR)方面的实战经验。本文次要围绕以下几个方面进行论述:

  • CSR 与 SSR 的比照
  • 性能优化
  • 自动化部署
  • 容灾、降级
  • 日志、监控

二、背景

vivo 官网商城目前前后端拆散采纳的是 SPA 单页模式,SPA 会把所有 JS 整体打包,无奈漠视的问题就是文件太大,导致渲染前期待很长时间。特地是网速差的时候,让用户期待白屏完结并非一个很好的体验。因而 vivo 官网商城前端团队尝试引入了 SSR 技术,以此来放慢页面首屏的访问速度,从而晋升用户体验。

三、SSR 简介

3.1 什么是 SSR?

页面渲染次要分为客户端渲染 (Client Side Render) 和服务端渲染(Server Side Rendering):

  • 客户端渲染(CSR)

服务端只返回一个根本的 html 模板,浏览器依据 html 内容去加载 js,获取数据,渲染出页面内容;

  • 服务端渲染(SSR)

页面的内容是在服务端渲染实现,返回到浏览器间接展现。

3.2 为什么要应用 SSR?

与传统 SPA (单页应用程序 (Single-Page Application)) 相比,SSR 的劣势次要在于:

  • 更好的搜索引擎优化(SEO),SPA 应用程序初始展现 loading 菊花图,而后通过 Ajax 获取内容,搜索引擎并不会期待异步实现后再行抓取页面内容;
  • 更快的内容达到工夫 (time-to-content),特地是对于迟缓的网络状况或运行迟缓的设施,无需期待所有的 JavaScript 都实现下载并执行,才显示服务器渲染的标记,用户可能更疾速地看到残缺渲染的页面,晋升用户体验。下图可能更直观的反馈加载时成果。

CSR 和 SSR 页面渲染比照:

四、SSR 实际

vivo 官网商城我的项目的技术栈是 Vue, 思考到从头搭建一套服务端渲染的利用比较复杂,所以抉择了 Vue 官网举荐的 Nuxt.js 框架,这是基于 Vue 生态的更高层的框架,为开发服务端渲染的 Vue 利用提供了极其便当的开发体验。

这里不做根底应用的分享,有趣味的同学能够到 Nuxt.js 官网学习根底用法;咱们次要聚焦于在整个实际过程中,次要遇到的一些挑战:

  • 性能:如何进行性能优化,晋升 QPS,节约服务器资源?
  • 容灾:如何做好容灾解决,实现主动降级?
  • 日志:如何接入日志,不便问题定位?
  • 监控:如何对 Node 服务进行监控?
  • 部署:如何买通公司 CI/CD 流程,实现自动化部署?

4.1 性能优化

尽管 Vue SSR 渲染速度曾经很快,然而因为创立组件实例和虚构 DOM 节点的开销,与基于字符串拼接的模板引擎的性能相差很大,在高并发状况下,服务器响应会变慢,极大的影响用户体验,因而必须进行性能优化。

4.1.1 计划 1 启用缓存

a、页面缓存: 在创立 render 实例时利用 LRU-Cache 来缓存渲染好的 html,当再有申请拜访该页面时,间接将缓存中的 html 字符串返回。

nuxt.config.js 减少配置:

serverMiddleware: ["~/serverMiddleware/pageCache.js"]

根目录创立 serverMiddleware/pageCache.js

b、组件缓存: 将渲染后的组件 DOM 存入缓存,定时刷新,有效期内取缓存中 DOM。次要实用于重复使用的组件,多用于列表,例如商品列表。

配置文件 nuxt.config.js:

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: LRU({
        max: 1000,                   // 最大的缓存个数
        maxAge: 1000 * 60 * 5        // 缓存 5 分钟
      })
    }
  }
}

缓存组件减少 name 及 serverCacheKey 作为惟一键值:

export default { 
    name: 'productList', 
    props: ['productId'], 
    serverCacheKey: props => props.productId
}

c、API 缓存: Node 服务器须要先调用后盾接口,获取到数据,而后能力进行渲染,获取接口速度的快慢,间接影响到渲染的工夫, 对接口的缓存能够放慢每个申请的处理速度,更快地开释掉申请,从而进步性能。API 缓存次要实用于数据根本放弃不变,变更不是很频繁,与用户集体数据无关的接口。

4.1.2 计划 2 接口并发申请

同一个页面,在 Node 层可能会同时调用多个接口,如果是串行调用,须要期待的工夫会比拟长,如果是并发申请,会放大等待时间。

例如:

let data1 = await $axios.get('接口 1')
let data2 = await $axios.get('接口 2')
let data3 = await $axios.get('接口 3')

能够改成:

let {data1,data2,data3} = await Promise.all([$axios.get('接口 1'),$axios.get('接口 2'),$axios.get('接口 3')
])

4.1.3 计划 3 首屏最小化

影响用户体验次要是首屏的白屏工夫,而第二屏、第三屏 …,并不需要立刻显示。以商品详情页为例,如下图:

能够对页面构造进行拆分,首屏元素采纳 SSR,非首屏元素通过 CSR;SSR 数据须要通过 asyncData 办法来获取,CSR 数据能够在 mounted 中获取。

CSR 写法如下:

<client-only>
    客户端渲染 dom
</client-only>

4.1.4 计划 4 局部页面采纳 CSR

并不是所有页面对体验、SEO 要求都很高,像商城这样的业务,能够只对首页、商品详情页等外围页面做 SSR,这样能够大大减少服务端的压力。

4.1.5 优化前后性能压测比照

优化前:

优化后:

从上图能够看出,未经优化前 QPS 只有 125,通过一系列优化 QPS 达到了 6000,晋升了靠近 50 倍。

这里的降级是指将 SSR 降级为 CSR,应用 Node 做 SSR,瓶颈在于 CPU 和内存,在高并发状况下,很容易导致 CPU 飙升,用户拜访页面工夫变长, 如果 Node 服务器挂了,间接会导致页面拜访不了。所以为了保障我的项目上线之后安稳运行,须要提供容灾、降级计划。

Nuxt.js 能够同时反对 CSR 和 SSR,咱们在打包时,既生成 SSR 的包,同时生成 CSR 的包,别离进行部署。

我的项目中采纳了以下几种降级计划:

4.2 降级策略

4.2.1 监控零碎降级

Node 服务器上启动一个服务,用来监测 Node 过程的 CPU 和内存使用率,设定一个阈值,当达到这个阈值时,进行 SSR,间接将 CSR 的入口文件 index.html 返回,实现降级。

4.2.2 Nginx 降级策略

4.2.2.1全平台降级

例如 618,双 11 等大促期间,咱们当时晓得流量会很大,能够提前通过批改 Nginx 配置,将申请转发到动态服务器,返回 index.html,切换到 CSR。

4.2.2.2单次访问降级

当偶发性的 Node 服务器返回 5xx 错误码,或者 Node 服务器间接挂了,咱们能够通过如下 Nginx 配置,做到主动切换到 CSR,保障用户能失常拜访。

Nginx 配置如下:

  location / {
      proxy_pass Node 服务器地址;
      proxy_intercept_errors on;
      error_page 408 500 501 502 503 504 =200 @spa_page;  
  }

  location @spa_page {
      rewrite ^/*  /spa/200.html break;
      proxy_pass  动态服务器;
  }

4.2.2.3指定渲染形式

在 url 中减少参数 isCsr=true,Nginx 层对参数 isCsr 进行拦挡,如果带上该参数,指向 CSR,否则指向 SSR;这样就能够通过 url 参数配置来进行页面分流,加重 Node 服务器压力。

4.3 CI/CD 自动化部署

基于公司的 CI/CD,咱们实现了 Docker 部署和 Shell 脚本部署两种自动化部署计划。

4.3.1 计划 1 Shell 脚本构建、部署

对于 Shell 脚本的形式,咱们次要解决的问题是如何通过脚本来装置指定 Node 的版本,这里咱们能够分为两步:

1、装置 nvm, nvm 是 Node.js 的版本管理器(version manager)
2、通过 nvm 装置或者切换成对于的 Node 版本
# 定义装置 nvm 的办法
install_nvm() {
  echo "env $app_env install nvm ..."
  wget --header='Authorization:Basic dml2b2Rldm9wczp4TFFidmtMbW9ZKn4x' -nv -P .nvm http://xxx/download/nvm-master.zip

  unzip -qo .nvm/nvm-master.zip
  mv nvm-master/* $NVM_DIR
  rm -rf .nvm
  rm -rf nvm-master

  . "$NVM_DIR/nvm.sh"
  if [[$? = 1]];
  then
    echo "install nvm fail"
  else
    echo "install nvm success"
  fi
}
# 定义装置 Node 的办法
install_node() {
   # command_args 为用户自定义的 Node 版本号
  local USE_NODEVER=$command_args

  echo "will install NodeJs $USE_NODEVER"

  nvm install $USE_NODEVER >/dev/null

  echo "success change to NodeJs version" $(node -v)
}
# Node 环境装置
prepare() {if [[ -s "$NVM_DIR/nvm.sh"]];
  then
    . "$NVM_DIR/nvm.sh"
  else
    install_nvm
  fi
  echo "nvm version $(nvm --version)"

  install_node
}

4.3.2 计划 2 Docker 构建、部署

Docker 是一个开源的利用容器引擎,让开发者能够打包他们的利用以及依赖包到一个可移植的镜像中,而后公布到任何风行的 Linux 或 Windows 机器上,也能够实现虚拟化。容器是齐全应用沙箱机制,相互之间不会有任何接口。

# 根底镜像
FROM node:12.16.0

# 创立文件寄存目录
RUN mkdir -p /home/docker-demo
WORKDIR /home/docker-demo
COPY . /home/docker-demo

# 装置依赖
RUN yarn install

# 打包,并把动态资源进行 md5 压缩
RUN yarn prod

# 动态资源部署 CDN
RUN yarn deploy

# 端口号
EXPOSE 3000

# 我的项目启动命令
CMD npm start

相比较而言,Docker 部署具备很大劣势:

  • 构建、部署更加不便
  • 统一的运行环境「这段代码在我机器上没问题啊」
  • 弹性伸缩
  • 更高效的利用系统资源
  • 快 \- 治理操作(启动,进行,开始,重启等)都是以秒或毫秒为单位

4.4 监控、告警

监控是整个产品生命周期中十分重要的一环,事先及时预警发现故障,预先提供详实的数据用于追究定位问题。
在利用呈现故障时,须要有适合的工具链来撑持问题的定位修复,咱们引入了开源的企业级 Node.js 利用性能监控与线上故障定位解决方案 Easy-Monitor,能够更好地监控 Node.js 利用状态,来面对性能和稳定性方面的挑战。咱们在内网部署了这套零碎,并进行了二次开发,集成了内网域登录,并能够通过外部聊天工具推送告警信息。

 

4.5 日志

利用上线后,一旦产生异样,第一件事件就是要弄清过后产生了什么,比方用户过后如何操作、数据如何响应等,此时日志信息就给咱们提供了第一手材料。因而咱们须要接入公司的日志零碎。

4.5.1 实现

日志组件基于 log4js 封装,对接公司的日志核心

在 nuxt.config.js 中减少:

export default {
  // ...
  modules: ["@vivo/nuxt-vivo-logger"],
  vivoLog: {
      logPath:process.env.NODE_ENV === "dev"?"./logs":"/data/logs/",
      logName:'aa.log'
  }
}

4.5.2 应用

async asyncData({$axios,$vivoLog}) {
    try {const resData =  await $axios.$get('/api/aaa')
      if (process.server) $vivoLog.info(resData)
    } catch (e) {if (process.server) $vivoLog.error(e)
    }
},

4.5.3 后果

五、写在结尾

用户体验的晋升是一个永恒的话题,vivo 官网商城前端团队始终致力于技术的不断创新,心愿能通过技术的摸索给用户带来更好的体验;以上是 vivo 官网商城前端团队在 SSR 技术方面实际的一些教训,分享进去心愿能和大家一起学习、探讨。

  • vivo 商城前端架构降级—多端对立摸索、实际与瞻望篇
  • vivo 商城前端架构降级—前后端拆散篇
  • vivo 商城前端架构降级 - 总览篇
  • vivo 寰球商城:从 0 到 1 代销业务的交融之路
  • vivo 寰球商城:架构演进之路

作者:vivo 官网商城前端团队

正文完
 0