当前位置: 首页 > news >正文

有货移动Web端性能优化探索实践

在移动互联网的时代里,对于一个web站点来说,移动端的用户体验尤为重要。现代web站点的设计和开发都是以移动优先作为第一原则,我们也专门为了移动端的web站点做了相应的优化和提升。而网页的打开速度和页面的流畅度,对于用户是否长时间访问至关重要。我们在移动端的站点通过一系列的方法,最终为了快速打开页面展示网页内容,触达用户,同时能流畅的浏览网页。

\\

移动端的硬件条件,网络条件相对于桌面端,会复杂的多,设备类型多样,硬件配置参差不齐,分辨率碎片化,网络状况在移动过程中稳定性,速率都会变化,而对于一个页面到达用户的终端展示,会经过,用户发起请求,服务端接受请求,服务端处理请求,返回响应内容,在用户终端的浏览器展示内容,用户操作页面发起其他页面时间,而这个过程中任何一个环节的延迟都会造成性能瓶颈,降低用户继续访问的可能性,所以我们在服务器端,浏览器端,网络加载,多个方面做了一系列的优化工作。 

\\

WEB服务端优化

\\

有货的WEB端主要使用了nodejs,基于后端服务提供的HTTP接口服务来实现的前后端分离,这里的服务端优化主要是指在nodejs实现的web服务端进行优化。

\\

cb1cee110b1669f0d533651fdfb7c38c.png

\\

优化的目的是提升服务端的响应和并发能力,充分发挥nodejs的异步非阻塞的特性,主要从以下几个方面去优化。

\\

接口服务调用的优化

\\

对于一个页面展示的路由,要处理这个路由,可能需要调用多个接口并且进行进行界面逻辑的处理。大体过程:

\\

fac53c5be4c9556d957daa180e126863.png

\\
  • 接口合并 我们对于一个页面调用可以合并的接口,进行接口合并,减少接口调用次数,如:以商品详情页为例,商品的一些特性,可以在一个接口返回,尽可能的减少接口调用的个数,因为每次接口的处理都有网络IO,对象序列化,压缩和解压的过程。\\t
  • 接口异步调用 但是并不是所有的接口都可以合并,对于无法合并的接口,我们尽量使用node的异步非阻塞的特性,进行异步调用,同时调取多个接口,而最终的调用耗时取决于最慢的接口。
    \\t这里要说明一点:对于接口依赖,如A接口依赖B接口的返回结果,像这种情况,我们最好梳理下接口设计,减少这样的串行调用,因为这样,调用耗时是多个接口耗时的总和。\\t
  • 减少接口交互数据 返回的数据较多的情况下,会导致JSON序列化,数据批量对象处理,产生额外的性能损耗。可以做下接口返回数据结构的精简,返回必要的字段(页面会展示用到的数据)以及可以调整返回item个数。从而达到减少数据的返回消息体的大小。此外请求接口时需要gzip压缩,可以大大的减少网络传输的时间,尽管需要解压会消耗一部分CPU的时间,但是对接网络IO的损耗,还是值得的。\

可以分享一组基准压测数据,在调用接口使用gzip和不使用gzip的QPS数据

\\

d48f3cdaf49c20bf7ac0adf5b0ab9cb1.png

\\
  • 减少接口调用次数 如何减少接口调用次数呢?对于一个页面,可能就会存在调用必要的接口,在这里我们使用到了缓存机制,对于热数据进行接口缓存,我们使用了一些内存数据库,同时对于一些规格数据可以进行进程级的缓存(如:导航信息,品类信息等)。缓存是有一定原则的:第一,需要容易命中的数据,第二,可以被缓存的数据,数据更新频率是可控的。通过缓存机制,一部分接口调用就会走到缓存,减少的接口调用的IO。此外缓存的数据可以是对接口数据处理后的视图对象,同时也减少的数据处理的时间。\\t
  • 内部服务调用DNS缓存 我们的内部服务使用域名方式,为了提高服务的灵活配置,但是需要内部DNS服务器进行域名解析,这个是有一定耗时的,所以我们在DNS这块加了DNS应用端cache,减少DNS解析的时间。\

业务处理的优化

\\

现在我们主要的服务端业务处理,主要对于页面逻辑的处理,如路由控制,会话处理,视图对象处理,模板渲染。我们在这些处理过程中进行了一些优化。

\\

