Nuxt ssr如何做性能优化

Nuxt的背景

Nuxt.js 是一个基于 Vue.js 的通用应用框架。

通过对客户端/服务端基础架构的抽象组织,Nuxt.js 主要关注的是应用的 UI 渲染。

我们的目标是创建一个灵活的应用框架,你可以基于它初始化新项目的基础结构代码,或者在已有 Node.js 项目中使用 Nuxt.js。

Nuxt.js 预设了利用 Vue.js 开发服务端渲染的应用所需要的各种配置。

除此之外,我们还提供了一种命令叫:nuxt generate ,为基于 Vue.js 的应用提供生成对应的静态站点的功能。

我们相信这个命令所提供的功能,是向开发集成各种微服务(Microservices)的 Web 应用迈开的新一步。

作为框架,Nuxt.js 为 客户端/服务端 这种典型的应用架构模式提供了许多有用的特性,例如异步数据加载、中间件支持、布局支持等。

Nuxt 特性

  • 基于 Vue.js
  • 自动代码分层
  • 服务端渲染
  • 强大的路由功能,支持异步数据
  • 静态文件服务
  • ES2015+ 语法支持
  • 打包和压缩 JS 和 CSS
  • HTML 头部标签管理
  • 本地开发支持热加载
  • 集成 ESLint
  • 支持各种样式预处理器: SASS、LESS、 Stylus 等等
  • 支持 HTTP/2 推送

Lifecycle

https://www.nuxtjs.cn/nuxt-schema.svg

Nuxt 直出原理

nuxt直出本质,其实还是vue在服务端的模板渲染,依然会涉及到 vue-component -> ast -> render(vdom)。对 compile 熟悉的同学知道,在 ast 的 parse 阶段,会做大量的正则匹配,属于CPU密集操作。并且为了隔离请求不同的请求,它会为每一个请求创建一个上下文 context ,这样一来,vue ssr 和传统的模板引擎相比,其性能相差甚远。

那如何优化?

SSR的优化策略一:缓存

  • 缓存
    • 组件级别缓存
    • 数据级别缓存
    • 页面级别缓存

组件级别缓存

nuxt的组件级别缓存,使用 Component Cache module。使用的缓存策略还是LRU Cache

{
  modules: [
    '@nuxtjs/component-cache',
    [
      '@nuxtjs/component-cache',
      {
        max: 10000,
        maxAge: 1000 * 60 * 60
      }
    ]
  ]
}

在需要缓存的组件中使用 serverCacheKey 函数来标识

export default {
  name: 'ReplyItem',
  serverCacheKey: props => props.postId,
  props: {
    postId: String
  }
}

数据级别缓存

对那些不涉及用户信息和实时性要求不高的接口进行缓存。对于数据层的缓存,依然可以使用LRU Cache

页面缓存

非千人前面的页面,可以对整个页面进行缓存。可以在 middleware 里处理

middleware/pageCache.js

const LRU = require('lru-cache')

const options = {
  max: 1000,
  maxAge: 1000 * 60 * 5
}

// 需要进行页面级别缓存的路由
const CACHE_URL = ['/xxx', '/xxx', '/xxx']

const cache = new LRU(options)

export default function (req, res, next) {
  const url = req._parsedOriginalUrl
  const pathname = !!url.pathname ? url.pathname : ''

  if (CACHE_URL.includes(pathname)) {
    const existsHtml = cache.get(pathname)
    if (existsHtml) {
      // 不要忘了设置 Content-Type 不然浏览器有时候可能不会渲染而是触发下载文件
      res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' })
      return res.end(existsHtml.html, 'utf-8')
    } else {
      res.original_end = res.end
      res.end = function (data) {
        if (res.statusCode === 200) {
          cache.set(pathname, { html: data })
        }
        res.original_end(data, 'utf-8')
      }
    }
  }
  next()
}

之后加到nuxt.config.js 的中间件中即可

SSR的优化策略二:业务后移

其实就是最小屏优化,实际就是讲SEO权重底的模块后移到client端处理。参考 movie.xunlei.com 的实践。

但影评在做最小屏优化后依然存在问题,那就是大量promise.all以及promise依赖实例化后的大量请求,缺少并发控制。导致tcp连接数到了上限,堆积了无数的调用栈引起内存泄漏。(通过 async-pool 控制并发就可以处理)

SSR的优化策略三:区分登录态

在通过 robots.txt 以及 sitemap 等等SEO优化手段后,搜索引擎爬虫同时也会提高服务端渲染的压力。但是搜索引擎爬虫不会存在登录态,对非登录态的用户可以采用直出的服务,带登录态的用户可以提供客户端渲染的方式。在保障SEO的同时依然可以减少服务端渲染压力。(参考juejin的实现)

SSR的优化策略四:负载和自动扩缩容

nginx 集群

通过 nginx 负载处理,适合的架构模型: nginx -> N * ECS -> N * instance Node(PM2)

PM2的负载均衡

PM2自身的cluster模式,这个不做赘述

容器化集群(k8s)

https://feisky.gitbooks.io/kubernetes/content/architecture/images/architecture.png

  • 在SVC侧,kube-proxy为SVC提供cluster内部的服务发现和负载均衡
  • hpa 自动缩扩容

其他

  • LRU Cache 虽好,但有缺陷。