直出是什麼?到底是怎樣的效能優化?本文將結合從在瀏覽器輸入url,到展示最終頁面的過程來對其進行一步步分析,並將在手Q web 中的實際應用實踐進行總結。
模式 1 – 前後分離
從使用者輸入 url 到展示最終頁面的過程,這種模式可簡單的分為以下 5 部分
- 使用者輸入 url,開始拉取靜態頁面
- 靜態頁面載入完成後,解析文件標籤,並開始拉取 CSS (一般 CSS 放於頭部)
- 接著拉取 JS 檔案(一般 JS 檔案放於尾部)
- 當 JS 載入完成,便開始執行 JS 內容,發出請求並拿到資料
- 將資料與資源渲染到頁面上,得到最終展示效果
具體流程圖如下
這種處理形式應該佔據大多數,然而也很容易發現一個問題就是請求數多,前後依賴大,如必須等待 JS 載入完成後執行時才會發起 資料請求,等待資料回來使用者才可以展示最終頁面,這種強依賴的關係使得整個應用的首屏渲染耗時增加不少。
模式 2 – 資料直出
資料請求在server端上提前獲取,並和html一同返回,頁面模板和資料的渲染在瀏覽器端上執行
在模式 1 中,第 1 點使用者輸入 url 時 server 端不做其他處理直接返回 html ,在第 4 點向 server 請求獲取資料。那麼,同樣都是向 server 請求獲取,如果在第 1 點中將請求資料放在 server 上,將拿到的資料拼接到 HTML 上一併返回,那麼可減少在前端頁面上的一次資料請求時間。 這就是模式 2 – 資料直出所做的事,處理方式也很簡單
- 使用者輸入 url ,在 server 返回 HTML 前去請求獲取頁面需要的資料
- 將資料拼接到 HTML 上 並 一起返回給前端 (可以插入 script 標籤將資料新增到全域性變數上,或放到某個標籤的 data 屬性中,如 <body data-serverData = ‘{list:[1,2,3]}’ >)
- 在前端的JS程式碼中判斷是否已在服務端拿到資料,直接拿該資料進行渲染頁面,不再做資料請求
具體可下面的流程圖看出這種模式下
這種模式與模式1 相比,減少了這兩種模式請求資料的耗時差距。這塊差距有多少呢?
發起一個 HTTP 的網路請求過程
1 2 3 4 5 6 7 8 9 10 |
DNS解析(100~200ms可以快取) | | 建立TCP連結 (三次握手100~200ms ) | | HTTP Request( 半個RTT ) | | HTTP Response( RTT 不確定優化空間 ) |
注: RTT 為 Round-trip time 縮寫,表示一個資料包從發出到返回所用的時間。
HTTP 請求在前後端發出,差距有多少?
由上面對 HTTP 的網路請求過程可看到建立一次完整的請求返回在耗時上明顯的,特別是外網使用者在進行 HTTP 請求時,由於網路等因素的影響,在網路連線及傳輸上將花費很多時間。而在服務端進行資料拉取,即使同樣是 HTTP 請求,由於後端之間是處於同一個內網上的,所以傳輸十分高效,這是差距來源的大頭,是優化的剛需。
模式 3 – 直出 (服務端渲染)
資料請求在server端上提前獲取,頁面模板結合資料的渲染處理也在server上完成,輸出最終 HTML
模式 2 中將依賴於JS檔案載入回來才能去發起的資料請求挪到 server 中,資料隨著 HTML 一併返回。然後等待 JS 檔案載入完成,JS 將服務端已給到的資料與HTML結合處理,生成最終的頁面文件。
資料請求能放到 server 上,對於資料與HTML結合處理也可以在server上做,從而減少等待 JS 檔案的載入時間。 這就是模式3 – 直出 (服務端渲染),主要處理如下
- server 上獲取資料並將資料與頁面模板結合,在服務端渲染成最終的 HTML
- 返回最終的 HTML 展示
可以從下圖看出,頁面的首屏展示不再需要等待 JS 檔案回來,優化減少了這塊時間
通過以上模式,將模式 1 – 常用模式中的第 3 和 4 點耗時進行了優化,那麼可以再繼續優化嗎?
在頁面文件不大情況下,可將CSS內聯到HTML中,這是優化請求量的做法。直出稍微不同的是需要考慮的是服務端最終渲染出來的文件的大小,在範圍內也可將 CSS 檔案內聯到 HTML 中。這樣的話,便優化了 CSS 的獲取時間,如下圖
小結
直出能夠將常用模式優化到剩下了一次 HTML 請求,加快首屏渲染時間,使用服務端渲染,還能夠優化前端渲染難以克服的 SEO 問題。而不管是簡單的 資料直出 或是 服務端渲染直出 都能使頁面的效能優化得到較大提高,以下將從實際應用中進行說明。
以手Q家校群的資料直出優化為例
由於專案上線時間緊,所以在第一次優化上使用了資料直出的簡單方式來優化首屏渲染時間。具體處理與 模式 2 資料直出方式 一致,與其不同的是這裡使用了由 AlloyTeam 開發的 基於KOA的玄武直出服務 來作為前端與服務端間的中間層。形式如下
使用這種中間層的方式,在專案的開發過程中依然可使用前後端分離的方式,開發完後再將頁面請求指向這個中間層服務上。中間層服務主要做了上述 模式 2 – 資料直出 中的處理
- 使用前端檔案及呼叫服務端做好的拉取資料介面
- 將資料與前端檔案結合並返回給請求來源
由於該中間層服務與具體server部署在相同的內網上,所以它們直接的資料互動是十分高效的,從而可達到 模式 2 – 資料直出 中所述的優化。
另一點,做為中間層玄武直出服務通過公司的L5負載均衡服務,完美相容直出與非直出版本,即當直出服務掛掉了,也可以順利走非直出版本,確保基本的使用者體驗,也能夠更好的支援 A/BTest。
效能資料
簡單的資料方式直出同樣迎來了較大的效能提升,手Q家校群列表頁在首屏渲染完成時間上,相比於優化前的版本,資料直出有大概 650ms 的優化,提升約 35% 的效能。
總結
在前後端沒有分離時 使用後端渲染出模板的方式是與文中所述的直出方案效果是一致的,前後端分離後淡化了這種思想,Node 的發展讓更多的前端開始做後端事情,直出的方式也越來越被重視了。
歷史的車輪滾滾向前,直出方案看似回到了服務端渲染的原點,實際上是在以前的基礎上盤旋上升。有了更多的能力,便可以有更多的思考。期待前端會越來越強大,這不,react-native也讓前端開始著手客戶端的事兒了 ~
後記
手Q家校群使用 React + Redux + Webpack架構,既然是 React,肯定不可忽略 React 同構 (服務端渲染) 關於React 同構直出的具體實踐,我將其總結在另外一篇文章上,可點選檢視 React同構直出優化總結
對於文章一開始提及的前端路由,對路由的實現原理感興趣的也可點選檢視 前端路由實現與 react-router 原始碼分析
感謝指教!