应用性能是产品用户体验的基石,性能优化的终极目标是优化用户体验。当我们谈及性能,最直观能想到的一个词是“快”,Strangeloop在对众多的网站做性能分析之后得出了一个著名的3s定律“页面加载速度超过3s,57%的访客会离开”,可见页面加载速度对于互联网产品的重要性。
速度在Google、百度等搜索引擎的PR评分中也占有一定的比例,会影响到网站的SEO排名。“天下武功,唯快不破”,套在性能上面也非常适用。
性能优化是个系统性工程,涉及到后端、前端、移动端、系统网络及各种基础设施,每一块都需要做各自的性能优化。当我们系统的分析性能问题时,可以通过以下指标来衡量:
互联网产品是创意、设计、研发、系统、网络、硬件、运维等众多资源相互交织的集合体,性能受多方面因素影响,犹如一只木桶,木桶能盛多少水,取决于最短的那块木板,也可称之为短板效应。影响产品性能的因素有:
1. 产品逻辑与用户行为
产品逻辑过于复杂、功能交互过于丰富、产品设计过于绚丽、页面元素素材过多等都会影响产品性能。
2. 基础网络
中国的基础网络是世界上最复杂的基础网络,国内的网络运营商众多且各自为政,互联互通成本很高。对于境外业务来说更是要面对国内国际网络交互的情况,再加上GFW的存在,网络延迟、丢包现象非常严重。
3. 代码及应用
开发语言瓶颈、代码质量及系统架构等都会影响系统性能,常见的代码及应用问题有:
4. 移动端环境
移动互联网时代,移动端环境的复杂性对产品的性能影响也很大,比如用户的设备类型、设备性能、操作系统类型、系统版本及网络类型(电信/移动/联通,Wi-Fi/4G/3G/2G)等。
5. 硬件及云环境
硬件的发展遵循着摩尔定律,生命周期一般都很短,服务器老化或其他硬件问题经常会导致应用故障。IDC、机架、服务器、内存、磁盘、网卡等不同硬件和操作系统上运行的应用性能差距可以达到数十倍之多。
关于性能优化这个主题,美团点评技术团队博客之前也发表过不少系统阐述的文章,比如《常见性能优化策略的总结”》和 《性能优化模式》,可以参考。
美团旅行境外业务主要包含商户信息、用户评论、特色美食、出境线路、境外当地玩乐、购物优惠券等6大板块,满足用户行前到行中的出境场景。在覆盖城市上,目前已经100%覆盖全球热门旅游目的地,大家出境游玩时都可以体验使用。根据官方提供的数据,超过30%的自由行游客在出境时会选择使用美团点评,当前美团点评已经成为除微信之外,国人在境外最喜欢打开的移动App之一。
境外业务与其他境内业务相比,区别主要表现在以下及方面:
基于以上背景,如何提升产品性能,做到像国内业务一样,其中面临了很多的技术挑战。本文将从网络优化、前端优化、后端优化几个方面来介绍境外业务在性能优化方面的做过的一些事情。
影响网络性能的问题有很多,常见的网络问题有以下几类:
一类是DNS解析慢或解析失败,我们统计过一些数据,我们的域名在国内DNS解析耗时大概在30-120ms之间,而国外网络下耗时达到200-300ms左右。在2G/3G等弱网环境下,DNS解析失败非常常见。DNS对于首次网络访问的耗时及网络成功率会有很大的影响。
下图是我们利用页面测速工具(GTmatrix)在加拿大温哥华节点测试的一个页面首次访问时的网络请求情况,可以看出当用户在加拿大第一次访问且运营商LocalDNS无NS缓存记录时,DNS解析耗时2.36s。
另一类常见问题是DNS劫持或失效,乌云(WooYun)上曾报过多起因域名服务商安全漏洞被黑客利用导致网站NS记录被篡改的case。而更多的DNS劫持问题则是来自于网络运营商的作恶,主要有以下几种:
我们在网络优化方面主要做了以下几件事情:
接下来,我们分别详细阐述一下。
CDN服务的好坏主要取决于节点部署的覆盖程度、带宽以及调度算法。对国内业务而言,使用蓝汛、网宿、帝联等老牌CDN服务商或腾讯云、阿里云、UPYUN等云CDN服务商性能都很好,但这些CDN服务商在境外的节点很少。
为了提升境外的静态资源加速效果,我们尝试对接了很多国外的知名CDN服务商。通过智能DNS解析用户的IP来源,如果是境外访问则CNAME到国外CDN,国内访问时仍然走的是国内CDN。
CDN也是一种缓存,是缓存就不得不谈命中率的问题。 如果用户在境外访问时CDN未命中,静态资源从境外回源到国内源站获取,成本非常高。为了提升缓存命中率,我们的做法是在中国香港搭了一个CDN中间源,在前端资源发布时会调用CDN的push接口把资源预热到中间源,保证当境外边缘节点缓存未命中时无需再回源到国内IDC,只需从中间源获取。
由于DNS的种种问题,腾讯推出了HttpDNS服务,使用HTTP协议向DNS服务器的80端口进行请求,代替传统的DNS协议向DNS服务器的53端口进行请求,绕开Local DNS,避免网络劫持和跨网访问等问题。但HttpDNS需要能够获取CDN边缘节点的IP,这就限制了只有像腾讯、阿里这种有自建CDN的大厂才能实现。
另外W3C也提供了DNS预读的方案,可以通过在服务器端发送 X-DNS-Prefetch-Control 报头,或是在文档中使用值为 http-equiv 的 <meta> 标签:
<meta http-equiv="x-dns-prefetch-control" content="on"> 的方式来打开浏览器的DNS预读取功能,但是该API功能目前在移动端浏览器内核中实现支持的较少。
我们采取的是一种轻量级的方案,如下:
DNS Prefetch上线后境外域名解析时间RT90从350ms下降至250ms左右。
HTTP请求重度依赖DNS,DNS劫持、移动端网络不稳定使建连失败,以及公网链路质量差等因素,导致移动端的网络成功率一直不高。HTTP 2.0可以通过SSE、WebSocket等方式与服务端保持长连接,并且可以做到请求多路复用,但HTTP 2.0对运维、前端、后端的改造成本非常高。基于此背景美团点评自研了Shark服务。一种“代理长连接”的模式,主要用于解决移动设备网络通信质量差的问题。
这种“代理长连接”的模式,对后端业务是无感知的,业务无需做任何改造。另外也巧妙的绕开了DNS、公网质量差等问题,极大的提升了Native API请求的网络成功率。
关于Shark的详情,可以参考之前发表的文章《美团点评移动网络优化实践》。
目前美团点评大部分的App都接入了Shark SDK基础网络库,Native API(我们内部叫Mobile API,MAPI)的网路请求由Shark SDK统一解决,使用的是自定义的序列化方式(内部称DPObject,比JSON效率高)。但对于H5页面中的Ajax请求,是没法直接享受到Shark带来的“福利”的。先看一下Hybrid模式下一次Ajax请求的过程:
上图可以看出,一次普通的Ajax请求会由WebView的内置浏览器内核来发送接受请求,一般是JSON格式的数据,和PC浏览器的一次HTTP请求过程差别不大,都是要经过DNS、TCP建连以及要面对公网链路差等问题,另外Ajax请求没法复用TCP连接,意味着每次请求都要重新建连。有了MAPI的经验,我们很容易想到,能否像MAPI一样利用长连通道来提升性能呢?
一种方式是在WebView中拦截页面的HTTP请求,在容器层做请求代理并处理序列化反序列化等事情。这种方式对业务比较友好,业务方几乎不需要做什么事情。但WKWebView的限制比较多,所以该方案目前很难推行。另一种方式是通过JsBridge来实现,缺点是对业务侵入性较高,业务方需要手动控制桥API的调用,一期我们选择的是“较笨拙”的方案二。
Ajax接长连解决了Hybrid模式下App内的H5场景,而对于App外的入口场景,如M站(针对手机的网站)、微信朋友圈分享等,我们没法使用到Shark长连接,跨网跨国访问的问题依然存在。这种情况下我们的优化主要是“利用专线”:
另一个场景是,我们和很多的境外供应商有直连对接,通过HttpClient的方式后端发起调用对方的Open API接口,这种场景优化前接口延迟及网络成功率都非常不理想(很多和国外对接的业务应该都遇到过类似的问题),我们的优化方案是:
在中国香港部署一个正向代理Squid,请求先由内网专线转发到中国香港的Squid服务器,再从中国香港机房的网络出口出去。以与中国香港迪士尼的对接为例,优化前的API接口RT95:9s+(迪士尼接口传输的数据非常多),优化后降到2.3s,效果非常明显。
除了专线方案,我们还测试CDN动态加速。
CDN不仅可以用来对静态资源做缓存加速,也可以对动态数据接口起加速作用,原理如下:
用户请求耗时=用户和边缘交互的时间 + 1*RTT(边缘到源站)+ 源站处理时间
HTTP请求中,建连是个非常耗时的事,普通HTTP请求TCP 3次握手要消耗2个RTT时间,如果用户和服务器位置很远,比方说用户在美国,服务在中国,请求走海底光缆的话一次RTT理论值是150ms左右(光波信号传输速度结合物理距离计算得出),实际肯定大于理论值。
CDN动态加速主要在以下几方面起到优化效果:
我们实测下来CDN动态加速在部分国家和地区有明显的加速效果,但整体的效果不够明显,所以最终未投入规模使用。
前端优化我们主要做了下面几件事情:
在之前的项目中,页面是“Java直出”的方式,由Java后端项目中通过FTL模板引擎拼装,前端团队会维护另外一个前端的项目,存放相应的CSS和JS文件,最后通过公司内部的Cortex系统打包发布。
这个流程的问题在于前端对于整个页面入口没有控制力,需要依赖后端的FTL拼装,页面的内容需要更改时,前后端同学就要反复沟通协调,整体效率比较差,容易出错,也不方便实现前端相关的优化,前端做模块组件化的成本较高。
前后端分离的关键点在于前端拥有完整独立的开发、测试、部署的流程,与后端完全分离。我们把页面的组装完全放置到了前端项目,后端只提供Ajax的接口用于获取和提交数据。前端页面完全静态化,构建完毕之后连同相应的静态资源通过CI直接发布到CDN。这样的好处有:
前后端分离架构有诸多的优点,但有一个坑需要注意:SEO的问题,无法提供给搜索引擎可收录的页面,因为主文档HTML基本为空页面,需要搜索引擎蜘蛛拥有执行JavaScript的能力才行,现实是大部分的搜索引擎都不支持。所以对于一些需要搜索引擎引流的页面不推荐用前后端分离。
在一些重体验的网页上,图片资源的占比通常较大,一些高清大图动则几十上百K大小。 针对图片这块我们主要做了以下几点优化:
美团云的图片服务提供了实时剪裁功能,后端在下发图片URL时不需要指定尺寸,由客户端根据屏幕尺寸做自适应计算,这样可保证每台设备上的图片都“刚好合适”。
域名过多会带来以下问题:
我们做了以下几件事:
通过域名收敛/减少请求数,我们商品详情页的页面请求数从8个减少到4个、域名数也减少一半,页面完全加载时间下降了约1000ms。
离线化可以减少网络请求,加速页面加载速度。另外当用户网络不稳定或断网时也可以访问已被缓存的页面和资源,我们先后使用了2种离线化方案:
前面介绍了前后端分离的架构,HTML主文档可以利用CDN加速,另外前后端同学很好的解耦开了,前端可以更方便的做组件化沉淀。但这种架构除了SEO时的问题还有另外一个问题,先看一下前后端分离下的一个页面加载过程:
当遇到页面引入的外部依赖很多时,这种架构性能可能还不如"Java直出":
同构渲染,结合了Java直出和前后端分离的优势:
Node同构和一开始的Java freemark后端渲染Java直出的方式对比,最大的区别在于:Node项目可由前端同学来维护,用的是前端工程师熟悉的JS语言。另外前端生态较好,React、Vue等框架都提供了丰富的渲染模板供前端工程师选择。
后端优化的思路相对比较比较通用,和境外业务的特点关系性并不大,稳重的前言部分“影响性能的因素”章节有简单描述,本文将不对各种后端优化手段做详细介绍, 只挑几件我们做过的事情做下简单介绍:
美团点评在上海、北京两地都有机房,以我们的商品服务为例,商品服务会同时被上海和北京的两侧的应用依赖调用。一开始我们的服务只部署在上海机房,由于上海、北京两地机房间地理间隔较远(北京-上海专线ping延迟30ms左右),当北京的应用调服务时就会有较高的延迟,如果一个API内多次跨地域调用则性能会非常差。
为此我们做了如下的缓存双集群加异地模式部署:
通过这种双缓存加异地部署的“异地多活”模式(实际是异地只读),提升了我们服务在跨地域场景下调用时的性能。
结合境外业务特点,本文从网络优化、前端优化、后端优化几个角度介绍了境外业务在性能优化上的一些实践,重点篇幅放在了网络优化部分。性能优化是一个系统性工程,需要前端、后端和SRE一起协作才能做好。得益于公司强大的高性能前端框架、BGP网络、高性能应用组件、云平台等基础设施,以及在靠谱的运维保障SRE团队、基础架构团队以及平台团队支持下,我们境外业务的性能优化取得了阶段性的成果,后续还要继续努力!