内容来源:2017年6月24日,腾讯前端高级工程师周明礼在“腾讯Web前端大会 TFC 2017 ”进行《QQ钱包h5应用开发实践》演讲分享。IT 大咖说(WeChat_ID:itdakashuo)作为独家视频合作方,经主办方和讲者审阅授权发布。
阅读字数:3071 | 5分钟阅读
观看嘉宾完整演讲视频及PPT,请点击:http://t.cn/EAAPbUx
摘要
移动互联网时代,提高网页性能是每个前端团队的目标。作为QQ钱包团队的前端工程师,我们是如何通过自研nodejs服务和利用service worker实现H5页面秒开?让我们来探讨一下QQ钱包H5应用的开发实践。
QQ钱包众多H5应用
2015年我们正式成立了钱包团队,从刚开始QQ钱包只有一个钱包入口,一直发展到今天,已经开发出了话费充值、卡券、积分、企鹅网吧、城市服务以及智慧校园等一系列服务。
QQ钱包H5应用开发挑战
接入层服务器压力大
QQ钱包H5应用日均pv在1000w以上,推广期pv可达上亿的级别,需要解决服务器性能优化问题。
移动网络环境复杂
为了提高用户的体验,需要尽快的吐出页面,安卓平台下一般要求为3秒,而在ios平台则是要求2秒以内,在某些业务场景下直接要求秒开。
交互场景复杂
移动端特别是安卓平台的web性能容易造成瓶颈,例如长列表渲染问题,图片内存占用问题,css3动画性能问题都需要去解决。
基于SERVICE WORKER的缓存管理方案
浏览器缓存
以卡券页面为例,整个页面总共会发出35个请求,其中有13个请求是数据上报,剩下的都是有效请求。而这些有效请求中,又有9个是JS的请求,有8个是IMG,还有一些其它的请求。
我们大概可以评估出一个页面可能有77%的静态资源。一般静态资源请求的变化比较少,我们不希望浏览器去重复下载一些相同的资源,所以我们就会采用到浏览器缓存来优化我们的页面。
我们一般通过配置一些http请求头去控制我们的缓存策略,然后通过版本号来更新我们的资源。
但在我看来,这样的流程存在着3个小问题。
缓存机制不足
更新不可靠。由于CDN的更新不是实时的,很多时候我们的资源已经发布到线上了,它的CDN还没有更新。
离线体验不佳。现在的浏览器缓存在离线体验上是不好的,明明已经缓存在本地了,但是断开网络打开页面之后还是会显示未连接到互联网。
不可定制化,例如无法增量更新。现在的浏览器缓存主要是通过配置,但如果需要实现一些自定义的策略是做不到的。
Service Worker
ServiceWorker是浏览器为了解决之前AppCache在管理离线缓存上的不足,而提供的在Web应用程序与服务器之间的代理层。
总的来说,Service Worker就是一段在浏览器后台自动运行的程序,负责协助浏览器,管理和响应所有从Web应用发出的请求,以达到更好的离线体验。
性能有所增强,比如预取并缓存用户可能需要的资源,比如页面中所需的静态资源文件;可以同步后台数据同步;响应来自其它源的资源请求;集中接收计算成本高的数据更新;后台服务钩子;自定义模板用于特定URL模式以及可以在客户端进行模块编译和依赖管理。
等待状态
到达installed态的Service Worker并不会直接进入activating态,如果浏览器中还有其他页面运行着该Service Worker的一个旧版本,那么新的Service Worker就会处于等待的状态,直到其他页面关闭。这主要是为了避免Service Worker中所使用到的资源被意外释放。
一旦其他相关页面都关闭了,就意味着旧的资源文件已经不再需要。这时候我们就可以执行下一步清理的工作。
Activate事件
Fetch事件
MoggyCache离线包管理
QQ钱包团队搭建了一套MoggyCache离线包管理系统。通过这套系统我们可以针对项目配置当前项目需要用到的静态资源。并且配置了离线包当前是否开启,或者是针对灰度用户进行开启。都可以配置到这个平台上,而且存储在内部的一个DB上。
MoggyCache工作原理
我们的node.js服务通过读取上述的配置动态生成了两个脚本,一个是install脚本,一个是worker脚本。
install脚本主要是读取离线包当前的一个开关以及它当前灰度用户的策略,来判断当前用户是否需要安装我们的离线包。
一旦判断出用户需要安装离线包,它就会通过注册的过程把worker脚本注册成当前页面的Service Worker。而这个worker就会把配置里的资源列表下发到worker脚本里面。Worker就会走一遍流程把这些资源加载并缓存在本地。
MoggyCache新增特性
以上过程仅仅是简化了我们的一些工作,还是没有解决问题。所以我们在这基础之上又新增了它的几个特性。
自动同步
我们每次配置一个项目的时候,会计算出所有资源的md5,并且存储在DB里面。然后我们的node.js服务就会读取到刚才的配置,把md5下发到刚才的Service Worker里面。
现在我们更新资源需要进行发布的时候,在发布系统上面添加了一个后置脚本,一旦资源发布,这个后置脚本就会触发离线包系统,去重新计算每一个资源的md5,并重新推送到Service Worker。
这时Service Worker就有了两个md5,一个是旧版本的md5,一个是当前最新的版本。通过对比这两个md5,我们就知道哪些资源已经过期了。当发现了过期的资源,Service Worker就会重新到服务器上拉取最新资源。整个过程是自动的,无需人工干预。这就解决了不可靠的问题。
增量更新
每当有新资源发布的时候,我们都会通过后置脚本的方式通知MoggyCache系统。它就会读取新的资源并进行计算,算出格式的增量包,然后把增量包存储在服务里面。
因为md5已经更新了,所以worker脚本就会重新发送请求到我们的服务。这时如果服务发现资源有可用的增量包,就会把这个增量包直接返回给Service Worker。Service Worker通过判断请求头就可以执行不同的策略。
接入层服务架构
在QQ钱包成立初期,我们使用的接入层架构是PHP + APACHE。当时PHP的版本非常成旧,我们需要开20台服务器才能完成所有请求的响应,而单机的QPS只有200。
从这些数据里可以看出性能还是不够好。再加上PHP用了腾讯内部编写的一些私有模块,各个模块之间又有不同的版本,导致它还有部署成本高,扩容困难,apache日志缺漏,web服务缺乏监控等一系列问题。
经过一段时间的考察,我们最终选择了NODEJS。
选择NODEJS的原因
性能优异:node.js采用的是基于libuv库的异步io方案,相比apache的多进程同步阻塞方案在性能上的提升明显的。
前端友好:前端熟悉js的语法,对node.js可以平滑过度,培养新同学更为简单容易。
社区成熟:成熟的社区和完善的技术文档,大量的成熟模块可以为业务使用。
选择自研框架的原因
应用场景:我们的MoggyServer服务,主要的使用场景是页面直出。
更加可控:自研的框架,更加可控,扩展性更加强,这些都是我们需要考虑的。
稳定和快速:社区里的框架大多会包含类似静态文件处理,json数据处理等额外的功能,这并不是我们想要的。
服务平滑重启
我们把服务器平滑重启的逻辑内置到框架里面。通过在发布系统上配置一个后置脚本来通知node.js的子进程有新的文件要发布,并在子进程接收到消息之后把这些消息通知发送给旧的子进程,它就会停止对外服务。
因为新的代码已经发布到线上,就可以用新的代码重新创建新的子进程。旧的子进程把原本还在服务的用户处理完后就会把自己注销掉,整个过程就能达到平滑的效果。
模版引擎优化
我们用积分将一个业务的页面进行10000次渲染,然后统计它所花费的时间。
这里我们主要是对ejs的模板嵌套语法做了精简,所以有了性能上的提升。
用传统的ejs渲染需要7500毫秒,而用tpl渲染只需要920毫秒。这就是我们做的模版引擎的优化。
MoggyServer线上数据
QQ钱包现在只运行了7台服务器,就完成了上千万级别的服务量。单机QPS从200已经提升到了900。2017年初,我们的总请求数峰值已经到达了1.69亿,申请了57台机器去支撑,但每台机器的CPU只用了30%。
直出页面加载
传统页面加载方案:从用户点击入口,native再去拉起webview,等待webview初始化完成后发送http请求去node服务拉取页面数据,最后对页面进行渲染。
SONIC优化方案
串行改并行
相对传统加载方案中,优化方案在native执行时候实例化webview,同时并行向sonic服务器发起请求,将此前的串行操作优化为并行,因此此处耗时由sum(webview,request)转变成max(webview,request),降低了客户端层面的耗时。
页面缓存
sonic支持对h5页面级进行本地缓存,将返回的页面分别拆分成数据层以及模版层层分开来缓存,生成本地缓存文件,且缓存时长为永久缓存,如果页面是没有任何变化,这时候会完全显示缓存的数据。
增量更新
对于页面更新的情况,sonic会去对比和计算客户端缓存中的页面的变更地方,封装成json数据结构返回给客户端进行页面更新以及缓存更新,这样可以大大减小了回包的大小,特别对于移动网络而言可以大幅度为用户节省了请求流量。
我的分享就到这里,谢谢大家!