未分类

MonkeyEye电影售票系统-性能优化总结

案例来源:SYSU SE305 课程大作业。互联网售票软件是比较常见的软件系统。通常由多个零售系统和多个供给系统系统。 机票、酒店房间、电影票似乎是风马牛大相关的系统,但它们之间存在极其相似的业务模型。 以电影票为例,格瓦拉、蜘蛛网、腾讯等等都做类似的电影票分销、推广业务,但票是由各大院线排期提供的。 分销-院线-影院形成了一个完整的生态体系。 本课程以大家熟悉的订票为例,学习分析、设计、开发的方法。

我所在的小组做的是一个叫做MonkeyEye的项目,实现一个简单的电影购票系统,并在项目完成过程中学习系统分析与设计,学习UML建模等技能。本系列文章将会以此项目为案例,总结整个项目的设计、建模与开发过程。

前端项目地址:https://github.com/SYSUMonkeyEye/MonkeyEye-FE

关于项目的介绍和技术要点请查看项目结构技术要点

由于我在项目中主要负责前端开发,因此本文将会较多地讲解本项目中使用到的前端性能优化技巧,后面再简单涉及一些服务器部署方面的优化技巧。

一、主要问题

  1. 服务器带宽较小,库文件体积较大。还没做优化的时候,构建之后的产品文件里面,库文件vendor.js的大小大概有400+KB。加载这个文件的时延大概是3-4s
  2. 图片体积较大,数量较多。应用中有比较多电影海报图片,大小大概也是几百KB,由于服务器带宽很小,加载一张图片的时延也是需要几秒。

应用没有计算性能方面的问题,另外我们也没有更高配置的服务器,所以优化方向主要朝着减小资源文件体积进行。

二、前端优化

1、按需引入

webpack打包的时候会根据代码中import的内容,将相关文件打包到产品文件中。本项目使用的UI框架是vue-material,之前引入这个库的方式如下。这种方式会将整个vue-material库引进来,也就可以随意使用它里面的UI组件了,但这也造成了引入部分无用组件从而增加了产品文件的体积。

vue-material普通引入

查看官方文档后,发现这个库是支持按需引入的,因此,我们将代码改成只引入我们用到的组件,如下。

vue-material按需加载

引入完成之后还要再一个一个挂到Vue上面才能使用。

挂载组件

完成按需引入之后,webpack打包就只会将import到的组件加到产品文件里面,最大的js文件vendor.js从400+KB减少到了376KBcss样式文件也减少了大概30KB。加载时延较少了一些。

2、迁移至webpack 2

项目一开始用到脚手架是基于webpack 1的,根据官方介绍,webpack 2优化算法,能够有效减少构建后的产品文件体积。因此,我们将项目迁移到webpack 2上去进行构建打包。最大的vendor.js376KB减少到了299KB,其他js、css文件也有所减少。整体加载时延减少了1-2s

3、开启gzip

我们使用了一个nodejs服务器app-server来提供网页服务,打开应用的时候从这个服务器上加载网页资源。在浏览器发起请求加载js/css等资源的时候,服务器不要给出原始的文件资源,而是做一个gzip压缩。做法如下,使用compression中间件即可实现gzip压缩,并且会自动帮你填充HTTP报文头部需要的字段,浏览器拿到响应报文之后就会去完成解压。

compression中间件

HTTP头部

gzip的效果非常明显。体积最大的vendor.js299KB压缩到了80.8KB,其他文件的体积也压缩到了很小。加载时延直接降低到了100毫秒以内。

时延

4、H5缓存

现在的加载速度已经非常快了,基本算是秒加载,但还可以继续优化,使用H5缓存。把应用的html/js/css缓存在前端,首次打开应用的时候会有100ms左右从服务器加载资源,但后面再次打开应用的时候便是直接从本地缓存读取,速度更快。先上一下效果图。现在时延降低到10毫秒以内,可以说是一打开应用就可以开始渲染呈现界面了,用户体验提升了很多。

时延

