接下来还是说页面的那些事,这次要介绍的是开发者工具,Chrome、Safari、Firefox、IE都有这个东西。通常它包含页面元素分析、控制台、资源查看、网络查看、性能分析等功能。在Network里可以看到页面加载的时候发起了哪些请求,以及这些请求的状态码、资源类型、大小等基本信息,和各阶段耗时分析。

如何针对单个请求进行优化

首先看下图,这是Chrome的Network面板在打开网站 https://mythk.net的瀑布图:

可以看到,浏览器首先请求了www.mythk.net这个页面,获取到html文档后,随之开始请求html中引用的site.min.css和jquery,其它请求处于Pending是指它们已经进入请求队列,但还未开始。

网页完全加载完成后,可以看到下面的图:

白色长条代表队列中的等待时间(确知需要下载该资源,但该资源并未立即开始下载,而是呆在队列中处于等待状态),绿色代表请求发起到收到响应这段时间,蓝色代表开始下载内容的这段时间。

按上面的说明进行比对,可以发现第一梯队下载了html,第二三梯队下载了css和js,最后一梯队下载的是图片(按上一篇文章提到的,每个浏览器有一个向同一服务器请求资源的并发数限制,同时这个并发并不总是跑满,有的时候css和js存在依赖关系,这就只能先下载完一个,再下载后续的其它文件)。看懂了这个,资源之间的衔接以及资源下载的速度就有了优化的依据。

  • 白色长条表示当前有其它任务正在进行,使得本资源无法下载,只能在队列中等待,所以本身没有可优化的地方,只能想办法让前置资源尽快下载完毕,这样自己才能早点开始下载。
  • 绿色长条是从请求发出到有响应返回的这段时间,它由多个因素决定:首先是双方网络状况,而且取决于更慢的一方(短板效应),只有访客到服务器之间的整条链路质量合格,才不至于拖累性能表现;来回路程消耗时间扣去以后,剩下的就是服务器响应这个请求的所耗费的时间,没有一个明确的优化方法,只能通俗地将它描述成“让服务器处理请求更快一点”。
  • 蓝色长条的优化方向是:减小资源体积,当然它也受带宽影响,不过一般将加带宽这种方案的优先级设置为最低,只有确实存在带宽瓶颈的时候才会考虑升级。

另一个角度对请求进行优化

我们知道,浏览器常用的HTTP请求谓词常用的有GET, POST,在进行API设计的时候,有可能也会用到OPTIONS, DELETE, PUT, HEAD。之所以这么划分,HTTP协议设计之初自有它的考量,希望大家按实际的请求用途来设计自己的服务,然而这么搞明显太费事,无脑GET、POST解决99.99%的烦恼,香。

扯得有点远,下面该说到表单提交了。一个比较典型的表单提交流程是这样的:打开页面、填写内容、前端验证数据有效性(如果有)、提交、跳转到初始页或者中间页。仔细回顾这个过程可以看到,页面加载了两次,这就意味着要请求双份的页面资源、要丢失当前页面的状态(如果什么处理都不做的话),这显然既打断了访客的当前浏览行为,又额外增加了一倍的网络消耗,当然如果专门为POST请求创建一个中间页,又多一笔消耗,所以避免直接使用页面提交,也是一个重要的优化手段。

早在很久很久很久以前,为了解决提交提交表单的时候页面跳转的问题,就已经有人发明了iframe提交、xmlhttp异步请求的方案,没有跳转就没有浏览行为打断、就没有额外的页面资源请求(当然请求本身提交数据所需的流量消耗是无法避免的)。

而ASP.net webform年代的时候也有自己的AJAX方案:UpdatePanel局部刷新技术。

然后就是应用广泛的jQuery的AJAX方案(再往后的就实在不知道了,前端技术了解得不是很深入)。

说到底,异步提交本质上就是通过一个xmlhttp对象进行http post/get请求,而不再是由浏览器从当前页面发出请求,也就避免了页面刷新,所有的事都交给了xmlhttp,当收到响应后,通过回调取得结果并进行后面的操作。

服务器选址

这是一个非常重要、非常容易优化、非常容易忽视、优先级非常高的因素。上文提到的所谓的“网络状况”,既对白色长条有影响,也对蓝色长条有影响。所以,在部署服务之前,建议尽量针对客户群体进行选址考量。比如,客户在海外,那自然将服务器部署到海外更好,如果客户群体位于某一个国家或者某一个地区,往往需要就近选择托管服务商。

