本文发表于 北斗同构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再快一点》