本文發表於 北斗同構github, 轉載請註明出處
注: 本文爲第12屆D2前端技術論壇《打造高可靠與高性能的React同構解決方案》分享內容,已通過數據脫敏處理。css
性能是一個綜合性的問題, 不能簡單地斷言同構應用必定比非同構應用性能好,只能說合適的場景加上合理的運用,同構應用確實能帶來必定的性能提高, 先來看一個線上的案例。html
一般來講,網絡情況越差,同構的優點越明顯,下圖是在不一樣網絡情況下首屏渲染時間的一組對比前端
阿里內部也有大量的應用,僅列舉部分beidou開發組作過技術支持的項目java
除了開源框架,底層方面React16重構了SSR, react-router提供了更加友好的SSR支持等等, 從某種程度上來講,同構也是一種趨勢,至少是方向之一。node
同構的出發點不是 「爲了作同構,因此作了」, 而是迴歸業務,去解決業務場景中SEO、首屏性能、用戶體驗 等問題,驅動咱們去尋找可用的解決方案。在這樣的場景下,除了同構自己,咱們還須要考慮的是:react
簡單概括就是, 咱們須要一個 企業級的同構渲染解決方案。webpack
咱們是怎麼作的?git
這裏再也不贅述具體如何實現,有興趣的讀者能夠閱讀咱們的開源同構框架beidou -- https://github.com/alibaba/beidougithub
任何一種技術都有其適用場景和侷限性, 同構也不例外,如下試舉一二,以作拋磚引玉.web
內存泄漏不是同構應用所特有的,理論上全部服務端應用均可能內存泄漏,但同構應用是「高危羣體」, 具體如何解決請參考本人的《Node應用內存泄漏分析方法論與實戰》, 接下來重點剖析下性能優化。
前面也提到了,同構應用並不必定就比非同構應用性能好,影響性能的因素實在太多了,再來看一組數據
上圖是基於Node v8.9.1 和 React@15.5.4, 開4個進程採集到的數據, X軸是最終生成頁面節點數量,Y軸紅色的線表示RT(包括渲染時間和網絡時間), 綠色的柱子表示QPS. 能夠看出來:
順帶提一下, 筆者採樣了淘寶首頁 和淘寶某詳情頁以及Lazada某詳情頁,頁面節點數分別是2620、2467和3701. 大部分狀況下,頁面節點數低於1000, 好比菜鳥物流市場首頁看起來內容很多,其實節點數是775.
那針對3000節點以上的頁面,咱們該怎麼作呢?筆者總結了如下策略並重點闡述其中一兩點:
若是說性能優化有"萬能"的招式,那必定是緩存, 從Nigix緩存到模塊級緩存到組件級緩存,其中最讓人興奮的就是組件級緩存,讓咱們一塊兒來看看如何實現
攔截React的渲染邏輯,業界主要有三種實現方式
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 }
上述緩存邏輯是基於屬性的,能覆蓋大部分的應用場景,但有一個要求,屬性值必須可枚舉且可選項不多. 請看下面的場景。
淘寶某頁面上有大量的商品,而淘寶的商品又何止百萬,就算某個被緩存,下次被命中的可能性依然微乎其微。那如何解決這個問題?聰明的讀者可能已經看出來了,雖然每一個商品最終渲染的結果變幻無窮,但結構始終是一致的,所以結構是能夠緩存的。
要實現結構的緩存,須要在上述邏輯上額外新增三步。
生成中間結構:
<Price>${price}</Price>
爲例,將變量price以佔位符${price}
代替set(price, "${price}")
, 再調用react原生的mountComponent方法則能夠生成中間結構<div>${price}</div
以上就是組件級緩存的實現方式, 特別要提醒的是緩存是把雙刃劍,運用不當可能會引起內存泄漏以及數據的不一致。
筆者拿以前的應用升級到React16, 對比下3909節點,RT從295ms降到了51ms, QPS從9提高到了44, 提高很是明顯。
接下來經過一個例子,展現如何一步步地提高性能。
代碼倉庫 -- https://github.com/alibaba/beidou/
295.75ms
(Node6.92, React15.6.2), 注: 圖中有296.50ms
,317.25ms
,297.25ms
,295.75ms
四個平均值,是由於開啓了四個進程,採樣最後一個,下同。219.00ms
207ms
production
模式平均渲染時間爲81.75ms
44.63ms
22.65ms
5.17ms
2.68ms
至此,服務端渲染時間已經最初的295.75ms
下降到了2.68ms
,提高了超過100倍。
其實除了上述應用的策略,還有其它的策略,好比
Async
, 有數據稱性能提高30%, 筆者試了下,未見明顯提高。應該是通過了babel的編譯,最終沒有發揮出Async
的優點,這是由於beidou框架在服務端要支持import
等ES6的寫法以及支持React的JSX語法
。其實也很是簡單,直接縮小babel
的編譯範圍,在beidou框架中是能夠本身定義的。...
借用《功夫》中的一句經典臺詞天下武功,無堅不破,惟快不破
,一樣的,
隨着時間的推移,上面這些策略策略早晚會被破
,好比react16 ssr重構以後,以前的組件級別緩存邏輯再也不有效。
另外,可能因爲架構設計/技術選型根本就使不上勁,好比react16是今年9月26才正式發版,不少第三方組件還沒來得及升級,若是應用中有些組件強依賴於react15或者更早的版本,可能根本就無法利用react16的性能優點。
那麼有沒有一種萬能的辦法
,可以作到惟快不破
呢?
答案是: 有的。 只有掌握了方法論,才能在不斷變化中,找到適合本身應用的性能優化策略。
具體的方法論,請參考本人的另一篇文章《惟快不破,讓nodejs再快一點》