如何发现node的性能问题,主要可以使用cpu-profile进行cpu处理堆栈的抓取,然后使用chrome的dev-tools进行火焰图的分析,找到性能瓶颈。

\\
  • 计算密集型操作使用原生实现 js是不擅长计算密集型的操作,如Hash处理,加密解密,压缩解压,像这些操作可以直接使用nodejs提供的原生实现(crypto, Zlib)\

以下是一组使用原生和js的md5处理性能对比:

\\

69919641463e82a9ea948677dc775d97.png

\\

返回结果:

\\
\native md5::31.59\result:5d3b7d53fdd4daaa2d75370e8a5d1789\js md5::181.54\result:5d3b7d53fdd4daaa2d75370e8a5d1789
\\

差距还是比较明显的。

\\

模板渲染的优化

\\

我们在实际使用过程中,发现模板的渲染是十分消耗性能的,特别的模板的预处理过程,如果预处理过程是在用户访问过程中去处理,会慢不止一个数量级,所以我们把预处理的过程提前了(改造了hbs),在启动web应用时,已经预编译完成。同时我们发现handlebars的一些默认配置属性,如缩减处理,在字符串拼接过程中会损耗一定的性能,所以可以关闭html片段的缩减。

\\

此外,我们还把可以缓存的html片段进行进程级的缓存,性能提升显著,可以把一些不怎么会变的html公共部分进行缓存。通过内部缓存刷新机制进行定时刷新html片段。

\\

nginx的优化

\\

启用page cache 使用了nginx的proxy_cache模块,配置了一些缓存机制,不同页面路由的缓存时长会读取node服务在http头里面返回的max-age时间。

\\
\proxy_cache cache_one_wap;\proxy_cache_valid 200 1m;\proxy_cache_min_uses 1;\proxy_cache_key $host$uri$args;\add_header X-Cache-Status $upstream_cache_status;
\\

然后我们会在应用服务添加max-age配置的中间件,对路由进行拦截装饰http header:

\\
\const cachePage = {\    '/': x * MINUTE,\    '/boys': x * MINUTE,\    '/girls': x * MINUTE,\    '/kids': x * MINUTE,\    '/lifestyle': x * MINUTE,\    ...\}
\\

另外要注意一个设置nginx缓存的时候,如果有服务端设置cookies的情况下,并且以服务端cookies的值作为标识用户会话信息,不要设置proxy_ignore_headers \"Set-Cookie\";,不然缓存会导致会话信息窜读的情况。

\\

全站HTTPS

\\

为什么要上全站https呢,这个主要考虑到https可以防止中间人攻击以及内容劫持,提高网站的访问安全性。但是因为多了SSL/TLS的服务端和浏览器端的处理,在性能方面也会有相应的下降,但是我们同时也启用HTTP/2,而主要的特性:多路复用 (Multiplexing)多路复用允许同时通过单一的 HTTP/2 连接发起多重的请求-响应消息。另外PWA里面的service worker 也是必须要求网站的协议是https的,同时也是为了这个做了铺垫,HTTPS是现代WEB的发展趋势。至于PWA不是本文讨论的重点,可以在后续其他的主题展开。

\\

浏览器端优化

\\

移动终端五花八门,导致过重的浏览器的处理和效果,会导致体验的不一致,特别是安卓手机,所以我们在浏览器端的策略是,尽量轻量化网页,当前页面只处理当前必要的内容多页面的方式。这个和现在google提出的开源项目AMP是一个思路。

\\

首屏直出优化

\\

从用户发出请求到页面完全展示,一般来说在网络正常基本上1s以上,但是如果页面打开耗时超过1s,用户流失的概率就会线性上升。所以移动端秒开至关重要,所以我们的思路是减少白屏时间,尽快把浏览器可视区域展示出来,就是所谓的首屏,我们就从以下几个方面做了优化。

\\

直出文档,简化dom结构 服务端进行HTML渲染输出,只处理首屏需要的HTML,并且简化DOM的树状结构,如:

\\
\\u0026lt;div class=\"nav\"\u0026gt;\   \u0026lt;ul\u0026gt;\      \u0026lt;li\u0026gt;xxxx\u0026lt;/li\u0026gt;\      \u0026lt;li\u0026gt;xxxx\u0026lt;/li\u0026gt;\   \u0026lt;/ul\u0026gt;\\u0026lt;/div\u0026gt;
\\

可以简化成