下面轮到后端了

在第一小节中,提到了网络面板瀑布图的绿色长条,除了去程和回程的网络消耗,剩下的时间就代表服务器接口本身的响应速度。通过对接口响应时的请求处理优化,也能整体提升网站性能表现。但是这一段内容极其开放,后端业务无法用简单的几百几千甚至上万字一概而论,所以只能试着多举几个例子说明这中间的优化空间。

  • 业务分拆甚至是项目分拆,这样可以更便利地对项目进行水平扩展,尤其是将不同业务部署到不同的服务器上,既减少了资源抢占和干扰,又能更高效地利用当前资源。
  • 假如有一个接口/abc,需要调用func1(), func2(), func3()三个方法获取数据,而这三个方法之间并无关联,并且每个方法均需耗时3秒钟,这种场景如果用顺序同步执行,直接把响应时间拉到了9秒开外,如果用并发处理,将只耗费3秒时间就可以完成任务,对请求进行响应,效率是原来的300%(例子有点极端,但是这种场景的确比较常见)。这是对接口内业务的各个部分进行适当统筹,就可以达到优化的目的。
  • 假如有一个报表系统,每天都有海量数据,生成月报的时候汇总30天的数据将变成一个十分繁重的任务,如果先按日汇总数据,然后在每月初对上个月30个日汇总数据再进行汇总,就将一个繁重的月度汇总任务工作量分摊到每天进行;又或者为计算公式创建冗余的计算字段,而非实时进行处理;为数据库关键数据字段创建索引;引入缓存……这些都是以空间换时间的做法。
  • 正如浏览器请求资源一样,后端在调用接口的时候仍然也会遵循着相同的流程:域名解析到IP,向指定地址发送HTTP请求,等待响应……所以外部接口的地址应当尽可能使用IP,可以省去域名解析的时间。
  • 实在憋不出来了,后端范畴实在过于广泛,就先举这几个例子抛砖引玉了。
        public string Index()
        {            
            List<string> r = new List<string>
            {
                DateTime.Now.ToString("HH:mm:ss.fff")
            };
            //9秒和3秒

            //string result = "";
            //result += Func1();
            //result += Func2();
            //result += Func3();
            //return result;
            List<Task> tasks = new List<Task>();
            tasks.Add(Task.Run(() => r.Add(Func1())));
            tasks.Add(Task.Run(() => r.Add(Func2())));
            tasks.Add(Task.Run(() => r.Add(Func3())));
            Task.WaitAll(tasks.ToArray());
            r.Add(DateTime.Now.ToString("HH:mm:ss.fff"));
            return string.Join("\r\n", r);
        }

操作系统、WEB容器、应用软件优化

这也是一个很杂很宽泛的话题,基本方向有这几个:操作系统本身,比如linux内核调优和tcpip优化,nginx、tomcat、java、各类db的性能调优。相关内容难以在这篇文章里详尽无遗地讲述;再者,一般如果不得不动到这里,说明已经面临很大的压力了,非常遗憾我在工作中没能接触到这么极端的场景,所以也没什么太多值得分享的。网上的各类调优文章多如牛毛,我再去抄一遍也没有意义,这个公众号只是希望能够分享网上找不到的、我自己的经验和对技术的一些粗浅见解,所以这一块内容的优化推荐大家结合自身的应用环境去搜一下相关调优文章参考进行。

内部网络结构、负载均衡和高可用

网络方面,千兆环境是必不可少的,链路的高可用如果有条件的话也推荐组建一下(服务器网卡team bonding、交换机端口汇聚,有机会写一篇端口汇聚的文章),这样将获得更大的带宽,更高的稳定性,这是一项性价比极高的投入。

即使业务压力不大,仍然推荐使用负载均衡,本身负载均衡这项功能并非刚需,但是负载均衡服务提供的高可用却是能从这项技术获得的另一个丰厚回报,这直接提升了服务的稳定性。

DNS和网络接入

网站访客来自世界各地,其中的网络运营商数不胜数。如果使用阿里云这类具备BGP网络的托管服务商,可以不用太在意DNS问题,直接使用外网IP即可。如果不幸只具备多线接入的条件,只能为不同的线路各自设置IP,通过智能DNS建立解析记录,如果更不幸,连智能DNS都没有,就只能通过不同的域名来发布服务了……具体多线接入的方案可以参考 服务器网络选型以及多线接入配置

分类: articles