最近兩個專案同時開發,使用了Vue2的SSR,這樣後端渲染頁面首屏可以加快頁面呈現,增加SEO和使用者體驗,但是專案上線後卻發現了嚴重的效能問題,於是在三天內兩次重大調整,最後只能放棄Vue SSR,本文從Vue SSR實現開始,逐漸覆盤整個事件。
兩週前就預告了要寫一篇Vue SSR的文章,但是沒想到上週四上線之後,週六放量之後發現效能問題,這週一到週三,做了兩次重大調整,最終還是放棄了SSR,並且做了這次事件覆盤。
技術選型
調研Vue已經很久了,隨著Vue2正式釋出,使用Vue來做專案又燃起了希望,不是為了一時的技術理想和情懷(瞭解我的人都知道,我不是這樣的人),主要是出於下面幾方面考慮:
- 用artTemplate+Sass+JS做的components方案已經做了很久了,沉澱了很多元件,隨著Node服務開始上線,一直想在此基礎上做同構,而公司Node框架Yog2的view層選擇偏向於Smarty模板的Swig,修改比較麻煩
- 既然改不了,那麼要換不如直接選擇新的components方案,這次最強烈需求是:元件化和支援SSR,而Vue2之後支援SSR
- 這次兩個專案同時進行,而且僅僅給兩週的開發時間,元件化有效提高工作效率,可以把通用的元件抽象出來,多個頁面之間業務元件複用率也很高,而且業務元件在後續的運營活動也可以直接複用
- 手百產品形態複雜,頁面即在手百內使用又有手百外使用,手百內頁面被多個Webview隔開,不適合SPA形式,而手百外適合SPA形式,所以一套程式碼需要適配兩種情況,Vue 可以適應這兩種方式
- Vue的SPA形式可以方便進行PWA和Hybrid改造(繼續關注本公眾號Hybrid系列)
所以,最後決定:上Vue!技術棧:Vue2+Yog2。
再介紹下兩個專案:
- 專案A是老專案進行重構,產品需求要跟功能全部保留,架構跑通使用的是Vue2.1,所以A專案程式碼相對複雜,一直沒有使用Vue2.2
- 專案B是新專案,開始使用Vue2.1,上線後發現已經有Vue2.2,於是升級Vue2.2,並且把專案目錄結構調整一番,Webpack config等都可配
Vue SSR入門到上線
先看下Vue SSR的實現流程圖:
簡單解釋一下:
- app.js是Server和Client公用的
- webpack會根據server-entry.js和client-entry.js打包出來兩個檔案:server-bundle和client-bundle
- server-bundle用於後端渲染(2.1是js檔案,2.2變成json,引入更加方便)
但是這張圖沒有說明在呼叫API介面方面,前後端是怎麼公用程式碼的。前端走的是Ajax請求,後端走的是http請求(百度內部是RAL介面服務管理),結合上圖補充完整的程式碼執行流程圖如下:
webpack區分介面請求方式
在瀏覽器內使用ajax請求,而在服務端需要呼叫內部API請求或者直接讀取儲存(RAL)。ajax請求到達服務端依次經過:action層、model層,最後走到還是API請求或者讀取資料(這裡重點讀三遍。。)。
這裡我們將服務端和客戶端API的請求方法寫在不同的檔案內,但是封裝暴漏的介面都是一樣的(介面模式)。在webpack裡面,針對server和client提供不同的alias:
這樣 require('api/demo')
的時候,會區分開server和client。
server內直接使用yog2 modal內的獲取資料方法,比如:
而client內,直接使用ajax請求:
Vue內使用Vuex來獲取資料
即下圖的流程:
在渲染的時候,prefetch階段通過dispatch觸發Store的Action(Action內允許非同步),Action內呼叫 api/demo
獲取資料成功後commit mutation,這樣整個資料就跑通了。
server.js
server.js是第一次渲染使用的入口action,核心程式碼如下:
//vue2.2
const vueServerRender = require('vue-server-renderer');
const bundle = require('../vue-ssr-bundle.json');
const renderer = vueServerRender.createBundleRenderer(bundle, {
template: '<!--vue-ssr-outlet-->',
cache: require('lru-cache')({
max: 1000,
maxAge: 1000 * 60 * 15
})
})
// 先渲染tpl(swig模板),內容類似vue ssr demo的index.html
// 這裡渲染使用chunk,先輸出不依賴資料的頭部html
res.render('page/index.tpl', { isSendSpeedCode }, (err, html) => {
if (!err) {
var htmls = html.split('<!--vue-ssr-outlet-->')
//先渲染頭部html
res.write(htmls[0])
// swig渲染時間
var time1 = Date.now()
const renderStream = renderer.renderToStream(context)
renderStream.on('data', chunk => {
// 邊解析,邊渲染html
res.write(chunk)
})
renderStream.on('end', () => {
if (isSendSpeedCode) {
// 統計vue 渲染時間
var time2 = Date.now()
var code = `
<script>if(window.alog){
alog('speed.set', 'p_swig', ${time1 - time0});
alog('speed.set', 'p_vue', ${time2 - time1});
}</script>
`
res.write(code)
}
// 渲染尾部html
res.end(htmls[1])
}).on('error', errorHandler)
} else {
errorHandler(err)
}
})複製程式碼
Webpack和FIS3兩次編譯
webpack是vue「全家桶」的後遺症,專案太急沒辦法去掉。我們專案的目錄結構如下:
專案需要兩次打包:
- 第一次是webpack,webpack把
vue-src
資料夾內容根據server-entry
和client-entry
打包出來,分別放進yog2的client和server對應的檔案,之後vue-src
在執行環境就不需要了 - 第二次是FIS3的打包,會按照Yog2的規範release出來可以上線的內容
這裡有個細節:webpack打包出來的靜態資源路徑需要跟FIS3打包的靜態資源路徑一致,不然就沒法通過FIS3進行靜態資源定位,比如替換為CDN地址。
由於vue2.2打出來的server-bundle是json格式檔案,所以FIS無法將json內的靜態資源進行統一管理,需要webpack判斷生產環境直接替換為CDN地址
遇見的其他問題和技巧
client程式碼在server上跑
手百的通用庫Bdbox是client程式碼,程式碼中有一些window
全域性變數的使用,而我們知道Node是沒有 window
的,在Node執行SSR的時候,會報錯,比如下面的程式碼:
// 自執行
isAndroid: /(Android);?[\s\/]+([\d.]+)?/.test(navigator.userAgent)複製程式碼
有兩種改法:
- 把
.isAndroid
由屬性變成方法:.isAndroid()
,放到mount
內執行 - 給vue-server-renderer傳入帶有
navigator.userAgent
的context
利用resolve.alias
目錄結構深了,尤其是Vue裡面還需要呼叫yog model的程式碼,會各種../../
很蛋疼,可以利用alias簡化寫法:
需要注意的是static
的寫法是:<img src=“~static/img/logo.png”
利用Yog2 的 Mock功能進行測試
訂好介面請求引數和返回資料格式之後,後端RD進行API的編寫同時,我們可以利用Yog2的Mock功能,對ral返回的資料進行假資料測試,實現後端和前端RD解耦,大大提高開發效率。
Vue SSR從上線到Case Study
現在來複盤下整個事件:
- 4月5日,完成程式碼開發,全功能提測,開始倒騰上線,晚上第一次上線成功,基本功能迴歸沒問題,
- 緊接著幾次bug修復上線,6號週四上線日,基本沒有問題了
- 4月7日開始APP稽核通過,放量開始,這時候發現隨著流量上升,伺服器扛不住了
- 8日(週六)緊急新增例項,週末算是硬扛過去了
- 10(週一)排查原因,發現記憶體可能存在洩漏和效能問題,增加打點統計後端渲染時間,但是VM相對來說是黑盒,效能不好排查
- 11日(週二)增加lru-cache,細化元件快取,下午上線後,晚上發現記憶體曲線更加嚴重,於是晚上10點回滾lru和元件快取程式碼,隨版本收斂影響,流量繼續上漲,增加機器例項
- 12日(週三)採取降級方案,第一次進入頁面將API資料放到以變數形式放到頁面,然後增加beforeCreate階段程式碼,將頁面資料直接commit給mutation進行渲染,曲線開始平緩
- 13日(週四)觀察一晚曲線沒有問題,中午開始縮容(下線例項)
從週一到週三經過兩次大的調整,終於服務穩定了,其中程式碼review階段,我們也發現了很多程式碼不規範的現象。下面來說下我們使用vue ssr的一些壓測等資料。
單例項QPS、記憶體和CPU資料
從上線之後,記憶體積累到一定時間就飆升,記憶體飆升同時,CPU也進行飆升,具體曲線如下:
從12日(週三19點)上線之後,就開始平穩了,13日中午縮容後,CPU稍有上揚。
同期QPS的資料如下:
檢視全文,關注微信公眾號:「三水清」(sanshuiqing123)