打造高可靠與高性能的React同構解決方案

前言

  • 隨着React的興起, 結合Node直出的性能優點和React的組件化,React同構已然成爲趨勢之一。享受技術福利的同時,直面技術挑戰,在複雜場景下,挑戰10倍以上極致的性能優化。

什麼是同構?

  • 一套代碼既能夠在服務端運行又能夠在客戶端運行,這就是同構應用。簡而言之, 就是服務端直出和客戶端渲染的組合, 可以充分結合二者的優點,並有效避免二者的不足。

爲何同構?

  • 性能: 經過Node直出, 將傳統的三次串行http請求簡化成一次http請求,下降首屏渲染時間
  • SEO: 服務端渲染對搜索引擎的爬取有着自然的優點,雖然阿里電商體系對SEO需求並不強,但隨着國際化的推動, 愈來愈多的國際業務加入阿里你們庭,不少的業務依賴Google等搜索引擎的流量導入,好比Lazada.
  • 兼容性: 部分展現類頁面可以有效規避客戶端兼容性問題,好比白屏。

性能數據

性能是一個綜合性的問題, 不能簡單地斷言同構應用必定比非同構應用性能好,只能說合適的場景加上合理的運用,同構應用確實能帶來必定的性能提高, 先來看一個線上的案例。javascript

isomorphic

一般來講,網絡情況越差,同構的優點越明顯,下圖是在不一樣網絡情況下首屏渲染時間的一組對比css

isomorphic

線上案例

  • 近兩年,不管是業界仍是阿里內部都涌現了大量同構實踐, 業界比較有影響力的包括Facebook, Quora, Medium, Twitter, Airbnb, Walmart、手Q以及QQ興趣部落等
  • 阿里內部也有大量的應用,僅列舉部分beidou開發組作過技術支持的項目
  • 阿里雲 – 大數據地產
  • 釘釘 – 企業主頁
  • 釘釘 – 釘釘日誌和審批模板市場
  • 菜鳥 – 物流大市場
  • 雲零售 – 店掌櫃
  • Lazada – PDP
  • 國際事業部 – AGLA
  • AILab – 行業解決方案
  • AILab – 智能硬件平臺
  • AILab – AliGenie開放平臺
  • AILab – AR官網
  • ICBU – ICBU店鋪
  • 業務平臺 – 門店評價
  • 國際UED – 數據運營
  • 國際UED – 知之
  • 國際UED – 探花
  • 國際UED – Nuke官網及過程管理
  • 國際UED – 會議記錄,實時翻譯
  • 國際UED – LBS數據地圖
  • 國際UED – 數探
  • 國際UED – 微策
  • 國際UED – shuttle
  • 國際UED – fie portal

業界生態

  • react-server: React服務端渲染框架
  • next.js: 輕量級的同構框架
  • beidou: 阿里本身的同構框架,基於eggjs, 定位是企業級同構框架

除了開源框架,底層方面React16重構了SSR, react-router提供了更加友好的SSR支持等等, 從某種程度上來講,同構也是一種趨勢,至少是方向之一。html

思考 與 實現

同構的出發點不是 「爲了作同構,因此作了」, 而是迴歸業務,去解決業務場景中SEO、首屏性能、用戶體驗 等問題,驅動咱們去尋找可用的解決方案。在這樣的場景下,除了同構自己,咱們還須要考慮的是:前端

  • 高性能的 Node Server
  • 可靠的 同構渲染服務
  • 可控的 運維成本
  • 可複用的 解決方案

簡單概括就是, 咱們須要一個 企業級的同構渲染解決方案。java

咱們是怎麼作的?node

基於 eggjs 加入可拔插的同構能力

  • beidou-plugin-react
    做爲原有MVC架構中, view 層的替換, 使用 React 組件做爲視圖層模板, 能夠直接渲染 React Component 並輸出給客戶端

     

  • beidou-plugin-webpack
    集成 Webpack 到框架中, 在開發階段, 提供代碼的編譯和打包服務react

  • beidou-plugin-isomorphic
    服務端的 React 運行時: babel-register
    polyfill 注入: 環境變量, BOM等
    非js文件解析: css, images, fonts…webpack

  • 服務端支持css modules
  • 自動路由: 純靜態頁面無需編寫任何服務端代碼,像寫純前端頁面同樣簡單

這裏再也不贅述具體如何實現,有興趣的讀者能夠閱讀咱們的開源同構框架beidou — https://github.com/alibaba/beidougit

熱點問題