\\
\   \u0026lt;ul class=\"nav\"\u0026gt;\      \u0026lt;li\u0026gt;xxxx\u0026lt;/li\u0026gt;\      \u0026lt;li\u0026gt;xxxx\u0026lt;/li\u0026gt;\   \u0026lt;/ul\u0026gt;
\\

在页面只保留必要的DOM,此外,可以估算下世面手机分辨率,确定最大首屏的输出可视区域的DOM,如果需要滚屏到第二屏的,可以延迟通过Ajax获取内容加载。减少DOM,可以减少HTML的输出,当然更重要的浏览器的布局和渲染的时间大大减少。当然首屏的静态资源和样式要优先加载,下个关键点就是首屏只加载所需样式和静态资源。

\\

首屏只加载所需样式和静态资源 光有DOM的处理是远远不够的,要从白屏到展示完整的首屏,还需要样式和静态资源。所以要优先加载样式和静态资源,所以直接把公共样式中首屏用到的样式抽离出来,并且首屏用到的样式,直接在html页面内置。此外,图片和字体等其他需要展示的部分,优先加载,促使首页快速展示出来。

\\

首屏渲染,js延迟执行

\\

当首屏渲染的时候,这时候js的执行可能会阻塞渲染的线程,所以为了减少对浏览器主线程的渲染过程,尽量延迟进行js执行,特别是操作DOM的情况,不然首屏展示过程中会产生额外的重布局和重绘,js引入或代码直接放到页面的底部,在body之后,在html之前。

\\

优化直出服务端处理时间 另外再强调下直出的关键,服务端的处理wait尽量减少,对于首屏直出至关重要。

\\

图片优化

\\

图片质量和体积控制 对于移动终端来说,分辨率相当于桌面会小很多,首先会降低图片的分辨率,以及图片的DPI值,第二步会降低图片的质量,以保证图片的体积变小。

\\

提高CDN缓存命中,第一,减少缩略图的尺寸规格,第二,尽量和其他端的图片规格保持一致,如APP端,小程序。

\\

WEBP的运用 webp不是所有的浏览器都支持,所以,我们的做法是对于需要js加载的图片,进行webp的判断,如果支持,就是直接加载webp的格式图片,如果不支持,采用默认的jpg的格式。

\\
\if (window.supportWebp \u0026amp;\u0026amp; (/format\\/png/i.test(query) || /format\\/jpg/i.test(query))) {\    imgUrl = imgUrl.replace(/format\\/png/i, 'format/webp').replace(/format\\/jpg/i, 'format/webp');\}
\\

4105d45ba2b905db990b4da29c4d3c8b.png

\\

浏览器端缓存优化

\\

当存在缓存,可以减少浏览器的再次请求,大大提升了网页的打开速度,一个优秀的缓存策略可以缩短网页请求资源的距离,减少延迟,并且由于缓存文件可以重复利用,还可以减少带宽,降低网络负荷。那么下面我们就来看看服务器端缓存的原理。

\\

**缓存优化(max-age) **

\\

页面的缓存状态是由http协议的header决定的,我们主要使用了max-age(单位S),设置缓存的最长有效时间,使用的是时间长短,例如说我设置max-age=60,也就是说在请求发出后的60秒内,浏览器再次请求时不会再请求服务器,而是从浏览器缓存中读取数据。

\\

预加载和懒加载

\\

预加载和懒加载是一对好兄弟,用的好,可以极高提升浏览器端的体验,就是要确定在何时预加载,何时懒加载。我们主要在浏览器首屏结束后,当浏览器相对idle的时候,可以预加载下一屏即将展示的内容。

\\

当用户在即将触发下一屏时,下一屏的数据或DOM已经stay by了,自然体验会流畅很多,但是在预加载是需要一个度,因为一个页面的DOM过多,对于浏览器占有的内存也会过多,预加载最好是用户即将触发需要浏览的内容,如第二屏,轮播后面的内容,tab页等。

\\

懒加载的运用场景主要还是为了减少单次DOM渲染的大小,对于当前页面的非可视区域,当需要展示或用户事件触发才进行加载。所以懒加载和预加载,在不同的场景下会有不同的运用,前提是保障页面的流畅度。

\\

DNS预读取

\\

DNS预读取配置的DNS的解析,可以减少DNS的次数,也可以加速不同域名的资源加载,目前支持的浏览器还是比较多的。

\\

配置也很简单:

