基于Nuxt.js项目的服务端性能优化与错误检测(容错处理)


Posted in Javascript onOctober 23, 2019

nuxt.js 是一个基于 Vue.js 的服务端渲染应用框架,使用nuxt.js在做同构项目开发时,需要考虑的一些点总结如下:

一、node服务端性能优化(提高node应用程序处理高流量的能力)

基于nuxt.js的服务端渲染项目我们能做的服务端性能优化有以下几点(需要注意的是持久化缓存不应该在本地开发环境去做,这样在缓存期间不会暴露本地开发中代码的问题)

优化点 参考文档及思路 优化场景/条件 特别说明 检测方法
1. 页面缓存 vue官方文档 页面内容不是用户特定(即对于相同的 URL,总是为所有用户渲染相同的内容) 一般来说,一个页面在服务端做了持久化缓存,那么对应页面的存在的api缓存,组件缓存也就没有意义了,对于页面缓存与api缓存同时存在的情况下(有可能存在),api缓存的时间应该比页面缓存的时间小,这样是为了让api响应的内容保持最新 1、代码本地测试:在asyncData中打印测试日志,页面缓存后,刷新页面后服务端不会输出测试日志;2、比较html页面加载的DOMContentLoaded时间,刷新页面可以看到缓存后的值比首次页面加载(未缓存)的值要小
2. api缓存 在axios请求与响应拦截器中去做 接口响应内容不是用户特定(即对于相同的api接口URL,即总是为所有用户响应相同的内容) 一般请求方式为GET的api请求 比较首次请求与缓存后的api接口响应的时间
3. 组件缓存 nuxtjs官网文档 vue 官网文档 不依赖与全局状态,对渲染上下文不产生副作用的子组件 要缓存的组件name值必须唯一,serverCacheKey根据某个prop的值作为唯一key 检测方法同页面缓存检测方法一致,这个可能几乎察觉不到
4. asyncData函数优化 Promise.all 该函数中请求api接口数超过1个,多的甚至达到10,20多个,这种情况我们不能使用async await,请求完一个再接着请求下一个(同步请求接口);如果有10个接口需要请求,每个接口平均响应1s,那么至少需要10s才会响应html页面;如果使用Promise.all异步请求10个接口,那么最快接近1s响应html页面; asyncData函数会在服务端执行代码,因此一定要做好容错处理;另外如果该函数代码一直未执行完,那么页面首次响应将会被挂起,一直处于加载中 对于页面首次加载,该函数执行耗时越短,页面响应时间就越短(页面加载越快)

1、页面缓存功能模块实现

我们在项目根目录中创建一个文件 ~/serverMiddleware/page-cache.js

import LRUCache from 'lru-cache'

const cache = new LRUCache({
 maxAge: 1000 * 60 * 2, // 有效期2分钟
 max: 1000 // 最大缓存数量
})

export default function(req, res, next) {
 // 本地开发环境不做页面缓存
 if (process.env.NODE_ENV !== 'development') {
 try {
  const cacheKey = req.url
  const cacheData = cache.get(cacheKey)
  if (cacheData) {
  return res.end(cacheData, 'utf8')
  }
  const originalEnd = res.end
  res.end = function(data) {
  cache.set(cacheKey, data)
  originalEnd.call(res, ...arguments)
  }
 } catch(error) {
  // console.log(`page-cache-middleware: ${error}`)
  next()
 }
 }
 next()
}

2、api缓存功能模块实现

我们在项目根目录中分别创建两个文件 ~/plugins/axios/createCacheKey.js 与 ~/plugins/axios/cache.js ;特别坑的一点是nuxt.js开发环境cache.js插件代码在页面刷新,路由切换都相当于首次运行,因此你会发现缓存功能失效,只有在 process.env.NODE_ENV === 'production' 生产环境中测试有效

// ~/plugins/axios/createCacheKey.js

import md5 from 'md5'

