nuxt快取實踐

nw2018發表於2018-06-21

nuxt是基於vuessr解決方案,可以是使用vue語法完成前後端的同構。

然而在與傳統純字串拼接的ssr方案相比,效能就沒那麼好了,nuxt需要在服務端生成虛擬dom,然後再序列化出HTML字串,我們常說nodejs的高效能指的是非同步IO操作頻繁的場景而非CPU操作密集的場景,畢竟nodejs是執行在單執行緒下的,在涉及到高併發的場景下,效能就會有所下降,可以考慮採用合理的快取策略

nuxt的快取可以分為元件級別快取, API級別快取以及頁面級別快取

元件級別的快取

配置項nuxt.config.js的配置大概長這樣子:

const LRU = require('lru-cache')
module.exports = {
  render: {
    bundleRenderer: {
      cache: LRU({
        max: 1000,                     // 最大的快取個數
        maxAge: 1000 * 60 * 15        // 快取15分鐘
      })
    }
  }
}
複製程式碼

並不是說配了該項就實現了元件級別的快取,還需要在需做快取的vue元件上增加name以及serverCacheKey欄位,以確定快取的唯一鍵值,比如:

export default {
  name: 'AppHeader',
  props: ['type'],
  serverCacheKey: props => props.type
}
複製程式碼

上述元件會根據父元件傳下來的type值去做快取,鍵值是:AppHeader::${props.type},由此,新的請求到來時,只要父元件傳下來的type屬性之前處理過,就可以複用之前的渲染快取結果,以增進效能

從該例子可以看出,如果該元件除了依賴父元件的type屬性,還依賴於別的屬性,serverCacheKey這裡也要做出相應的改變,因此,如果元件依賴於很多的全域性狀態,或者,依賴的狀態取值非常多,意味需要快取會被頻繁被設定而導致溢位,其實就沒有多大意義了,在lru-cache的配置中,設定的最大快取個數是1000,超出部分就會被清掉

其次,不應該快取可能對渲染上下文產生副作用的子元件,比如,元件的createdbeforeCreated的鉤子在服務端也會走,元件被快取後就不會執行了,這些可能影響到渲染上下文的地方也要小心,更多內容請參考:元件級別快取

一般來說,比較適合的場景是v-for大量資料的渲染,因為迴圈操作比較耗cpu

API級別的快取

在服務端渲染的場景中,往往會將請求放在服務端去做,渲染完頁面再返回給瀏覽器,而有些介面是可以去做快取的,比如,不依賴登入態且不依賴過多引數的介面或者是單純獲取配置資料的介面等,介面的處理也是需要時間的,對介面的快取可以加快每個請求的處理速度,更快地釋放掉請求,從而增進效能

api的請求使用axiosaxios即可以在服務端使用也可是在瀏覽器使用,程式碼大概長這樣子

import axios from 'axios'
import md5 from 'md5'
import LRU from 'lru-cache'

// 給api加3秒快取
const CACHED = LRU({
  max: 1000,
  maxAge: 1000 * 3
})

function request (config) {
  let key
  // 服務端才加快取,瀏覽器端就不管了
  if (config.cache && !process.browser) {
    const { params = {}, data = {} } = config
    key = md5(config.url + JSON.stringify(params) + JSON.stringify(data))
    if (CACHED.has(key)) {
      // 快取命中
      return Promise.resolve(CACHED.get(key))
    }
  }
  return axios(config)
    .then(rsp => {
      if (config.cache && !process.browser) {
        // 返回結果前先設定快取
        CACHED.set(key, rsp.data)
      }
      return rsp.data
    })
}
複製程式碼

使用上跟平時使用axios還是一樣的,就多加了個cache的屬性標識是否需要在服務端做快取

const api = {
  getGames: params => request({
    url: '/gameInfo/gatGames',
    params,
    cache: true
  })
}
複製程式碼

頁面級別的快取

在不依賴於登入態以及過多引數的情況下,如果併發量很大,可以考慮使用頁面級別的快取, 在nuxt.config.js增加serverMiddleware屬性

const nuxtPageCache = require('nuxt-page-cache')

module.exports = {
  serverMiddleware: [
    nuxtPageCache.cacheSeconds(1, req => {
      if (req.query && req.query.pageType) {
        return req.query.pageType
      }
      return false
    })
  ]
}
複製程式碼

上面的例子根據連結後面的引數pageType去做快取,如果連結後面有pageType引數,就做快取,快取時間為1秒,也就是說在1秒內相同的pageType請求,服務端只會執行一次完整的渲染

nuxt-page-cache參考了route-cache,寫得比較簡陋,你也可以重新封裝下,nuxt最終返回資料是使用res.end(html, 'utf-8'),頁面級別的快取大概的思想如下:

const LRU = require('lru-cache')

let cacheStore = new LRU({
  max: 100,         // 設定最大的快取個數
  maxAge: 200
})

module.exports.cacheSeconds = function (secondsTTL, cacheKey) {
  // 設定快取的時間
  const ttl = secondsTTL * 1000
  return function (req, res, next) {
    // 獲取快取的key值
    let key = req.originalUrl
    if (typeof cacheKey === 'function') {
      key = cacheKey(req, res)
      if (!key) { return next() }
    } else if (typeof cacheKey === 'string') {
      key = cacheKey
    }

    // 如果快取命中,直接返回
    const value = cacheStore.get(key)
    if (value) {
      return res.end(value, 'utf-8')
    }

    // 快取原先的end方案
    res.original_end = res.end

    // 重寫res.end方案,由此nuxt呼叫res.end實際上是呼叫該方法,
    res.end = function () {
      if (res.statusCode === 200) {
        // 設定快取
        cacheStore.set(key, data, ttl)
      }
      // 最終返回結果
      res.original_end(data, 'utf-8')
    }
  }
}
複製程式碼

如果快取命中,直接將原先的計算結果返回,大大提供了效能

總結

在高併發的情況下可以考慮使用快取,而快取策略的使用需要視場景而定,這裡不再贅述,還可以考慮使用pm2開啟叢集模式去管理我們的程式,從而滿足更高的併發。

相關文章