\\
\\u0026lt;link rel=\"dns-prefetch\" href=\"//cdn.yoho.cn\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//static.yohobuy.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//img10.static.yhbimg.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//img11.static.yhbimg.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//img12.static.yhbimg.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//img13.static.yhbimg.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//analytics.m.yohobuy.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//search.m.yohobuy.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//list.m.yohobuy.com\"\u0026gt;\\u0026lt;link rel=\"dns-prefetch\" href=\"//guang.m.yohobuy.com\"\u0026gt;
\\

当然最好的减少DNS的时间是减少站点使用DNS的数量,我们会去掉部分二级域名。

\\

CSS,JS的优化

\\

项目构建主要采用了webpack的工具链,对css,进行依赖管理和构建打包,最小化css,js,并针对我们现有的多页面项目进行多入口的分包管理。

\\

多页面css和js构建

\\

打包代码如下,便利js的源文件目录,构建各个页面模块的js,所有的页面会包含两个js文件,libjs(全局公用的js),xxx.js(当前页面特有的js),css也是一样。这样每个页面的js和css都会最小化,同时我们也对这些个静态字符串文件进行gzip压缩,当然这些文件会按照版本进行静态存储,以及CDN的缓存。

\\
\   // 构建各模块子页面JS\    // 新的生成规则 module/page/index.js\    shelljs.ls(path.join(__dirname, '../js/**/index.js')).forEach((f) =\u0026gt; {\        const dir = _.slice(f.split('/'), -3); \        // Important\        // 生成规则:module.page: './js/module/page/index.js'\        entries[`${dir[0]}.${dir[1]}`] = path.join(__dirname, `../js/${dir.join('/')}`);\    });
\\

DOM优化

\\

页面流畅度和DOM渲染和操作息息相关,渲染流程大致如下:

\\
  • 处理HTML 标记并构建 DOM 树。\\t
  • 处理 CSS 标记并构建 CSSOM 树。\\t
  • 将 DOM 与 CSSOM 合并成一个渲染树。\\t
  • 根据渲染树来布局,以计算每个节点的几何信息。\\t
  • 将各个节点绘制到屏幕上。\

ccefc06dae8e50b1432bdb3bdcc4c82f.png

\\

可以使用DEVTOOLS分析整个渲染过程中那块存在性能问题。

\\

e3b12d0bc78f41339fea3984efcccede.png

\\

简化DOM,DOM操作优化

\\

简化DOM可以减少渲染过程的时间,优化DOM操作,可以减少重布局和重绘的时间。简化DOM在上面的首屏直出已经介绍过相应的做法。

\\

这里主要说下DOM操作的优化,第一,减少DOM操作次数,可以把多次DOM操作在js的执行过程中生成好结果HTML,一次插入到DOM;第二,尽量在使用不在页面DOM树里面直接操作,可以脱离文档流的DOM中进行操作,可以使用fragment,一次插入文档流中。

\\

当然现在的react和vue都使用虚拟DOM的技术,通过diff算法进行通用化的DOM操作。这个也不失是一种效率高效的做法,但是对于一些不易优化的页面,还是需要人为干预和操作DOM使其性能最好。

\\

减少重布局和重绘

\\

第一,要减少布局调整,当您更改样式时,浏览器会检查任何更改是否需要计算布局,以及是否需要更新渲染树。对“几何属性”(如宽度、高度、左侧或顶部)的更改都需要布局计算。第二,绘制的复杂度、减小绘制区域:除 transform 或 opacity 属性之外,更改任何属性始终都会触发绘制。绘制通常是像素管道中开销最大的部分;应尽可能避免绘制。

\\

通过层的提升和动画的编排来减少绘制区域。可以使用 Chrome DevTools 来快速确定正在绘制的区域。打开 DevTools,按下键盘上的 Esc 键。在出现的面板中,转到“rendering”标签,然后选中“Show paint rectangles”。每次发生绘制时,Chrome 将让屏幕闪烁绿色。如果看到整个屏幕闪烁绿色,或看到你认为不应绘制的屏幕区域,则应当进一步研究。

\\

0899ce9c35717606c8525f9ba2d0fe6e.png

\\

页面动画优化

\\

尽量使用CSS3的动画,使用 transform 和 opacity 属性更改来实现动画。使用 will-change 或 translateZ 提升移动的元素。避免过度使用提升规则;各层都需要内存和管理开销。此外,需要减少动画的图层,每多一个图层,会多一份内存占有和管理的开销。

\\