/**
 * 根据请求配置,是否是请求拦截器 创建缓存key
 * @param {Object} config
 * @param {Boolean} isRequest 
 */

export default function createCacheKey(
 config = {},
 isRequest = false
) {
 const {
 url,
 data,
 params,
 method,
 baseURL,
 } = config || {}

 let commonUrl = url

 /**
 * request拦截器中config.url是未拼接baseURL的,response拦截器中response.config.url是拼接过baseURL的,
 * 为了保持统一,使用统一拼接baseURL的commonUrl;注意下面的if条件判断
 */
 if (isRequest && !commonUrl.match(baseURL) && !commonUrl.match(/^https?/)) {
 commonUrl = !!baseURL.match(/.+\/$/) ? `${baseURL.replace(/\/$/, '')}${url}` : `${baseURL}${url}`
 }

 // 根据请求指令,url,body体,参数生成规则
 const rule = `method=${method}-url=${commonUrl}-data=${JSON.stringify(data || {})}-params=${JSON.stringify(params || {})}`

 // md5加密
 return md5(rule)
}

// ~/plugins/axios/cache.js

import LRUCache from 'lru-cache'
import axios from 'axios'
import globalConfig from '../../global-config'
import createCacheKey from './createCacheKey'

const cache = new LRUCache({
 maxAge: 1000 * 60, // 有效期60秒,如果存在页面缓存,api缓存的时间应该比页面缓存的时间小,这样是为了让api响应的内容保持最新
 max: 1000 // 最大缓存数量
})

/**
 * matchCacheCondition 是否满足持久化缓存条件:服务端运行时 && 非本地开发环境 && api请求为get请求方式
 * @param {Object} config 请求配置
 */
function matchCacheCondition(config = {}) {
 return process.server && process.env.NODE_ENV !== 'development' && config.method.toLowerCase() === 'get'
}

/**
 * 如果所有页面都启用了缓存,api缓存就没有必要了
 */
export default function({ $axios, redirect }) {
 $axios.interceptors.request.use(config => {
 const { baseUrl } = globalConfig
 config.baseURL = baseUrl[process.env.environment] || baseUrl['other']

 // 不满足缓存条件直接return config
 if (!matchCacheCondition(config)) {
  return config
 }

 const cacheKey = createCacheKey(config, true)
 const cacheData = cache.get(cacheKey)

 if (cacheData) {
  const source = axios.CancelToken.source()
  config.cancelToken = source.token
  source.cancel({ cacheData, cacheKey, url: config.url })
  return config
 }

 return config
 })

 $axios.interceptors.response.use(response => {
 if (matchCacheCondition(response.config)) {
  cache.set(createCacheKey(response.config), response)
 }
 return response
 }, (error) => {
 if (axios.isCancel(error) && matchCacheCondition(response.config)) {
  // console.log(`当前页面组件asyncData或者fetch函数中被缓存的接口url为:${error.message.url}`)
  return Promise.resolve(error.message.cacheData)
 }

 // 服务端打印api接口请求错误日志
 if (process.server) {
  try {
  const {
   config: {
   url
   },
   message
  } = error || {}
  console.log(`请求url:${url},错误消息:${message}`)
  } catch(error) {
  // console.log(error)
  }
 }

 // 服务端,客户端统一reject错误对象,因此页面组件asyncData,fetch函数请求api接口一定要做catch处理
 return Promise.reject(error)
 })
}

3、组件缓存

vue官网文档原话:如果 renderer 在组件渲染过程中进行缓存命中,那么它将直接重新使用整个子树的缓存结果。这意味着在以下情况,你不应该缓存组件:

  • 它具有可能依赖于全局状态的子组件。
  • 它具有对渲染上下文产生副作用(side effect)的子组件。

