场景还原
2019/12/28晚19:05左右,突然被拉进微信群。放出一段微信聊天记录,公司(国内某知名的互联网金融公司)高管使用app时发现公司app中活动推广页面打开缓慢,20+s才打开,产品,市场部老大被质问。此时问题来到了我们开发组内。
收到问题后,组长和其他运营人员等其他人员,用手机访问同一个活动推广页,都可以1.5s内打开页面。
问题设想
先简单介绍下项目结构和背景。
该项目是一个配置项目,姑且称之为"活动配置项目",该项目分为一个配置平台和一个H5展示平台,在配置平台可以配置H5页面的展示图片,按钮定位,轮播图等等,通过生成一个结构化的json文件,上传到cdn。H5展示平台通过ajax获取cdn的json文件,同时解析JSon文件,并渲染出对应的页面。同时需要对接公司三大平台app,所有内部有很多和app进行交互的JS文件,还有各平台特有的统计JS。由于公司内三大平台基本相互独立,在技术层面没有对native的一些能力做统一,导致一些JS的功能存在冗余,但是又不得不引入。
"活动配置项目"配置生成的主要有两部分内容,第一部分:描述的配置JSon文件,第二部分:项目中用到的图片。两者均存放于cdn中。
由上场景还原可猜想,页面业务逻辑应该不会有问题。做以下猜想:并从上往下优先级排查
- 静态资源在某些特定网络下加载缓慢导致,包括html,JS,配置的JSon文件,以及图片资源。
- 检查前端页面代码,看是否是JS获取缓慢(阻塞)而导致页面无法进入业务中。
排查方向
第一点排查:对公司所用到的所有cdn进行测试,未发现明显异常。在cdn回源测试中,发现个别cdn站点出现504异常,后经排查,应该不是由于cdn导致缓慢。
第二点排查:服务器使用情况排查,发现当前活动部署与一台公用iis服务中,当前服务器中部署了多个服务,可能存在某些时刻服务资源争抢导致服务响应时间变长的情况。
第三点排查:发现H5展示平台对JS的引入都放在html的head或body的首部进行加载,同时,通过chromeDev tool工具,发现一共引入了20左右个JS文件。
问题定位
因为无法重现公司老总的问题,但是既然问题出现了,还是要想尽办法解决问题,进行优化的。
前端中JS在未开启async,defer时,JS的载入执行都是依照从上往下执行的。所有党20个JS同时放在首部,只需要一个JS载入缓慢,则可以导致后续的业务无法执行。这一点是必须要解决的。
同时另一点,当有多个JS同时放在首部,每个JS的下载都需要和服务器建立连接,在Chrome浏览器中建立连接的并发数在7个左右,所有后续的资源都需要等待。同时建立多个连接,都需要进行TCP的建立,握手,SSL的建立,在网络层面是很大开销。
问题定位一:对此给出策略是降低连接的建立,即减少JS的网络请求
对于是否需要把所有的JS放在首部,主要取决于业务是否需要依赖这些js。因此对所有js进行了梳理,把业务强依赖相关的js放在首部加载。而对于业务弱相关的js放在尾部引用。这里弱相关的有两种类型,第一种是类似于统计功能的,即使js载入失败,也不会导致业务页面无法出现。第二种是只会在页面的点击事件等才会用到的js。基于第二种的,还是会存在js没有加载成功,此时点击事件中业务逻辑无法完成。
问题定位二:理清强依赖js和弱依赖js,分开进行首尾分别加载
文件的大小在文件传输时间中占有很大的因素,所以对文件进行特定的处理,会有比较大影响
问题定位三:对未进行压缩的js进行压缩处理。同时在服务器开启gizp压缩,减少网络传输的字节大小
基于定位的三点问题,得到的整体优化方案是:优化cdn回源配置,将活动迁移到单独的服务器上。在前端层面理清强依赖的js放在首部,弱依赖的js放在尾部,同时对首部的js进行合并压缩处理,对尾部的js进行压缩合并出来。并且服务器开启gzip压缩
修正问题
基本确定了优化方向,剩下就是如何实现优化方案了。
- 厘清页面渲染依赖关系,将强制依赖放在首部,非依赖或弱依赖放在尾部。
- 项目前期并没有使用任何构建工具,并没有对一些js进行压缩。优化的方案是使用gulp对前置依赖js进行合并并压缩,对后置弱依赖或者非依赖的js分开打包,在引入。
- 保证页面渲染为第一要务,剩下的其他sdk初始化等操作都等最后再进行。
测试性能报告
修改前
修改前在ChromeDev Tool工具下网络的连接传输情况。如下,从图上可以看到,页面载入了20个js,从这张图反馈出来的信息除了载入js的多少,在waterfall一栏中,可以看到诸多的黄色和紫色,期表示的就是网络连接必要的,黄色为建立连接的时间,紫色则是SSL建立连接的时间。而造成这个问题最主要的原因猜测是由于Chrome建立连接的并发数只能在7个左右,多余的资源需要下载,这需要排队等待,并且可能导致连接断开,无法复用连接,在这里就已经花费了较多的时间。
修改后
修改后,js的资源从20个变为10个,将前置依赖打包成vendor.js,将app的弱依赖或者是非依赖打包成app.js。通过减少js的请求,以减低http连接的建立和ssl的建立,减少服务器的io操作。
从ChromeDev Tool中的waterfall已经不见了黄色和紫色,证明网络方面表现良好。
总结
凡是免不了要总结一下
前端的表现性能直接影响着用户的体验,毕竟是里用户最近的,前端工程师更应该站用户的角度去理解产品。话说回来,前端优化抛开前端代码层面的实现因素,影响最大的莫过于网络的因素。而网络的因素是复杂的,资源的访问取决于你的网络架构是怎么样的。
从最简单的:客户端发起请求 -> (建立连接)-> 资源服务器返回资源 -> 客户端执行并渲染。
但是,为了提升服务的可用性,网络架构会使用代理服务器
使用了代理服务:
客户端发起请求 -> (建立连接) -> 代理服务器检查资源是否有效 -> (有效) -> 资源返回 -> 客户端执行并渲染
客户端发起请求 -> (建立连接) -> 代理服务器检查资源是否有效 -> (无效) -> 访问资源服务器 -> 资源返回 -> 代理服务器缓存(继续响应客户端) -> 客户端执行并渲染
又但是,如果某些资源是存放cdn中,则资源又需要访问cdn和回源等问题
客户端发起请求 -> (寻找最近节点的cdn服务器,建立连接) -> cdn是否存在资源 -> (存在) -> 资源返回 -> 客户端执行并渲染
客户端发起请求 -> (寻找最近节点的cdn服务器,建立连接) -> cdn是否存在资源 -> (不存在) -> 回源处理 -> 资源返回 -> 客户端执行并渲染
当一个页面的包换的资源存在不一样的部署方式时,在排查问题时,就需要厘清最有可能导致问题的关键点。这当然取决于发生情况时候的具体表现。在网络方面的排查基本上就是回溯资源的访问过程。
回到最后,代码层面上的排查,是否存在影响代码执行,或者耗时长的操作。如前文提到的js载入的问题,其实在前端已经是老生常谈了,在html中,如果未对js添加defer,async,则js代码的执行依赖于前置js代码执行完成才会执行。这个则会阻塞页面的渲染,所以都将js置于</body>之前。
对于复杂业务,将业务拆分成多个模块处理是一种很好的习惯,这样有利于以后维护和需求的递增。但是这样必然导致资源文件增多,当资源过多时则可能引起浏览器并发不够而耗时。左右在开发过程中建议模块化,但是发布前还是需要将资源合并压缩处理,这里也不是绝对,主要还是要看资源的数量,建议js资源文件保持在10个以内。
除此以外,对数据源这一块,可以使用缓存,目前浏览器对localStorage支持度已经很好,关键数据可以进行存储。在ajax异步成功前展示缓存数据。当然对一些数据准确性要求很高的,通过一些loading明确提示其实也是不错的选择。
结束 2020-01-01 一眨眼,已经2020了。
老许,你要老婆不要。只要你开金口,今晚就给你送来。—— 《牧马人》