Vue SSR在好大夫的落地

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"​介紹","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於使用過vue的前端開發者來說,對vue-ssr應該有着或多或少的瞭解。那讓我們簡單瞭解一下什麼是vue-ssr?以及它的優劣勢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"什麼是vue-ssr?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ssr是Server-Side Rendering的縮寫,即由服務端負責頁面的渲染和直接輸出。在服務端將vue組件解析渲染成html字符串併發送到瀏覽器端,在瀏覽器端進行渲染激活並進行相關dom操作。由於應用程序的大部分代碼都可以在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"服務器","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端","attrs":{}},{"type":"text","text":"上運行,故也可以被認爲是\"同構\"或\"通用\"。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"優勢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 更好的SEO,服務端返回的頁面是帶有頁面信息的html文檔,這對於搜索引擎的爬蟲是非常友好的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 更快的內容到達時間,特別是對於緩慢的網絡情況或運行緩慢的設備,無需等待所有的 JavaScript 都完成下載並執行後才能看到頁面完整的內容。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. 跨端同構,對於我們編寫的Vuejs的應用程序大部分代碼都是可以在","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"服務器","attrs":{}},{"type":"text","text":"和","attrs":{}},{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"客戶端","attrs":{}},{"type":"text","text":"上運行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"劣勢","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 開發條件所限,在開發ssr項目時,你需要知道你的代碼哪些是在服務端運行,哪些是在客戶端運行,因爲window、document等這些就不能出現在初始化代碼和服務的一些鉤子函數中,global、process等就不能出現客戶端的鉤子函數中。這就需要我們寫出更加通用的代碼來兼容兩端;並且一些外部擴展庫 (external library) 可能需要特殊處理,才能在服務器渲染應用程序中運行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 更多的服務器端負載,在 Node.js 中渲染完整的應用程序,顯然會比僅僅提供靜態文件的 server 更加大量佔用 CPU 資源 (CPU-intensive - CPU 密集),因此如果你預料在高流量環境 (high traffic) 下使用,請準備相應的服務器負載,並明智地採用緩存策略。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對於上面vue ssr的介紹,那麼它主要解決了我們什麼樣的問題呢?好大夫在線爲什麼需要落地Vue-SSR這樣一種技術?它給我們帶來了什麼樣的好處呢?以及它又會帶來什麼問題和挑戰呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"帶着這些問題,我們來看一下目前好大夫前端技術棧的一個現狀。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"現狀","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"一、express+handlebars","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從好大夫引入node的時候,採用的就是express+handlebars,主要是獲取service端數據,處理業務邏輯,解析視圖模板,將解析後的html字符串發送到瀏覽器端。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼有人會問,這不是典型的服務端渲染嗎(SSR)?它不是已經很好地解決了SEO和更快的內容到達嗎?沒錯,現有的模式的確是最典型的服務端渲染,並且在SEO和更快的內容到達。那麼我們是不是就沒有必要使用Vue SSR了呢?其實不然。目前的開發模式主要有以下幾個問題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 頻繁的dom操作、複雜業務狀態管理等,都會使得開發變得困難許多,dom操作彷彿回到了刀耕火種的時代。那有的人就說了,我在頁面引入vue來解決這類問題不是好了,但是對於首次需要加載的數據又如何去獲取填充?那又有人說了,我們首次需要的數據我們服務端渲染,對於頁面交互和異步的數據,我們通過vue去綁定和獲取。那接下來的問題來了,對於首次獲取的數據,如何進行響應式的綁定呢?那還有人說我們可以通過vue  $mount來強制使用激活。這種是一種方式,但是這會導致vue丟棄現有的 DOM 並從頭開始渲染,造成重複渲染的問題。並且此種方式存在維護多套模板的情況(vue template & handlebars template),也就是無法達到代碼同構從而提升開發成本。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"app.$mount('#app', true)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2.對有SEO和更快內容到達需求的情況下很難融合現有的前端技術棧(vue技術棧、react技術棧等)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"3. 對於業務模塊、抽象組件客戶端和服務端無法同構。  ","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"4. 相對於前端vue或是react,開發效率低。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"二、express+vue spa","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於那些沒有SEO需求,以及對於內容到達時間要求不高,但是有着複雜的交互和狀態的流程性、功能性等頁面,我們通常採用的是vue spa的模式進行開發。express相當只是提供了靜態頁面服務,對於頁面的數據通過異步接口獲取填充後再渲染。其弊端我們也很清楚,對於SEO和更快內容到達的頁面是不友好的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"針對於目前使用的技術現狀,我們想解決在使用vue情況下,也能滿足SEO需求和更快的內容到達,並且實現前後端視圖層代碼的通用性。於是我們選擇了Vue SSR。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"vue ssr落地","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在一項技術的落地的過程中,肯定少不了技術選型,前期vue ssr方案調研中我們有對比過nuxt.js,發現nuxt在我們現有的node項目中沒辦法很好的嵌入以及自定義擴展和維護的成本較高,於是我們選擇了vue官方的方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"現有的node架構流程如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/4d/4db218b3dcdabc594fa4f134b1356b14.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於頁面的請求,到達node層,經過各種中間件處理後,會傳遞到路由層,路由層通過匹配的路由規則傳遞到controller層,controller對於業務數據的獲取和業務邏輯的處理,最後拿到獲取的數據渲染指定頁面的模板發送給客戶端。對於現有的node架構,我們如何把vue ssr應用到我們現有的node工程中去呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們提出了兩種解決方案:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 借鑑nuxt,我們可以單獨的把vue server renderer當做一個node服務,對於頁面的數據我們可以通過axios進行跨平臺調用。此node服務主要服務解析路由規則,調用asyncData,通過axios獲取服務端數據,渲染頁面數據,響應頁面請求。此方案職責清晰,功能單一,容易理解。但是介於好大夫技術場景的原因,此方案並不是我們實際使用的。主要原因如下:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於service數據的獲取問題。我們node是通過調用Java、PHP獲取業務數據,但是Java和PHP的接口並不是對外暴露的,導致我們如果是在客戶端渲染就會報錯。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增加node中間層node api層,首先對於一個業務維護兩個node工程,維護成本增加,並且目前好大夫node層不對內提供服務,還有就是涉及請求鏈路變長,請求驗證信息透傳等問題。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"對於現有的node開發方式也不兼容","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於方案一存在的一些問題,我們提出了第二種能很好地結合我們現有的node架構的方案。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 在不脫離我們現有node架構的前提下,我們做了如下設計:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f387ea572cdfb164eb123d7b4a251a3e.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在不影響現有的node架構模式的前提下,我們對於新增的使用vue ssr做服務端頁面,添加了統一的處理。在服務端渲染時我們通過@hnpm/h-ssr-action match router規則,調用asyncData方法,獲取頁面數據,進行服務端數據渲染,響應頁面請求。對於降級的情況下我們@hnpm/h-ssr-action不會獲取頁面數據,而是直接輸出無數據的模板頁,在客戶端去異步獲取service數據。","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"const ssrAction = require('@hnpm/h-ssr-action');\n...\nconst ssrPageRouterArr = ['/index/search',/\\/*.php/,'/nsearch/*',...];\nrouter.get(ssrPageRouterArr, [...middleware], ssrAction(app));\n...","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@hnpm/h-ssr-action  在渲染之前解析cdn資源、插入頁面監控代碼、統一處理error handle和404 頁面、添加自定義請求頭標識等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這就很好的解決了我們上面面臨的問題,我們node即提供了renderer能力也提供api接口的能力。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"那麼有人會問了,你是如何實現node自身調用的問題的?對於node在調用asyncData的問題上,我們是進行端的標識的,對於service端和client端來的請求,我們設置了自定義的請求頭,如下:","attrs":{}}]},{"type":"codeblock","attrs":{"lang":"javascript"},"content":[{"type":"text","text":"// server\nreq.headers['x-hdf-ssr'] = 'server';\n\n// client\naxios.create({ ... headers: { 'x-hdf-ssr': 'client' }});","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其次我們封裝了統一的請求類庫@hnpm/h-ssr-fetch。@hnpm/h-ssr-fetch  主要進行端的區分,我們對於服務端渲染的請求直接require action由action直接返回數據(通過match當前訪問路由規則找到對應action文件,避免node自己調用自己api和cookie透傳等問題),而對於客戶端來的請求我們會通過axios發起請求走正常的http請求(並且對該請求做了統一的處理)。這樣的話我的baseController層添加統一處理不同端的響應方式。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"與此同時我們還封裝了@hnpm/h-ssr-server 實現客戶端和服務器端路由邏輯 ,以及處理一些加載指示器、服務端降級處理、路由守衛全局配置等。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此後,我們還將vue ssr集成到內部的開發工具skit中(vue ssr cli (skit ssr)  ),實現了:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"skit ssr -i 初始化vue skit工程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"skit ssr -b 構建vue ssr工程","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"@hnpm/setup-dev-server  開發環境實現熱更新","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"自動化構建方面我們也將打包skit ssr工程作爲構建的一個環節,實現開發測試上線的一個閉環流程。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"降級處理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由於vue ssr很大的一個缺點是更多的服務器端負載。所以在上線前我們做了一次壓力測試,在8核8進程下渲染1000條靜態數據的qps量大概是2200-2300左右。如下圖:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7b/7b1b128fac95b54afaed9fd35eb1c3d2.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"承載現有業務的qps量,我們node服務集羣完全沒有問題的。但是萬一出現服務器壓力飆升怎麼辦?(因爲服務端渲染是CPU密集型操作,很耗CPU資源)爲了保證系統的可用和穩定性,我們設計了必要的降級方案。如下:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Apollo配置渲染方式,在可預見的大流量或是系統大流量告警的情況下,及時通過平臺配置使得整個node集羣降級到客戶端渲染。","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"cpu閥值降級,當單臺node機器中cpu資源佔用觸及閥值時主動降級。避免單臺機器流量過高而導致負載過高。","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"除此之外,對於單個流量也存在降級的情況,也就是對於單個請求出現服務端渲染失敗,也會降級到客戶端渲染,保證頁面的可用性。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"遺留的問題","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 目前在頁面模板渲染數據時我們都是通過vuex獲取填充,這就需要我們每次都會去從vuex mapstate數據,對於數據狀態比較單一併且數據只是用來展示的情況下,通過從vuex獲取數據顯得有點繁瑣。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 在qps壓測時,我們發現相對於handlebars的vue ssr渲染的效率較低,在單一的靜態數據渲染能力上handlebars的渲染能力要比vue ssr效果高出20-30%。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"未來規劃","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"1. 參考nuxt把asyncData獲取的數據的數據同步到頁面組件的data中,方便開發時獲取頁面的渲染數據。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"2. 添加有效的緩存策略保證node服務響應請求的能力,深入對比handlebars和vue ssr頁面渲染機制,提高vue ssr渲染的效率。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":"center","origin":null},"content":[{"type":"text","text":"------- END -------","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"【作者簡介】","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"梁偉:好大夫在線前端架構師,專注於前端系統設計、流程優化等前端基礎建設。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a2/a2dd3cc4988032c5982b86cc5ec8355b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"blockquote","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"好大夫在線創立於2006年,是中國領先的互聯網醫療平臺之一。已收錄國內10496家正規醫院的69.2萬名醫生信息。其中,23萬名醫生在平臺上實名註冊,直接向患者提供線上醫療服務。“讓行醫簡單,看病不難” ,始終追求“成爲值得信賴的醫療平臺”。","attrs":{}}]}],"attrs":{}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章