Nuxt頁面級快取

大洋洋2020發表於2020-03-08

  雖然 Vue 的伺服器端渲染 (SSR) 相當快速,但是由於需要為每次請求為了避免交叉請求狀態汙染,都建立一個新的根Vue例項,建立元件例項和虛擬 DOM 節點的開銷,無法與純基於字串拼接的模板的效能相當。在 SSR 效能至關重要的情況下,明智地利用快取策略,可以極大改善響應時間並減少伺服器負載。同時還可以大大減少後端介面伺服器的負載。
  在vue SSR指南中,快取有兩種,分為頁面級快取和元件級快取。本次講的是頁面快取,如果內容不是使用者特定的並且在相對較短時間內,頁面內容不需要更新。我們就可以使用頁面快取。對於頁面級快取我們可以通過這段koa伺服器的程式碼大概知道快取的思路:

const microCache = LRU({
  max: 100,
  maxAge: 1000 // 重要提示:條目在 1 秒後過期。
})

const isCacheable = req => {
  // 實現邏輯為,檢查請求是否是使用者特定(user-specific)。
  // 只有非使用者特定 (non-user-specific) 頁面才會快取
}

server.get('*', (req, res) => {
  const cacheable = isCacheable(req)
  if (cacheable) {
    const hit = microCache.get(req.url)
    if (hit) {
      return res.end(hit)
    }
  }

  renderer.renderToString((err, html) => {
    res.end(html)
    if (cacheable) {
      microCache.set(req.url, html)
    }
  })
})
複製程式碼

流程圖如下:

Nuxt頁面級快取

  上面的程式碼為vue的ssr渲染提供了方案,但是對於使用nuxt框架的同學而言,用腳手架初始化完,框架對於vue服務端渲染的res.end()函式做了高度封裝,從下圖nuxt在接收到請求後進行渲染的流程可以看出,nuxt主要是通過nuxtMiddleware呼叫renderRoute()來進行渲染的:

Nuxt頁面級快取

  那麼我們是否可以通過重寫renderRoute()這個api攔截其內部渲染邏輯,在渲染之前加上快取呢?nuxt-ssr-cache外掛已經這樣做了。我們來看一下這個nuxt模組核心部分的原始碼:

const renderer = nuxt.renderer;
const renderRoute = renderer.renderRoute.bind(renderer);
renderer.renderRoute = function(route, context) {
    // hopefully cache reset is finished up to this point.
    tryStoreVersion(cache, currentVersion);

    const cacheKey = (config.cache.key || defaultCacheKeyBuilder)(route, context);
    if (!cacheKey) return renderRoute(route, context);

    function renderSetCache(){
        return renderRoute(route, context)
            .then(function(result) {
                if (!result.error) {
                    cache.setAsync(cacheKey, serialize(result));
                }
                return result;
            });
    }

    return cache.getAsync(cacheKey)
        .then(function (cachedResult) {
            if (cachedResult) {
                return deserialize(cachedResult);
            }

            return renderSetCache();
        })
        .catch(renderSetCache);
};

複製程式碼

  在這段程式碼中,先儲存了renderer原來的renderRoute程式碼,之後又重寫了renderRoute程式碼,返回了一個通過cache快取來獲取快取內容的邏輯。cache返回了一個promise,如果是resolve的,並且有快取的內容,就直接返回快取內容。如果沒有快取內容或者reject,就執行renderSetCache()。而renderSetCache()中,返回了原來最初的renderRoute()處理邏輯,同樣如果renderRoute()返回的promise被resolve了,那麼就通過cache的setAsync方法來進行快取,之後返回渲染結果。
  使用方法大家自行參考git中的readme文件,這裡就不說了。
  下面我們真正來模擬一下,看看這個模組的功效到底如何。我們通過ab命令

ab -n 4000 -c 50 -s 120 -r http://localhost:3000/
複製程式碼

來進行壓測:

  第一種情況,沒有新增頁面快取,大約持續請求了10秒鐘,執行到3600個請求的時候,發生錯誤,不再繼續請求了:

Nuxt頁面級快取

我們來通過日誌看下是什麼錯誤:

Nuxt頁面級快取

可以看到FATAL ERROR這一句,JavaScript heap out of memory。堆記憶體已經沒有辦法再進行分配,所以程式終止了。

我們在終止之前通過程式監視器可以看到node程式已經彪到了1.7GB的記憶體。

Nuxt頁面級快取

  第二種情況,我們新增了頁面快取,通過server端的日誌,我們可以看出,只請求了一次後端的api資料介面,說明快取已經成功攔截了頁面請求。請求資料如下:

Nuxt頁面級快取

在2秒鐘之內,就順利結束了4000個請求,記憶體沒有任何明顯波動,優化效果顯而易見。

  更多精彩文章請關注公眾號:

Nuxt頁面級快取

相關文章