️This article has been over 2 years since the last update.
随着业务的增加,客户对IT系统的前端速度不太满意,希望通过改造提高速度。
本文场景是内网企业级应用(SQL多,表格多,图片少)的首屏速度加载优化
1. Backgroud
随着项目业务越做越大,前端也越来越臃肿。经过分析,有如下质量问题
- 项目本身有历史债务,由于人员变化导致很多没用到的js不敢删了
- 由于项目是自有人员带外包的模式,导致js代码质量难以控制
- 研发人员以Java后端为主,本次优化不希望提高研发的学习成本
2. Requirement
目前项目首页速度已经高达5s,希望通过前后端拉通将速度降低到2.5s(含各种复杂的查询)
3. Action
众所周知,让界面加快无非几种方法
- 压缩js/css为单个文件
- 开启gzip/缓存
- 使用asm.js /Worker 线程
- 通过组件化分批按需加载js/html(比如Angular中的component实现企业级SPA应用)
- 后端接口的SQL优化/Redis缓存/Sleuth记录等
3.1. 压缩JS/CSS
虽然网上有很多基于NodeJS的压缩项目,但是我个人更偏好在Java端使用纯maven实现。最终选择了基于谷歌ClosureCompiler
的第三方minify-maven-plugin
项目
从Demo开始学习
开源项目的特点就是文档不是很全,需要自己去找Demo或者看源码,毕竟纯情怀做高质量开源是一件吃力的事情,因此
导入与使用Demo方法
1 | git clone https://github.com/samaxes/minify-maven-plugin.git |
如何给maven plugin 打断点
打开IDEA导入刚刚项目,注意demo项目也要导入。
进入MinifyMojo.java
文件,对感兴趣的位置打上断点
然后点击maven project
,选择Minify Maven Plugin Demo
,在生命周期中的package
右键选中debug,这时就可以发现断点进入了,读者就可以自行分析代码了。
这个也是快速阅读开源项目的一个技巧,通过断点降低陷入文档的时间
插件配置
maven配置中的所有配置可以参考MinifyMojo
这个对象,下面是我的定制
1 | <profiles> |
外部json(src/minify/static-bundles.json)配置,注意这个要注意文件的前后依赖性(比如JQuery插件不能优先加载),因此不要使用通配符偷懒
1 | { |
如果你希望手动打包的话,那么可以在Terminal中运行运行
1 | # -P minify表示激活了minify的配置,如果你不要压缩,那么可以去掉它以减少打包时间 |
如果你在IDEA下希望失去焦点后能够自动刷新打包,可以在Maven Projects
选项中选择
1 | Plugins -> minify -> minify:minify(Right Click) -> Execute Before build |
这样你就可以聚焦业务,不用折腾打包流程了。
3.2. 降低传输数据量
请求侧:降低Header信息量
降低Cookies的payload
由于很多企业级项目的Cookies非常多,而这些Cookies只要是在同一个域名下,浏览器将始终拼砖发送,因此冗余的Cookies可以考虑干掉,或者切换到x-auth-token
这种更安全的实现。
为静态文件使用不同的域名
当普通的图片与业务均共享使用同一个域名时,浏览器请求图片等资源时也会带上Cookies,Token等Header信息,导致请求Payload耗时变高。此问题除了显性配置set-cookies
,还有使用CDN域名规避的方法,举个StackOverflow的例子
1 | API业务: stackoverflow.com |
返回侧:开启Gzip并配置缓存
此处有两种方法,一种是配置在Nginx中存放静态文件,还有一种是直接配置tomcat
Nginx配置方法
这个网上很多了,详见serving-static-content。你可以通过查看js返回的Header中是否有Server: Nginx
来判断是否生效,一般来说压缩级别配置到5就差不多可以了
Tomcat配置方法
同样网上很多,可以参考这里
1 | <Connector port="8575" connectionTimeout="20000" redirectPort="8443" |
上面两个方法二选一即可,通过开启Gzip可以将文件压缩到1/3甚至1/4
3.3. 按需加载
按需加载就是客户点进业务详情页面时再去加载html/js,一般常见的单页应用(Single Page Application)均支持按需加载。在本项目中选用了成熟的AngularJS + UI-Router的方案,可以参考这里。下面讲两个常见问题
项目白屏时间变长(400ms)
由于浏览器的主线程是单线程,因此只能使用异步API交给Worker线程池来实现多线程加载。但是有些位置是很难优化的,比如UI-Router是依赖于AngularJS,那么必须在AngularJS初始化完成后才能加载
1 | ParseDOM -> AngularJS.inject() -> AngularJS.eval() -> AngularJS.apply() -> UI-Router |
由于其他JS与AngularJS本身需要初始化,因此这里的UI-Router以及需要加载的界面只有等待AngularJS初始化完成才能进行请求,可以使用Chrome的Performance工具进行分析调优。
对策如下
- 升级支持预编译生成静态网站的框架,比如Angular/React,前提是项目成员学习跟得上
- 降低AngularJS双向绑定的对象,移除主页面的冗余依赖注入对象
- 使用假CSS实现视觉效果,避免一直白屏
如何压缩按需加载的静态文件
此需求在网上尚未找到一个全自动的方案,因为JS里的URL是需要替换的,目前正在研究中,目前思路是基于分析$State
的AST对象
- 分析AST对象自动生成minify需要的json文件,手动维护一下
- 分析AST对象将css/js内联到html中
3.4. 后端优化
后端优化是一个很宽泛的话题,以Tomcat为例,常见的调用栈如下
1 | Tomcat -> filterChain -> SpringMVC -> Inteceptors -> Controller -> Service(RPC/SQL) |
常见瓶颈如下
过滤器(FilterChain, Inteceptors, AOP)
此处包括Tomcat中的过滤器,SpringMVC中的拦截器,以及Controller中暗藏的AOP
- 假如在Session过滤器中拦截了js的请求,很可能导致下载一个简单的js都需要去请求Redis => 过滤器不要偷懒使用
/
进行全局过滤,而只用把RESTful请求过滤即可 - 在访问记录等过滤器中使用了插入SQL的方式记录导致阻塞 => 应该使用MQ替换掉阻塞任务
- 至于AOP,我个人认为是一种反模式,如果缺乏自动提示的IDE,还真很难定位出AOP的位置
业务优化
对于业务处理来说,一般就是查表或者调用微服务
- 数据库优化是一个冗长的议题,如果你使用Mybatis,可以参考我写的Gitbook相关文档
- 微服务优化一般来说只能优化传输语法层面,比如将JSON换成二进制通信,更多可以使用Sleuth进行分析,可以参考我写的Gitbook中Eureka的相关文档
- 对于频繁调用的可以考虑使用Redis/Elastic进行缓存计算结果
4. Summary
通过上述改造,项目终于又快起来了,说白了优化只是一个苦力活,这里工作应该尽可能包出去。
5. 附录
5.1. 如何区分生产环境
在本地开发时,一般开发者喜欢分散开的js文件,而上线后需要打包成一个单独的js文件,那么如何实现呢?
- IDEA自动打包
最简单的操作就是使用上文的方法自动打包,这样就不用区分各种环境,缺点是打包速度比较慢。
- 纯前台实现
1 | <script> |
这种方法的缺点就是很丑,但是js速度不会损失(如果是css的话肯定就慢了)
- 后端模版引擎实现
同上,比如将当前的spring.profile.active
作为上下文传到前台即可,这样看到渲染后的界面比较赏心悦目
5.2. Chrome的Audio工具
虽然网上有很多关于前端优化的文档,但是Chrome自带的这个工具已经非常好用了,通过将分数提高即可实现优化。