因此,应该小心使用组件缓存来解决性能瓶颈。在大多数情况下,你不应该也不需要缓存单一实例组件。适用于缓存的最常见类型的组件,是在大的 v-for 列表中重复出现的组件。由于这些组件通常由数据库集合(database collection)中的对象驱动,它们可以使用简单的缓存策略:使用其唯一 id,再加上最后更新的时间戳,来生成其缓存键(cache key):

serverCacheKey: props => props.item.id + '::' + props.item.last_updated

4、页面组件asyncData函数优化

举一个简单的例子进行优化

{
 async asyncData({ $axios }) {
 // 1、增加catch处理,是为了让服务端,客户端运行时不报错,特别是防止服务端运行时不报错,不然页面就挂了
 // 2、catch函数返回一个resolve空字面量对象的Promise,表明dataPromise1的状态未来始终是resolved状态
 const dataPromise1 = $axios.get('/api/data1').catch(() => Promise.resolve({}))

 const dataPromise2 = $axios.get('/api/data2').catch(() => Promise.resolve({}))
 const dataPromise3 = $axios.get('/api/data3').catch(() => Promise.resolve({}))
 const dataPromise4 = $axios.get('/api/data4').catch(() => Promise.resolve({}))
 const dataPromise5 = $axios.get('/api/data5').catch(() => Promise.resolve({}))
 const dataPromise6 = $axios.get('/api/data6').catch(() => Promise.resolve({}))
 const dataPromise7 = $axios.get('/api/data7').catch(() => Promise.resolve({}))
 const dataPromise8 = $axios.get('/api/data8').catch(() => Promise.resolve({}))

 // 保证apiData有数据
 const apiData = await new Promise(resolve => {
  Promise.all([
  dataPromise1, dataPromise2, dataPromise3, dataPromise4,
  dataPromise5, dataPromise6, dataPromise7, dataPromise8,
  ])
  .then(dataGather => {
   resolve({
   data1: dataGather[0],
   data2: dataGather[1],
   data3: dataGather[2],
   data4: dataGather[3],
   data5: dataGather[4],
   data6: dataGather[5],
   data7: dataGather[6],
   data8: dataGather[7],
   })
  })
 })

 return apiData
 }
}

二、node服务端错误检测,容错处理(提高node应用程序处理容错的能力)

首先确定使用nuxt.js框架,vue组件(页面/非页面组件)中以下函数都会在服务端执行,因此代码容错非常重要,函数代码执行一旦出错,页面就挂了

  • fetch
  • asyncData
  • beforeCreate
  • created

1、看的见的错误

看的见的错误是指在开发环境中,你只要在fetch等以上函数中js执行错误,本地就会有错误提示,便于你发现纠正错误代码逻辑

2、未知/看不见的错误(让未知错误暴露出来)

看不见的错误是指一些异步回调中的错误代码不容易被发现,如果异步行为一直没有触发,那么处理该异步行为的回调代码也不会执行;但是对于处理所有页面的api接口请求回调的错误排查(主要是做容错处理,使代码更加健壮,java接口请求404、接口数据字段/结构的处理)我们能够做好,很简单,我们只需要在请求拦截器中把请求url更改就可以

$axios.interceptors.request.use(config => {
 // TODO
 // 检测由于请求java接口失败而导致的node应用程序错误
 config.url += '/xxxx'

 return config
})

3、对于页面刷新加载不需要渲染的数据的处理

只有页面组件asyncData(函数返回的对象跟组件data融合),fetch(更新store操作)函数处理的数据跟页面绑定后,页面刷新加载服务端才会渲染;因此不建议组件在beforeCreate,created函数中通过请求api接口获取页面刷新加载不需要渲染的数据,只需要在mounted函数中处理即可,防止由于代码错误导致node应用程序出错

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

