站点体验:欢迎您
Discuz !Q作为一个对外开源的私域流量建站工具,在加载性能优化上会受到很多限制。因为Discuz !Q部署形态是存在于用户自己的服务器中,网络情况,服务器性能等,都有很多未知因素,经过团队共同努力出谋划策,最终成效不错,借此机会记录一下极端情况下的加载性能优化思路。
Discuz !Q作为一个对外开源的私域流量建站工具,用户通过服务器安装Discuz !Q代码进行部署。因此每个用户的服务器配置以及网络带宽都不一样。因为是私域流量的关系,因此客户(站长)所使用的服务器和网络配置一般都比较低。经过收集用户的信息,发现大量用户的机器配置为1C2G,网络带宽为1Mbps。首次无缓存加载基本在20秒以上。
基于以上情况,Discuz !Q组织优化小组,对Discuz !Q极端情况下进行性能优化。
Discuz !Q使用React技术栈,同时考虑后续提供SEO优化,所以搭配Next进行项目构建。使用mobx进行数据管理。Discuz !Q为了减少pc端和h5端分别开发两套系统,所以在架构上做了一定设计。因为考虑到业务形态一致,但是可能会存在部分的交互和布局形式不一,所以在运行时会对当前运行环境进行判断,从而确定需要渲染那个页面组件。从而达到最大复用逻辑和组件。
经过优化小组的定位发现以下问题:
经过使用webpack-bundle-analyzer发现当前构建的包体积中每个页面的资源包存在很多没有引用的资源。经过排查发现由于在使用以下的引用方式。
import { a, b } from 'xxxx';
这种引用方式会导致webpack无法很好的去做treeshaking。导致会引入很多没有使用的代码块。经过统计,发现渲染需要用到资源经过gzip后高达1.26MB
结论:es6引入代码块方式,会导致webpack无法很好的进行treeshaking,导致每个页面分包中存在大量无用代码,增加代码体积,导致资源文件加载时间长,从而影响页面的加载性能。
相信很多同学都知道,http1.x和2.x对前端静态资源加载的优化方式是不一样。在NextJS构建时,默认的分包策略会对代码的粒度拆分的比较细,所以导致最终资源输出时,但是因为用户服务器的带宽不高,导致如果使用http2.0的优化策略,会导致每个页面访问时,资源并发多,带宽被平分。导致每个下载进程能获取到的带宽极低。
结论:因为用户的服务器带宽比较低,导致大量分包利用多路复用特性会导致每个下载进程的带宽资源被平分,导致每个资源加载很慢,从而影响页面的加载性能。
通过对加载资源分析,发现在html到达关键的资源加载之前,存在一些统计脚本的阻塞加载,以及一些外部资源(如:验证码,编辑器)等资源阻塞,导致关键的渲染资源没有第一时间触发加载。
结论:html解析过程中,存在非首屏需要的资源进行加载,并形成阻塞,导致关键渲染路径过长,从而影响页面的加载性能
导致页面加载慢的问题基本上已经定位出来,那么接下来需要针对用户的部署环境,做针对性的优化。
对于es6的引用方式,webpack处理不友好,一开始提出要不约定引入方式,不要使用以下方式
import { a, b } from 'xxxx';
改为
import a from 'xxxx/dist/a'; import b from 'xxxx/dist/b';
但是这种需要大量的成本,并且需要通过约定的方式,无法确保后续是否会出现人为原因编写错误导致包体积再次上升。通过调研,可以通过babel的插件babel-plugin-import实现等价的效果,并且在编译时自动转换。
最终通过引入babel-plugin-import对所有引入做插接,让webpack能正确treeshaking。最终包体积从1.26MB减低到958KB,减少约24%的体积。
思考:
针对以上思考,要在不能使用CDN,并且在1Mbps带宽下进行优化,那么我们就需要调整一个适当的分包策略,尽快降低服务器对于每一次访问的资源并发数量。
通过分析NextJS的源码,我们看到NextJS默认的分包模式是对http2.0的版本做了优化。https://github.com/vercel/next.js/blob/62a4de9f8c23fe753650b78d4bd892d0081bc6b7/packages/next/build/webpack-config.ts
经过多次尝试,目前分包的配置如下:
{ chunks: 'all', // 异步加载的内容也会进行拆包处理 minChunks: 2, maxAsyncRequests: 8, // 并发同步加载数量,相当于拆包数量 minSize: 102400, maxSize: 102400 }
通过配置webpack的分包模式,尽可能将包的体积统一在100kb左右(并不能准确限定在100kb,因为受到代码块的影响,可能会超出或不足)。同时将并发请求的包数量控制在8个以下。从而减低服务器带宽平分的影响。并且也能利用http协议的特性进行并发请求。
关键渲染路径中存在非必要资源
通过分析渲染路径,对统计代码全部改为script标签动态创建加载代码,实现后加载,让统计代码不再阻塞渲染。
经过以上优化,我们在1C2G 1Mbps的机器上进行测试,测试方式以10次取中位数进行统计。
启动总体积(kb) | domready(ms) | onload(ms) | 首次渲染(ms) | 并发极限 | 最快打开速度(单人) | 最快打开速度(多人) | |
---|---|---|---|---|---|---|---|
未优化 | 1264 | 12.054 | 32.97 | 28.5109 | 1 | 15 | 50 |
一期优化 | 958 | 4.83 | 9.16 | 8.3508 | 5 | 10 | 40 |
成果 | -24.21% | -149.57% | -259.93% | -241.42% | +80% | -50% | -25% |
从上述成果看到,还是有比较明显的提升,但是首次渲染需要8秒感觉还是有点高。还是想再优化一下。经过思考和复盘,一期的优化总体来说属于比较通用的优化,但是感觉还是能继续优化一下的。那么我们改变一下思路,是不是可以采用分段式渲染的方式,将首屏的打开速度再提升一个档次。
通过分析整个页面的加载,发现就算是js和css等静态资源加载完毕后,还是无法渲染有价值的内容,因为并不是静态页面的关系,代码运行时需要请求后端数据才能渲染出对应的组件,提供有价值的内容给用户。既然如此,我们能不能进一步减少首屏的代码体积,将依赖后端数据渲染的内容进行懒加载,这样就能既减少首屏所需要的代码量,也可以让懒加载的代码和数据请求同时进行。进一步优化首屏速度呢。
我们主要优化红框部分的流程。
经过我们分析首页中每个模块的大小,发现首页业务代码中,帖子列表的代码体积比较大,因为帖子列表中设计很多能力:
而且整个首页高度依赖后端进行渲染,在无法提供SSR服务的前提下,而且也需要依赖js运行时获取接口数据才能正常渲染,首页中的首屏加载代码携带这些内容组件的代码将毫无意义,所以决定将红框中的组件代码全部进行懒加载处理。
通过改写代码,提供友好的加载中交互,最终的样子如下:
懒加载组件资源完成后,并且数据已经返回立即进行渲染。
一期优化我们根据服务器性能和网络带宽,对资源的加载时机,分包策略,包体积等进行一次优化,属于比较通用的优化。但是需要进一步优化时,就需要结合业务,从业务层面找优化点。二期优化方案主要是对于首屏所需要的代码优先加载,对于页面中与接口数据高度依赖的模块,统一采用懒加载的方式,可以让用户更快的看到页面的呈现。
测试方式和一期优化一致,我们在1C2G 1Mbps的机器上进行测试,测试方式以10次取中位数进行统计,最终的优化数据如下:
启动总体积(kb) | domready(ms) | onload(ms) | 首次渲染(ms) | 并发极限 | 最快打开速度(单人) | 最快打开速度(多人) | |
---|---|---|---|---|---|---|---|
未优化 | 1264 | 12.054 | 32.97 | 28.5109 | 1 | 15 | 50 |
一期优化 | 958 | 4.83 | 9.16 | 8.3508 | 5 | 10 | 40 |
二期优化 | 753.3 | 4.284 | 6.532 | 6.39 | 6 | 2.8 | 28 |
一期优化成果 | -24.21% | -149.57% | -259.93% | -241.42% | +80.00% | -50.00% | -25.00% |
二期优化成果 | -40.40% | -181.37% | -404.75% | -346.18% | +83.33% | -435.71% | -78.57% |
一般以前对于自研项目的性能优化,更多会使用CDN,或者网络带宽不会存在如此极端的情况。虽然在整体的优化方案中,并没有什么非常黑科技的技术进行优化,但是最终的效果还是非常显著的。当中用到的所有方案和思路其实网上都是一搜一大把的,但是我们需要结合实际场景,有时候常规的优化方式,在特殊的情况下,或许不是最优解。例如利用多路复用的特性,在带宽低的机器上,有时候可能会导致加载更慢。优化方案没有一成不变的方案,结合实际,灵活运用各种优化方案搭配使用,才是唯一出路。
希望大家多多提出意见或者建议,留意区留下一下你遇到过最深刻的优化经历吧。