性能優化,這是面試中經常會聊到的話題。我覺得性能優化應該因具體場景而異,因不同項目而異,不同的手段不同的方案並不一定適合所有項目,當然這其中不乏一些普適的方案,比如耳熟能詳的文件壓縮,文件緩存,CDN,DNS 預解析,等等,但是我更希望聽到的是因為不同的項目不同的需求,解決不同的問題而采取的不同的優化手段,比如 BigPipe,分段輸出頁面的各個部分,對於 SNS 網站是非常合適的,減少了用戶的等待時間;相對應的還有一個 BigRender,這是一個大的延遲加載,360 導航首頁目前還在使用,京東淘寶首頁也是這個思路,對於一些類門戶網站非常適用,但是如果你的網頁內容不是非常多,就沒有必要了
今天要說的是 Nuxt。Nuxt 是支持 Vue SSR 的一個框架,底層需要運行 Node 服務。大概描述一下 Vue 的渲染過程,首先每個組件都會被編譯生成一個渲染函數(這部分基本 webpack 打包已經做掉),然后渲染函數生成虛擬 dom,最后虛擬 dom 通過 patch 方法將真實 dom 渲染到頁面上。Nuxt 其實就是將這部分放到了服務端去做,在服務端拿到渲染頁面所需要的 html,從而使得 html 能夠直出,而客戶端其實還是會運行整個 Vue 的生命周期,這就帶來了一個問題,這部分操作放在了服務端其實是非常耗 cpu 的,創建組件實例和虛擬 DOM 節點的開銷,無法與純基於字符串拼接的模版的性能相當,如果是不加優化的 Nuxt 項目,高並發下是很脆弱的,畢竟 Node 運行在單線程下,不適合 cpu 操作密集型的場景
使用 Nuxt 的項目無非看中了它的兩大優點,一是服務端渲染滿足 SEO 的需求,二是首屏直出比 SPA 快,再加上如果如果公司是 Vue 系,使用 Nuxt 就更順理成章。但是不要忘了性能,高並發下 Nuxt 性能確實不樂觀,我測試了官網的 hackernews demo 項目,2 核 cpu + 4g 內存,400 並發下它的吞吐量不超過 50,就算是最簡的 Nuxt 項目,吞吐量也就 300+,這就說明如果項目不做緩存,300+ 已經是最大的吞吐量了,而最小 express demo 可以輕松到 3000,這就決定了高流量項目並不會輕易去使用 Nuxt
我們的項目目前其實是一個不加優化的 Nuxt 項目,因為用戶不多,平時並沒有什么問題,但是一到展會,就會有不少用戶同時訪問,反饋頁面會很卡。同條件下做了壓測后,吞吐量也是 50 上下,平均響應時長七八秒,所以卡是正常現象
看了一下項目代碼,發現了幾個問題:
-
項目沒做緩存,所以每次訪問都會經歷所有 Nuxt 生命周期,消耗 cpu,這點是最致命的
-
項目打包默認 gzip。Nuxt 項目打包會默認在服務端開啟 gzip,因為我們網關層已經做了 gzip,所以這里是不必要的,測試了下關掉 gzip 吞吐量和響應時間都能提高 20% 左右。具體做法是在 nuxt.config.js 中配置(還是得看 英文文檔,會告訴你如何不設置
To disable compression, use compressor: false
,中文文檔當時三月份我寫這文的時候還沒加這個選項,而目前中文文檔也沒有翻譯這一句 2020-07-16)render: { compressor: false }
-
API 請求比較亂。很多請求並沒有很好地區分客戶端和服務端,而是都由服務端去做了,造成服務端壓力過大,其實多數和用戶有關的請求理應放到客戶端。有的接口為了方便,一次性返回了所有內容,也沒有做客戶端/服務端區分。另外,服務端的接口請求可以並發,用類似 Promise.all 的形式去控制
-
SEO。有的內容頁面,很長,有五個部分,除了內容外,還有猜你喜歡等其他部分,詢問了 SEO 同事,說這幾部分都是需要 SEO 的,我不是很懂 SEO,但是在我看來,ssr 只應該渲染首屏內容,而 UI 在設計的時候應該把主要內容設計到首屏,從而滿足 SEO
對此我覺得可以從兩個方向去優化:
-
緩存。緩存是最重要的方案,針對 Nuxt 項目可以做三級緩存,頁面緩存、組件緩存以及 API 緩存。頁面緩存是最重量級的緩存方案,能不能做頁面緩存可以從以下兩個點判斷:
- 同一個 URL,對於 登錄 / 非登錄 用戶,服務端渲染的內容是相同的(注意是服務端渲染內容,而非前端)
- 同一個 URL,對於不同的登錄用戶,服務端渲染的內容是相同的,即沒有一些個性化的渲染(常見的個性化渲染,比如針對不同用戶渲染不同的猜你喜歡內容等)
其實也就是返回的 html 代碼相同就好,主要關注下返回的全局 store 是否一致,另外也不能做一些服務端才能做的操作,比如 set-cookie 等
-
控制好首屏模塊個數,對返回的結果進行精簡,最小化,保證吐出到瀏覽器的內容足夠小。這就是前面說的並不要對所有模塊都做 ssr,需要首屏呈現的/需要爬蟲爬的,我們直出,其他部分做 CSR 就行了
而我們的網站大部分頁面是滿足做頁面緩存條件的,測試了下如果做頁面緩存,吞吐量能到 500+,這個數據這個時候其實是和頁面大小有關系了,頁面緩存的性能是能滿足需求的。而有另一類頁面,相同的 URL 會返回不同的內容,而且整頁都是不同內容,它的實現是獲取 cookie 中的不同 city-id,渲染不同城市的內容,很顯然這部分頁面做不了頁面緩存了,API 緩存和組件緩存理論上都是可以試試的
做緩存優化,至少需要訪問一次,第二次才能生效,那么還有另一種情況,對於這樣的路由 /store/:id
,並發打開 id 0~1000,很顯然每個頁面都是不一樣的店鋪數據,並不能命中緩存(可能命中組件緩存,暫時忽略),這個時候只能從 Nuxt 生命周期上去優化了,那么以上方向的第二點,控制首屏模塊個數就能用到了。所以本文一開始我就說,不同的方案是適配不同的場景的,解決不同的問題會采取不同的手段