2017-03-05 21:39:10

vue2服务端渲染:直连数据库的首屏优化策略

vue2的服务端渲染,在数据获取方面仍然是依靠同构型Ajax库发起HTTP请求,即使API服务器与SSR在同一台机器,HTTP的开销也足有20毫秒以上。

本文介绍一种直连数据库的首屏优化策略,效果十分显著,单请求平均降低了20毫秒,优化前后的截图如下:

左边为优化前,右边为优化后

上图片中的data pre-fetch是纯数据获取的耗时,通过对比可以发现,单请求平均降低了20毫秒这一说法甚至稍微有点谦虚。

另外,图片中的whole request是整个服务端渲染的耗时

这种优化策略已在本博客上线

优点

  • 效果显著
    • 提速20毫秒已经非常逆天,本博客在内网的TTFB如上图右边所示最多只有30毫秒左右,首屏访问时间已经稳稳地停留在一百多毫秒了
  • 不需要更换同构型Ajax库
    • 采用这种策略后,Ajax库只会在客户端跑起来,服务端并不需要

缺点

  • SSR服务器必须能够连接到API的数据库
    • 像firebase之类API服务器的就不行了
  • API请求的URL格式必须有一定规则
    • 否则SSR服务端无法推算出怎样查询数据库
  • 需要耦合部分后端逻辑
    • 如评论中讨论,这种办法适合RESTful或GraphQL这种查询逻辑基本由前端提供的系统

优化流程

流程代码都在这个commit里,有过SSR经验的同学可以直接进去看

改造API入口

本博客采用的是axios库,现在需要将axios变为一个对象的成员,否则webpack打包仍然会在服务端打包中打包axios

// store/api.js
import api from 'create-api'

const prefix = `${api.host}/api`

const store = {}

export default store

store.fetch = (model, query) => {
  const target = `${prefix}/${model}`
  return api.axios.get(target, { params: query }).then((response) => {
    const result = response.data
    return result
  })
}

create-api则使用webpack的alias,在服务端打包中将其重定向为store/create-api-server.js,打包如下:

  resolve: {
    alias: Object.assign({}, base.resolve.alias, {
      'create-api': './create-api-server.js'
    }),
    extensions: base.resolve.extensions
  },

store/create-api-server.js入口文件改造如下:

export default {
  host: '', // 由于直连数据库,因此域名已经没有意义了
  axios: process.__API__
}

这里使用process.__API__这一node的变量来mock axios

之所以需要用一个全局变量,是因为vue的服务端渲染采用的是纯vm来加载模块,不使用全局变量则会导致每个请求都会初始化一次mock的操作,造成浪费。

根据URL推算如何查询数据库,进行mock

以本博客为例,博主只调用了axios的get方法,因此只需要在服务端mock axios.get即可。

// 初始化mongo数据库及schema定义
const models = require('./mongo')

// mock axios.get方法
process.__API__ = {
  get: function (target, { params: query }) {
  // 处理请求URL,获得应当查询的schema表名
  // 这里的处理规则需要自己在前端的URL请求链接中做约定
    const modelName = target.split('/').slice(-1)
  // 根据表名获取对应model
    const model = models[modelName]
  // 进行查询并返回Promise,因为axios.get本身就返回Promise
    return new Promise((resolve, reject) => {
      queryModel(model, query).then(data => {
        resolve({ data })
      }) // mongoose使用mpromise,它在这里并没有reject方法
    })
  }
}

// 此方法为后端RESTful服务器进行get查询的方法,完全从后端导入
function queryModel (model, query) {
  // ...
}

然后在SSR服务端中require这个文件

直连数据库

这一步只是简单的连接数据库,并定义schema,供给上一步获取model

由于篇幅有限,下面代码中只包含了一个schema

const config = {
  mongoHost: '127.0.0.1',
  mongoDatabase: 'blog',
  mongoPort: 27017
}
const mongoose = require('mongoose')
const log = require('log4js').getLogger('ssr axios')

mongoose.Promise = require('bluebird')

const mongoUrl = `${config.mongoHost}:${config.mongoPort}/${config.mongoDatabase}`

mongoose.connect(mongoUrl)

const db = mongoose.connection

db.on('error', (err) => {
  log.error('connect error:', err)
})

db.once('open', () => {
  log.debug('MongoDB is ready')
})

const Schema = mongoose.Schema

let post = new Schema({
  type: { type: String, default: '' },
  status: { type: Number, default: 0 },
  title: String,
  pathName: { type: String, default: '' },
  summary: { type: String },
  markdownContent: { type: String },
  content: { type: String },
  markdownToc: { type: String, default: '' },
  toc: { type: String, default: '' },
  allowComment: { type: Boolean, default: true },
  createdAt: { type: String, default: '' },
  updatedAt: { type: String, default: '' },
  isPublic: { type: Boolean, default: true },
  commentNum: Number,
  options: Object,
  category: String,
  tags: Array
})

post = mongoose.model('post', post)

module.exports = {
  post
}

可以看到,整个流程其实相当简单,只有按照规则解析前端发出的请求这一步需要自行处理,其他的逻辑可以从后端API提供方直接拷贝。

结语

这种优化策略并不局限于vue2的服务端渲染,它对react与angular同样有效。

它完全去除了SSR仍然发出HTTP请求进行数据获取的劣势,优化之后,SSR与MVC框架直出相比,影响性能的因素将只有得到HTML的途径这一不同点,即前者的虚拟DOM,以及后者的字符串拼接。

  • SSR的虚拟DOM性能较差,但是可以允许前端进行MVVM形式的自由开发
  • MVC的字符串拼接性能较好,但是前端与后端耦合,开发体验非常差

虚拟DOM虽然很慢,但总的耗时不过十几毫秒(参照本文第二章图,即 data pre-fetchwhole request之差),增加十几毫秒的单请求耗时,解放前端和后端的生产力,让前端专注于视图以及路由,后端专注于大量数据的处理,博主认为这是非常值得的,这也是为什么前后端分离以及MVVM框架盛行的原因。

最后,对于vue2而言,由于vuex并没有采用不可变数据immutable,导致服务端渲染处理每次请求时都必须用vm模块去加载服务端打包文件,来避免不同请求间状态共享这一问题。这种做法会增加vm模块启动时的耗时,拖慢了SSR的速度。

因此理论上来说,react+redux+immutable.js会比vue+vuex在SSR环境中要快一些

本文链接:https://smallpath.me/post/vue2-ssr-connect-db

-- EOF --