下面说说怎么实现。H5缓存需要在HTML标签里面引入一个manifest文件,manifest文件中写好要缓存的资源。manifest的写法也有一些简单的规则,这里不一一介绍。

引入manifest

manifest内容示例

然后,配置服务器让服务器可以提供manifest这个文件即可。前端解析HTML之后发现标签里面有manifest文件,就会向服务器请求manifest文件,然后根据该文件的描述去缓存相关的资源。

服务器配置

缓存效果

H5缓存到这里已经实现好了,很简单。不过仔细观察就会发现,有一个比较烦人的事情要处理。那就是这些静态资源文件名,webpack构建后的产品文件名会加上一串哈希码,使得每次构建的产品文件名都不同。这种机制可以避开缓存问题,使得新构建的产品文件能够及时更新到生产环境上去。如果不加这串哈希码,新旧产品文件都是同名,那么可能因为缓存导致生产环境中的代码没有及时更新。

文件名示例

既然每次构建产品文件名的哈希码都会有所不同,那不就意味着manifest文件必须每次构建完成后都重新写一次。如果是人为自己去写就很烦了。所以,下面需要写一些代码让这个过程自动化。通过分析代码,发现在build/build.js中,webpack构建完成之后会有个回调函数,我们可以在这个回调函数里面做些手脚。

构建完成之后产品文件会放到产品文件夹,我们只需要对产品文件夹做一次深度优先搜索(DFS),即可得到所有产品文件的路径和名称,然后把路径和名称写入到manifest文件即可,写入的时候顺便将manifest的版本设置为当前的时间戳,使得每次构建产生的manifest文件版本都有有更新。

DFS

DFS

5、webp图片格式优化

现在剩下图片问题了。首先可以修改一下图片格式,将图片从原来的jpg、png等格式转成体积更小的webp格式,一次请求的大小从原来的几百KB降低到了几十KB,少量比较大的图片会到140+KB,最小的可以到30KB左右。时延降低到了几十毫秒的级别,比较大的图片也只有500+毫秒。加载快了很多,主界面很快就可以渲染出来。

时延

6、图片懒加载

由于主界面图片较多,一次性把全部图片加载出来的做法很不好。可以做成懒加载,当图片出现在屏幕可视区域之后才加载图片。做法就是在img元素进入到可视区域的时候,才设置它的src属性,src属性设置之后才会开始向服务器请求响应的图片。自定义一个directive给img元素使用,在directive里面通过监听屏幕滚动判断元素是否进入了可视区域,进入可视区域之后再设置相应img元素的src属性。此部分代码由我另一位组员实现,我不保证能完全讲清楚,所以这里我不做过多阐述,感兴趣可以查看本项目代码里面的/src/common/utils/LazyLoad.js

1
img(v-lazyload="movie.poster")

三、部署优化

项目使用了Python服务器作为API服务器和图片服务器,搭配MySQL数据库。使用NodeJS服务器作为网页服务器,然后利用Nginx做负载均衡,做了些简单的分布式部署,用了4台云主机。部署图如下所示。

部署图

python服务器使用的数据都是来自同一个数据库,如果服务器数量支持的话,MySQL也可以做分布式部署。然后由于部署了多台API服务器,当请求被转发到不同服务器上时会出现session丢失的尴尬情况,所以这里用了一个Redis数据库来实现共享session。网页服务器单独用nodeJS部署而没有跟静态资源服务器合并在一起,主要是方便前端可以及时更新代码,以及做一些像H5缓存之类的东西,毕竟这些内容前端会比较熟,nodeJS也比较熟。最后静态资源服务器只部署了1台,首先是服务器数量不够,其次是静态资源服务器的计算量很少,只是简单地提供文件服务,所以就没有部署多个节点。

Nginx会根据请求的地址将API请求、网页资源请求和静态图片请求转发到后面响应类型的节点,并且会做负载均衡。虽然本项目只是一个简单的课程大作业,但还是尽量尝试我们力所能及的优化,多把握一些实践机会。也许还有很多缺陷,欢迎讨论。

分享到