閒魚源碼頁面SSR最佳實踐

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"背景"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"「讓每一個用戶在最短的時間內看到頁面上重要的內容」一直以來都是前端工程師們精益求精的方向。對於一個H5的源碼頁面,我們已經有了很多縮短首屏渲染時間的方法,比如數據預取,離線緩存。但在目前看來,由於數據預取和離線緩存都依賴客戶端的能力,很多時候會給我們帶來一些限制。比如用於增長業務的外投拉新頁面,我們並不能知曉第三方APP是否具備這樣的能力。再比如使用離線緩存能力,我們受制於命中率高低,以及緩存對APP性能帶來的負面影響這樣的問題。"}]},{"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":"服務端渲染,讓最重要的內容和用戶之前,只需要請求HTML DOC的時間,並且不依賴於客戶端的能力,可以大大縮短用戶看到頁面首屏內容的時間。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"方案要點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"目前主流前端框架比如React,都已經提供了支持SSR的API,我們可以只用幾行代碼便將一段原本執行在客戶端的繪製UI的邏輯轉化爲可以在Node層直出HTML的功能。在此基礎之上,一個SSR技術方案應該同時做到以下幾點要求:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"頁面首屏有效繪製(FMP)時間變短:從webview發出頁面url請求,到用戶看到首屏有效內容的時間真實變短。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用穩定性高,運維成本低:保證由Node應用提供前端頁面,儘可能跟已經非常成熟的「CDN緩存前端靜態資源 + Java服務提供首屏數據」有相近的穩定性。並且Node服務一旦出現不可用的情況,頁面能夠自動降級到穩定的CSR(當下H5頁面都在用的客戶端渲染)模式,而不需要工程師手動執行降級。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"低研發成本:採用SSR以後,前端工程師仍然只需要關心業務功能的實現,而無需爲滿足穩定性要求增加額外開發成本。"}]}]}]},{"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":"本文將重點介紹閒魚的SSR方案圍繞這三個要點進行了怎樣的設計。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"技術架構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"先來看下閒魚目前SSR架構整體的設計,再來單獨聚焦每一個功能點的實現。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a2\/a2df406dfb46b9a6966b34ae7d727fb7.png","alt":"圖片","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":"如圖,架構設計分別考慮了用戶正常訪問的SSR鏈路(紅色結點),以及SSR應用不可用時自動降級到的CSR鏈路(藍色結點)。用CSR鏈路作爲SSR失敗時的降級兜底方案,是保證SSR方案穩定性的關鍵點。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"SSR鏈路"}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"基於serverless的node應用"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"隨着阿里Serverless生態建設的不斷完善,前端同學開發Node應用,已經不需要像面對傳統Node應用一樣,花費太多時間來運維Node應用。我們可以憑藉「函數即服務」的FaaS能力,聚焦於Node應用本身功能的實現。"}]},{"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":"爲了實現SSR功能我們需要在Node層實現兩個服務:"}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"數據聚合。一個前端頁面首屏需要的數據,可能來自於多個服務端(Java)的接口。我們在Node層實現一個Service,該Service調用首屏依賴的所有服務端接口,將其匯聚爲一個接口,輸出全部首屏數據。這種由Node層實現數據膠水的設計,同時可以降低前端與服務端開發協作的成本。前端只需要得到服務端原子功能級別的數據,便可以根據UI的需要按照自己方便的方式定義最終輸出的數據結構。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直出HTML頁面(HTTP服務)。當用戶訪問某個URL時,Node應用獲取頁面構建產物,同時調用上條提到的首屏數據接口,將返回的數據用於生成頁面的DOM樹。"}]}]}]},{"type":"heading","attrs":{"align":null,"level":4},"content":[{"type":"text","text":"SSR應用網關"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當需要發佈某一個前端應用,或者當某一個應用不可用時,我們肯定不希望影響到其他的應用。所以我們在業務維度,將跟某個特定業務相關聯的前端功能作爲獨立的Node應用單獨維護。那麼,爲什麼我們還需要一個公共的網關爲應用提供服務,而不是每一個應用單獨向用戶提供服務呢?"}]},{"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":"原因是每一個對用戶開放的HTTP服務,都必須接入集團的"},{"type":"text","marks":[{"type":"strong"}],"text":"「統一接入」"},{"type":"text","text":"機制。統一接入承載了轉發、負載均衡、安全認證等多種必不可少的功能。接入的整個流程,包括應用架構的評審、域名的配置、其他安全機制的確認等等需要1~2個工作日。而由於我們不斷會有新的業務、新的項目,爲每一個新應用都走一遍統一接入是要消耗大量人力成本且沒有必要的。因而我們在所有Node應用之上,架設網關應用,利用Nginx根據訪問path的不同,將用戶的請求分發到不同的應用上。網關本身作爲CDN回源的源站。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"CDN邊緣節點"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CDN可以將源站上的資源緩存到距離客戶端最近的CDN節點上。當用戶訪問該靜態資源時,直接從緩存中獲取,避免通過較長的鏈路回源,提高訪問效率。這個過程被稱爲「CDN加速」。"}]},{"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":"除了資源加速之外,阿里雲的CDN節點還提供了基於Serverless的邊緣計算能力,簡單來說,就是除了緩存靜態文件,CDN還可以作爲JavaScript腳本的運行環境。這讓Node服務的穩定性還沒有達到業務的要求時,前端工程師可以通過在CDN節點上部署JavaScript代碼,讓CDN節點完成一定的功能。CDN邊緣程序、邊緣緩存和源站的鏈路如下圖。"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/61\/61d8bae3fc634f99b71016df9ff43e3c.png","alt":"圖片","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":"對於SSR來說,CDN邊緣程序實現的功能非常簡單:當獲取SSR頁面成功時(status 200),將直出的頁面返回給用戶,否則訪問降級頁面CSR的地址,保證用戶永遠能夠看到正確的頁面。我們將CDN緩存週期設置爲5分鐘。緩存生效時,用戶訪問鏈路爲上圖紫色部分所示,緩存失效時,爲紅色鏈路。"}]},{"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":"自此,我們就解釋了開始時3個問題的前2個:怎麼保障Node應用穩定性,以及怎麼進行頁面降級。"}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"CSR鏈路"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CSR降級鏈路雖然不經常被用戶覆蓋到,但對於整個方案來說仍然很重要。用戶訪問CSR頁面時,首先從air源站獲取到html頁面(此時頁面中沒有任何首屏內容),然後執行html頁面中引入的JS腳本,調用獲取首屏數據的mtop接口(阿里彈外異步請求數據獲取接口)進行頁面渲染。因而對於CSR鏈路的實現,我們只需要關心兩件事情:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"靜態資源文件需要構建並推送到air。"}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"需要有在Web端可以被調用的Ajax接口(在阿里一般被稱爲mtop接口)。"}]}]}]},{"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":"對於第一點,我們可以在應用構建和發佈的時候,直接將只有layout內容的HTML模板發佈。對於第二點,我們可以複用SSR鏈路時開發的首屏數據接口,直接將其對接集團對外的網關,無需二次開發,如下圖所示。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/82\/822d32d5043f119d680baa4c2a1202d6.png","alt":"圖片","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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"由此可以讓整個鏈路全部由前端同學完成,無需像傳統CSR頁面開發那樣由服務端同學提供mtop接口。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"實現SSR鏈路和CSR鏈路之後,我們已經滿足了3個關鍵要素中的前2個:"}]},{"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","marks":[{"type":"strong"}],"text":"FMP時間縮短:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SSR鏈路中,用戶訪問到首屏有效內容的時間爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• CDN緩存命中時:HTML頁面 RT。"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• CDN緩存未命中時:Node服務直出頁面執行時間 + HTML頁面RT"}]},{"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":"CSR鏈路中,用戶訪問到首屏內容的時間爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"• HTML頁面RT + JS文件下載&執行時間 + 首屏mtop接口 RT + 首屏內容DOM內容生成時間"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線上數據爲:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/99\/99dc6b4306130f62dccfbc4e68995872.png","alt":"圖片","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}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong"}],"text":"穩定性高、運維成本低:"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"應用之間的研發、發佈及運行的隔離,使得每個研發同學不需要擔心自己應用的穩定性會受其他應用的影響;CDN對請求的緩存,使得即使緩存時間只有1分鐘,Node應用也只在每60s才被CDN回源一次,大大降低了流量高峯時對Node服務穩定性的考驗;以及CDN邊緣程序的自動降級邏輯,使得SSR鏈路失敗時,應用自動降級爲CSR模式,不影響用戶對頁面的訪問,不需要研發同學手動運維。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"應用結構"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們在應用結構的設計上,儘可能降低了前端同學研發的成本。當CSR降級鏈路與請求轉發的功能全部發布之後,每次前端新增一個新的項目,前端開發同學只需要根據應用模板開發相應的業務功能,無需關心穩定性保障鏈路。並且只需要開發一份前端源碼,便可以構建出用於不同場景的產物。我們的應用結構設計如下圖所示:"}]},{"type":"image","attrs":{"src":"https:\/\/static001.geekbang.org\/infoq\/a0\/a0bbdaa41c18867d389dfa8cdf7ce4f6.png","alt":"圖片","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層接口在發佈時會被部署到Serverless服務市場。網關的對接等功能均可以通過配置的方式直接完成。"}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"後續"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"可灰度、可監控、可回滾是前端應用穩定性保障的三要素,SSR應用也不例外。目前閒魚的SSR應用已經實現了可回滾與可監控,接下來我們將爲它設計可靠性強、操作方便的灰度方案。"}]},{"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":"本文轉載自閒魚技術(ID:XYtech_Alibaba)"}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"原文鏈接:"},{"type":"link","attrs":{"href":"https:\/\/mp.weixin.qq.com\/s\/np4HOb5fJyuS9qPsSKtQpQ","title":"xxx","type":null},"content":[{"type":"text","text":"閒魚源碼頁面SSR最佳實踐"}]}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章