Javascript 相关文章推荐
javascript 原型模式实现OOP的再研究
Apr 09 Javascript
IE 条件注释详解总结(附实例代码)
Aug 29 Javascript
JS实现程序暂停与继续功能代码解读
Oct 10 Javascript
在jquery中combobox多选的不兼容问题总结
Dec 24 Javascript
js/jquery获取文本框输入焦点的方法
Mar 04 Javascript
首页图片漂浮效果示例代码
Jun 05 Javascript
jQuery中nextUntil()方法用法实例
Jan 07 Javascript
基于jQuery实现简单的折叠菜单效果
Nov 23 Javascript
jQuery实现的网格线绘制方法
Jun 20 Javascript
详解微信小程序 登录获取unionid
Jun 27 Javascript
AngularJS实现表单验证功能详解
Oct 12 Javascript
Vue使用vue-recoure + http-proxy-middleware + vuex配合promise实现基本的跨域请求封装
Oct 21 Javascript
原生JS无缝滑动轮播图
Oct 22 #Javascript
Vue实现开心消消乐游戏算法
Oct 22 #Javascript
p5.js码绘“跳动的小正方形”的实现代码
Oct 22 #Javascript
微信小程序绑定手机号获取验证码功能
Oct 22 #Javascript
微信小程序开发之转发分享功能
Oct 22 #Javascript
微信小程序转发事件实现解析
Oct 22 #Javascript
小程序点击图片实现png转jpg
Oct 22 #Javascript
You might like
Smarty变量调节器失效的解决办法
2014/08/20 PHP
jquery ui dialog ie8出现滚动条的解决方法
2010/12/06 Javascript
深入理解JavaScript系列(36):设计模式之中介者模式详解
2015/03/04 Javascript
js代码验证手机号码和电话号码是否合法
2015/07/30 Javascript
Javascript实现鼠标右键特色菜单
2015/08/04 Javascript
jquery调整表格行tr上下顺序实例讲解
2016/01/09 Javascript
JS获取复选框的值,并传递到后台的实现方法
2016/05/30 Javascript
Vue系列:通过vue-router如何传递参数示例
2017/01/16 Javascript
轻松理解Javascript变量的相关问题
2017/01/20 Javascript
浅谈React Native 中组件的生命周期
2017/09/08 Javascript
微信小程序实现基于三元运算验证手机号/姓名功能示例
2019/01/19 Javascript
JavaScript数据结构与算法之二叉树插入节点、生成二叉树示例
2019/02/21 Javascript
vue-router之解决addRoutes使用遇到的坑
2020/07/19 Javascript
一篇文章带你搞懂Vue虚拟Dom与diff算法
2020/08/25 Javascript
[05:09]2016国际邀请赛中国区预选赛淘汰赛首日精彩回顾
2016/06/29 DOTA
Python struct.unpack
2008/09/06 Python
python连接MySQL、MongoDB、Redis、memcache等数据库的方法
2013/11/15 Python
Python编程中字符串和列表的基本知识讲解
2015/10/14 Python
python3实现暴力穷举博客园密码
2016/06/19 Python
Pyspider中给爬虫伪造随机请求头的实例
2018/05/07 Python
Python读取csv文件分隔符设置方法
2019/01/14 Python
django 信号调度机制详解
2019/07/19 Python
Python编写带选项的命令行程序方法
2019/08/13 Python
基于python实现从尾到头打印链表
2019/11/02 Python
python实现将json多行数据传入到mysql中使用
2019/12/31 Python
openCV提取图像中的矩形区域
2020/07/21 Python
大学生简历中个人的自我评价
2013/10/06 职场文书
工程地质勘察专业大学生求职信
2013/10/13 职场文书
给朋友的道歉信
2014/01/09 职场文书
优秀团队获奖感言
2014/02/19 职场文书
霸王洗发水广告词
2014/03/14 职场文书
法律进社区实施方案
2014/03/21 职场文书
项目技术负责人岗位职责
2015/04/13 职场文书
少年雷锋观后感
2015/06/10 职场文书
职工食堂管理制度
2015/08/06 职场文书
如何用六步教会你使用python爬虫爬取数据
2022/04/06 Python