如果一定要使用js的动画,建议使用:requestAnimationFrame。此外,能不用页面动画的场景尽量不要使用动画,如果一定要使用,可以简化动画渲染的过程。

\\

之前我们使用过一个js插件,iScroll就是一个案例,页面内初始化了多个iScroll实例,特别在安卓手机上特别卡顿,最后,我们的解决办法是自己使用css3动画和touch事件简单轻量的实现需要滑动的部分,对于页面滚动部分使用了原生的scroll,保证了不同终端体验一致。

\\

总结

\\

移动web端的优化以上每个点如果展开去讲,都可以单独写一篇文章,我们分别在以上方面做了优化,并且,也产生了比较不错的效果,移动端的打开速度和体验都有了不错的提升,普遍打开的时间提升了30-50%,在网络稳定的情况下,基本上服务端的耗时在50ms以内,首屏时间在500ms以内,但是优化这件事情是永无止境的,没有最好,只有更好,需要开发者探究根本勇于创新,达到更好的更优的境地。

\\

我们后续,还可以在web的基本技术点上深挖,同时在PWA以及AMP等现代web新思维的多个方面大家积极面对,继续探索,在未来的web前端之路可以走的更好,提供更优的用户体验,创造更高社会价值。

\\

感谢徐川对本文的审校。

相关文章:

  • 修复.NET的HttpClient
  • 51nod1683
  • KPN iTV的敏捷转型之旅
  • 设计模式之禅之单例模式!
  • 纠纷判决已出,法官要求Uber归还所有Waymo自动驾驶机密文件
  • 10个最新交互式Web设计实例欣赏
  • VSCode建立.net core项目
  • 事物(物质)的存在形式:结构与运动、维度空间:结构-空间,运动-时间...
  • 从0搭建SpringBoot的HelloWorld -- Java版本
  • 商城系统针对开发者自有支付系统提供的解决方案
  • 基于Redis实现分布式消息队列(4)
  • Java 多线程之线程池的使用
  • 5、React组件事件详解
  • Linksys WRT54G 路由器溢出漏洞分析—— 运行环境修复
  • 也谈链路劫持
  • 《Java8实战》-第四章读书笔记(引入流Stream)
  • 2017-09-12 前端日报
  • ES6--对象的扩展
  • express + mock 让前后台并行开发
  • javascript面向对象之创建对象
  • JS实现简单的MVC模式开发小游戏
  • Linux gpio口使用方法
  • nginx(二):进阶配置介绍--rewrite用法,压缩,https虚拟主机等
  • Promise面试题2实现异步串行执行
  • React-redux的原理以及使用
  • vue-cli3搭建项目
  • web标准化(下)
  • 从零开始的无人驾驶 1
  • 关于Java中分层中遇到的一些问题
  • 解析带emoji和链接的聊天系统消息
  • 利用jquery编写加法运算验证码
  • 批量截取pdf文件
  • 深度学习中的信息论知识详解
  • 使用 Node.js 的 nodemailer 模块发送邮件(支持 QQ、163 等、支持附件)
  • ​批处理文件中的errorlevel用法
  • #Linux杂记--将Python3的源码编译为.so文件方法与Linux环境下的交叉编译方法
  • (12)目标检测_SSD基于pytorch搭建代码
  • (C语言)球球大作战
  • (delphi11最新学习资料) Object Pascal 学习笔记---第5章第5节(delphi中的指针)
  • (ibm)Java 语言的 XPath API
  • (八)c52学习之旅-中断实验
  • (附源码)spring boot儿童教育管理系统 毕业设计 281442
  • (附源码)springboot 房产中介系统 毕业设计 312341
  • (附源码)ssm本科教学合格评估管理系统 毕业设计 180916
  • (附源码)计算机毕业设计大学生兼职系统
  • (转)jdk与jre的区别
  • (转)ORM
  • .NET 将多个程序集合并成单一程序集的 4+3 种方法
  • .NET 使用配置文件
  • .NET/C# 利用 Walterlv.WeakEvents 高性能地定义和使用弱事件
  • .Net各种迷惑命名解释
  • .Net开发笔记(二十)创建一个需要授权的第三方组件
  • [ element-ui:table ] 设置table中某些行数据禁止被选中,通过selectable 定义方法解决
  • [Android实例] 保持屏幕长亮的两种方法 [转]
  • [BZOJ1877][SDOI2009]晨跑[最大流+费用流]