任何一種技術都有其適用場景和侷限性, 同構也不例外,如下試舉一二,以作拋磚引玉.github

  • 內存泄漏
  • 性能瓶頸

內存泄漏不是同構應用所特有的,理論上全部服務端應用均可能內存泄漏,但同構應用是「高危羣體」, 具體如何解決請參考本人的《Node應用內存泄漏分析方法論與實戰》, 接下來重點剖析下性能優化。

極致的性能優化

前面也提到了,同構應用並不必定就比非同構應用性能好,影響性能的因素實在太多了,再來看一組數據

react15 performance

上圖是基於Node v8.9.1 和 React@15.5.4, 開4個進程採集到的數據, X軸是最終生成頁面節點數量,Y軸紅色的線表示RT(包括渲染時間和網絡時間), 綠色的柱子表示QPS. 能夠看出來:
* 隨着頁面節點的增多渲染時間可能變得很長,QPS降低很是迅速。在頁面節點超過3000左右的時候,QPS接近個位數了,並且實際頁面中可能包含較複雜的邏輯以及不友好的寫法,狀況可能會更糟。

順帶提一下, 筆者採樣了淘寶首頁 和淘寶某詳情頁以及Lazada某詳情頁,頁面節點數分別是2620、2467和3701. 大部分狀況下,頁面節點數低於1000, 好比菜鳥物流市場首頁看起來內容很多,其實節點數是775.

那針對3000節點以上的頁面,咱們該怎麼作呢?筆者總結了如下策略並重點闡述其中一兩點:
* 採用編譯後的React版本: 根據Sasha Aickin的博客,React15在Node四、Node六、Node8下,採用編譯後的版本性能相比未編譯版本分別提高了2.36倍、3倍、3.85倍
* 模塊拆分: 模塊拆分有利於併發渲染,目前ICBU店鋪裝修採用的就是這種方式
* 模塊級別緩存: 頁面中某些模塊實際上是很適合緩存的,好比Lazada詳情頁中節點數雖然高達3701, 但其實頁頭部分就佔比55.5%,頁尾佔比3.5%,而頁頭頁尾是常年不變的.
* 組件級緩存: 最小粒度的緩存單位了,性能提高依賴於緩存的範圍和命中率,運用得當,可能帶來很是大的性能提高。參考walmartlabs
* 採用hsf代替http對外提供服務: hsf的網絡消耗遠低於http, 在店鋪同構實踐中,改用hsf, java端調用Node端的耗時縮短了一半.
* 部分模塊客戶端渲染(對SEO無用的部分): 直接下降SSR部分的複雜度
* 智能降級: 當流量暴增,接近或超過閾值時,會直接致使服務的RT快速上升。能夠實時監測CPU和內存的使用率,超過必定的比例自動降級爲客戶端渲染,下降服務端壓力,CPU和內存恢復常態時,自動切回服務端渲染。
* 採用Node8: 一樣在店鋪實踐中,採用Node8相比Node6, 渲染時間從28ms下降到了18ms, 提高幅度爲36%.
* 採用最新版React16: facebook官方數據, 在Node8下,React16相比編譯後的react15仍有3.8倍提高,相比未編譯的React15更是有數量級的提高。

組件級緩存

若是說性能優化有」萬能」的招式,那必定是緩存, 從Nigix緩存到模塊級緩存到組件級緩存,其中最讓人興奮的就是組件級緩存,讓咱們一塊兒來看看如何實現
* 攔截React的渲染邏輯,業界主要有三種實現方式
* Fork一份React, 暴力加入緩存邏輯, 表明庫是react-dom-stream, 雖然這個庫的人氣很高,但筆者仍是反對這種實現方式的。
* 經過require hook攔截instantiateReactComponent的載入並注入緩存邏輯,參考react-ssr-optimization
* 擴展ReactCompositeComponent的mountComponent方法,參考electrode-react-ssr-cachin
* 注入緩存邏輯, 代碼以下

const ReactCompositeComponent = require("react/lib/ReactCompositeComponent"); ReactCompositeComponent.Mixin._mountComponent = ReactCompositeComponent.Mixin.mountComponent; ReactCompositeComponent.Mixin.mountComponent = function(rootID, transaction, context) { const hashKey = generateHashKey(this._currentElement.props); if (cacheStorage.hasEntry(hashKey)) { // 命中緩存則直接返回緩存結果 return cacheStorage.getEntry(hashKey); } else { // 若未命中,則調用react的mountComponent渲染組件,並緩存結果 const html = this._mountComponent(rootID, transaction, context); cacheStorage.addEntry(hashKey, html); return html; } }; 
  • 設置最大緩存和緩存更新策略
lruCacheSettings: {
max: 500, // The maximum size of the cache maxAge: 1000 * 5 // The maximum age in milliseconds } 

上述緩存邏輯是基於屬性的,能覆蓋大部分的應用場景,但有一個要求,屬性值必須可枚舉且可選項不多. 請看下面的場景。
items

淘寶某頁面上有大量的商品,而淘寶的商品又何止百萬,就算某個被緩存,下次被命中的可能性依然微乎其微。那如何解決這個問題?聰明的讀者可能已經看出來了,雖然每一個商品最終渲染的結果變幻無窮,但結構始終是一致的,所以結構是能夠緩存的。

template

要實現結構的緩存,須要在上述邏輯上額外新增三步。
* 生成中間結構:
* 以組件<Price>${price}</Price>爲例,將變量price以佔位符${price}代替set(price, "${price}"), 再調用react原生的mountComponent方法則能夠生成中間結構<div>${price}</div
* 緩存中間結構
* 生成最終組件

以上就是組件級緩存的實現方式, 特別要提醒的是緩存是把雙刃劍,運用不當可能會引起內存泄漏以及數據的不一致。

React16 SSR

  • FB在9.26發佈了React16正式版,以前萬衆期待的SSR性能提高沒有讓你們失望, 引用React核心開發Sasha Aickin的對比圖
    react16

筆者拿以前的應用升級到React16, 對比下3909節點,RT從295ms降到了51ms, QPS從9提高到了44, 提高很是明顯。

react16

實戰

接下來經過一個例子,展現如何一步步地提高性能。
代碼倉庫 — https://github.com/alibaba/beidou/

10倍以上性能提高

  • 首先構造一個很是複雜的頁面, 頁面節點數是3342, 對比之下,淘寶首頁首屏的頁面節點數是831, 異步充分加載以後(懶加載完成),整個頁面節點數爲3049. 注: 淘寶頁面爲動態頁面,每次採樣可能會有差別。

複雜頁面

淘寶首屏

淘寶全屏

  • 初始平均渲染時間爲295.75ms(Node6.92, React15.6.2), 注: 圖中有296.50ms,317.25ms,297.25ms,295.75ms四個平均值,是由於開啓了四個進程,採樣最後一個,下同。

初始渲染時間

babel性能加速插件

  • 採用Node8.9.1(或更新版本)平均渲染時間爲207ms

Node8

  • 採用production模式平均渲染時間爲81.75ms

production mode

  • 部份內容客戶端渲染,平均渲染時間爲44.63ms

part csr

  • 部份內容組件級別cache,平均渲染時間爲22.65ms

part cache

  • 採用React16(或更新版本),平均渲染時間爲5.17ms

react16

  • 結合React16和部分客戶端渲染,平均渲染時間爲2.68ms

react16+csr

至此,服務端渲染時間已經最初的295.75ms下降到了2.68ms,提高了超過100倍。

更多性能策略

其實除了上述應用的策略,還有其它的策略,好比
* 採用Async, 有數據稱性能提高30%, 筆者試了下,未見明顯提高。應該是通過了babel的編譯,最終沒有發揮出Async的優點,這是由於beidou框架在服務端要支持import等ES6的寫法以及支持React的JSX語法。其實也很是簡單,直接縮小babel的編譯範圍,在beidou框架中是能夠本身定義的。
* 下降React組件的嵌套層級。試驗數據,一樣的頁面節點數,服務端渲染時間和組件的嵌套層級是線性正相關的。
* 熱點緩存

萬變不離其宗

借用《功夫》中的一句經典臺詞天下武功,無堅不破,惟快不破,一樣的,
隨着時間的推移,上面這些策略策略早晚會被破,好比react16 ssr重構以後,以前的組件級別緩存邏輯再也不有效。
另外,可能因爲架構設計/技術選型根本就使不上勁,好比react16是今年9月26才正式發版,不少第三方組件還沒來得及升級,若是應用中有些組件強依賴於react15或者更早的版本,可能根本就無法利用react16的性能優點。
那麼有沒有一種萬能的辦法,可以作到惟快不破呢?

答案是: 有的。 只有掌握了方法論,才能在不斷變化中,找到適合本身應用的性能優化策略。具體的方法論,敬請期待接下來文章《惟快不破,讓nodejs再快一點》

相關文章
相關標籤/搜索