网络请求全过程及其优化空间

一、网络请求全过程总览

从用户输入URL到网页呈现给用户都经过了哪些主要过程?主要有八大过程:

  1. 移动网络连接(移动端特有)
  2. DNS解析
  3. IP寻址
  4. TCP连接
  5. Request
  6. 服务器处理
  7. Response
  8. 页面渲染

图中为第一次请求过程,皆不考虑缓存。

注:本文讨论的请求都是HTTP请求,其他请求类型暂不考虑

二、各过程详解及其优化空间

上图中的所有过程对于一个前端er来说都是必须了解的,接下来,本文对于各个过程逐一进行分析。
各过程分析都试着回答以下三个问题:

  1. 这个过程是什么?
  2. 这个过程存在什么问题?
  3. 如何解决存在的问题?

1、移动网络连接

近些年来,移动互联网快速发展,移动web成为主流,对于页面性能提出了更高的挑战。

移动网络连接过程概述

下图是手机连接到互联网的完整过程(4G网络为例)。

主要有四个过程:

  • 控制面延迟:由RRC(无线电资源控制器)协商和状态切换导致的固定的、一次性的时间,不同的移动网络这个时间不一样。其中4G网络为50-100ms,3G网络为200-2500ms,而2G一般需要几秒。
  • 用户面延迟:应用的每个数据分组从设备到无线电信号塔之间都要花固定的时间,少于5ms。
  • 核心网络延迟:分组从无线电信号塔传输到分组网关的时间,因运营商而不同。
  • 互联网路由延迟:从运营商分组网关到公共互联网的目标地址所花的时间,可变。

上述四个过程,我们唯一可以把控的是控制面延迟,也就是RRC(无线电资源控制器)协商导致的延迟。下图为LTE RRC状态机。

  • RRC空闲:设备的无线电模块处于低功率状态(<15mw),只监听来自网络的控制信号。
  • RRC连接:设备的无线电模块处于高功率状态(1000-3500mw),要么传输数据,要么等待数据。

其中RRC连接状态还有多个子状态:

  • 连续接收:最高功率状态,网络环境就绪,已分配网络资源。
  • 短不连续接收(短DRX):网络环境就绪,未分配网络资源。
  • 长不连续接收(长DRX):网络环境就绪,未分配网络资源。

RRC协商的问题

RRC是专门设计用来解决高耗电问题的,经过RRC协商,让手机不是一直处于高功率状态,保证手机的使用时间。虽然RRC解决了部分高耗电问题,但是它引入了新的问题,RRC协商过程中各状态的切换都有延迟,特别是从RRC空闲状态切换到RRC连接状态,也就是上文所说的控制面延迟。如果RRC状态频繁的在空闲和连接状态之间切换,会极大的影响用户体验。

另外,虽然RRC是专门设计来解决高耗电问题,但是只有想传输数据,就必须切换到高功率模式,就会耗电。节约用电是用户关心,应用开发者必须考虑的问题。

节约用电与延迟优化

RRC不断切换状态导致的延迟和耗电问题,都是必须关注的问题,下面分别对其探讨。

如何节约用电:

  • 避免轮询
    移动应用最忌轮询,轮询使手机一直处于高功耗模式,严重耗费电量。
  • 预取资源
    预先在无线模块活动时取后面需要用到的资源,或者非关键的请求推迟到无线模块活动时进行。
  • 数据缓存
    最节约电量的就是不请求,完全不需要用到无线模块。
  • 合并请求
    在移动通信中,无论传输数据大小,每次数据传输都会触发一次大约10s的耗电。

如何处理RRC状态切换的延迟:

  • 提前唤醒
    如果对延迟敏感,对速度要求高,可以考虑提前唤醒无线模块,使接下来的请求无状态切换延迟。但是这点是以耗费电池电量为代价,需慎重。
  • 数据缓存
    最快的请求就是不请求。
  • 使用不同策略
    现在2G,3G,4G网络并存,延迟差距也很大,针对每种网络应该有不同的应对策略。比如2G和4G采用不同的交互提示,给用户以延迟预期。

移动网络请求比有线(及WIFI)网络多了一个移动网络连接过程,后续过程二者一致。

2、DNS查询

网络通讯大部分是基于TCP/IP的,而TCP/IP都是基于IP地址的,所以计算机在网络上通讯只能识别"192.168.0.1"之类的IP地址。但是这样给用户记住网站造成很大的困难,我们很难记住一个个IP地址,所以就有了域名,域名与IP地址存在映射关系。DNS就是解决这个映射关系的系统。

什么是DNS查询

DNS查询就是将域名解析成IP地址的过程。典型过程如图所示。

  1. 用户电脑的解析器向LDNS(也就是Local DNS,互联网服务提供商ISP)发起域名解析请求,查询www.google.com的IP地址
  2. 在缓存没有命中的情况下,LDNS向根服务器查询www.google.com的IP地址
  3. 根告诉LDNS,我不知道www.google.com对应的IP,但是我知道你可以问com域的授权DNS,这个域归他管
  4. LDNS向com的授权服务器问www.google.com对应的IP地址
  5. com告诉LDNS,我不知道www.google.com对应的IP,但是我知道你可以问google.com域的授权DNS,这个域归他管
  6. LDNS向google.com的授权服务器问www.google.com对应的IP地址
  7. google.com查询自己的zone文件,找到了www.google.com对应的IP地址,返回给LDNS
  8. LDNS本地缓存一份记录,把结果返回给用户电脑的解析器

我们可以用dig指令来跟踪DNS查询过程,使用+trace选项后,dig会从根域查询一直跟踪直到查询到最终结果,并将整个过程信息输出出来。

DNS查询问题

很明显DNS查询需要耗费时间,一般耗时在20-120ms,但是在2G和3G下会更久。DNS查询结束后都会缓存本次查询结果,但是缓存时间一般很短。

DNS查询优化

DNS查询优化的关键点就是快速完成DNS查询,有如下方案:

  • DNS预处理
    DNS预处理指的是提前解析后面需要用的DNS,把解析结果缓存起来,后面用到时可以节省DNS解析的时间。在页面中加入link标签,指出rel属性是"dns-prefetch",并通过href属性指出需要解析的域名地址。
    目前大部分浏览器都支持DNS预处理。
  • 减少域名个数
    因为现在浏览器的连接数一般限制为6个,我们通常会采用多域名的方案来提升并行连接数,来加速页面加载,但是如果域名数量太多,DNS查询时间又会影响性能,这里需要一个平衡,目前来说2-4个域名为佳。

3、IP寻址

经过DNS查询获得IP地址过后,我们可以与服务器通信了?还不可以,我们还是不知道服务器在哪,还需要通过IP寻址找到服务器。
IP寻址实际就是ARP(地址解析协议)地址解析过程,将IP地址解析为以太网MAC地址,获得MAC地址后,数据才可以封装成帧通过数据链路层发送。

ARP解析分为同网段解析过程和不同网段解析过程,不同网段的解析过程,实际是重复ARP同网段的解析过程。具体详情可查看《TCP/IP详解 卷1:协议》。

4、TCP连接

IP寻址完成后,客户端和服务器可以开始通信了。几乎所有的HTTP流量都是通过TCP传送的,本小节就讨论TCP连接。

TCP连接的三次握手

所有的TCP连接都要经过三次握手。

  • SYN
    客户端选择一个随机序列号x,并发送一个SYN分组,其中可能还包括其他TCP标志和选项。
  • SYN ACK
    服务器给x加1,并选择自己的一个随机序列号y,追加自己的标志和选项,然后返回响应。
  • ACK
    客户端给x和y加1并发送握手期间的最后一个ACK分组。

上述官方一点的说法摘自《Web性能权威指南》。通俗来讲就是:

  1. 客户端:我来了,你在吗?
  2. 服务器:我在的,你还在吗?
  3. 客户端:我还在。

三次握手完成后,客户端和服务器之间才可以开始通信。

TCP的慢启动

发送端和接收端在连接建立之初,谁也不知道可用带宽是多少,所以不知道该把发送数据的速度设为多少合适。这个时候需要一个估算机制,来动态改变传输速度。慢启动就是在这种情况下诞生的。

建立连接后,第一次往返,服务器只会向客户端发送其自身设置的cwnd(拥塞窗口大小)大小的数据,也就是图中的4个TCP段(RFC2581标准),每次往返都会令其加倍。这就是TCP的慢启动过程。

拥塞预防

TCP调节传输速度,主要依赖丢包反馈机制。慢启动以保守的窗口初始化连接,随后每次往返都会成倍提高传输数据量,直到超过接收端的流量控制窗口或者有分组丢失,此时拥塞预防算法介入。
拥塞预防算法把丢包作为网络拥塞的标志,必须调整拥塞窗口大小,避免更多的包丢失。

图中显示的拥塞预防算法遇到丢包直接把传输数据量减半,这是最初TCP使用的AIMD算法,太过保守。目前最新的算法是PRR(比例降速)(RFC6937标准)。根据谷歌测算,使用新算法后,因丢包造成的平均连接延迟减少了3%-10%。

带宽延迟积

发送端和接收端之间在途中未确认的最大数据量,取决于拥塞窗口和接收窗口的最小值。接收窗口一般固定,而拥塞窗口则由发送端根据拥塞控制和预防算法动态调整。

无论发送端发送的数据还是接收端接收的数据超过了未确认的最大数据量,都必须停下来等待另一方ACK确认完成某些分组才能继续,要等待的时间取决于往返时间。

这个时候就有了带宽延迟积的概念:

BDP (带宽延迟积)
数据链路的容量与其端到端延迟的乘积。这个结果就是任意时刻处于在途未确认状态的最大数据量。

现假设已知:

往返时间:56ms
拥塞窗口和接收窗口最小值:64k
客户端到服务端的带宽:2M/S

那么使用的带宽最大为1.116M/S,计算过程如下:
64Kb = 64 / 1024 Mb = 0.0625Mb
0.0625 * 1000 / 56 Mb/s = 1.116Mb/s
带宽延迟积为:
2 * 0.056 M = 0.112M
0.112M = 0.112 * 1024 kb = 114.7kb

可以看出,往返时间固定,带宽固定的情况下,不合适的窗口大小严重限制的TCP的性能,浪费带宽。

针对TCP的优化

优化TCP性能的回报是丰厚的,无论什么应用,性能提升可以在与服务器的每个连接中体现出来。

  • 把服务器内核升级至最新版本(Linux:3.2+)
    3.2+的服务器内核好处是优化了拥塞预防算法,采用的是更先进的PRR。另外,默认采用了初始拥塞窗口为10的设置,加速了慢启动过程。
  • 确保初始拥塞窗口为10
    初始拥塞窗口设为10后,可以在TCP在第一次往返就传输较多数据,性能提升巨大。
  • 确保启用窗口缩放
    启用窗口缩放可以增大最大接收窗口大小,可以让高延迟的连接达到更好的吞吐量,避免浪费带宽。
  • 尽最大可能重用已建立的TCP连接
    尽量开启keep-alive,重用已有的TCP连接,把慢启动和其他拥塞控制机制的影响降到最低。

5、Request与Response

HTTP请求这部分把Request和Response合在一起,是前端er最熟悉的部分,不准备花很多笔墨讲,主要分享下我对HTTP优化的看法,大体可以从三个方面考虑。

  • 协议本身的优化
    目前我们大部分用的HTTP1.1版本,估计还有少量停留在1.0,实际上对于HTTP的优化,最好的是升级到HTTP2.0,很多东西在协议层面就优化了,甚至很多在1.x版本所用的优化,在2.0都成为了反模式。
  • 减少传输大小
    现存很多流行的优化方式都是从减少传输大小的角度出发,比如压缩、开启GZIP、减少重复代码、利用缓存等,想办法减少传输大小,相应的就能提升性能。
  • 减少请求数
    从另外一个角度出发就是减少请求数,比如合并请求、按需加载、利用缓存等。

6、服务器处理

请求到服务器后,服务器开始处理,一般耗时100ms左右。作为前端,懂一些后台东西能够更好的与后台童鞋配合,Node出来后,更是拓展了前端的边界,有机会处理服务端的工作。
下图绿色部分是部门后台架构的简单描述,在CGI层上再加一层Node直出是现在很火的直出方案,经兄弟部门验证对性能提升巨大,作为团队的一个尝试方向。

7、页面渲染

请求返回给浏览器,浏览器获得相应资源,就开始渲染页面。

渲染过程

浏览器渲染页面主要有五个过程:

  1. 处理 HTML 标记,构建 DOM 树
    浏览器首先处理HTML,将字节转换为字符,确认符号,将符号转换为节点,然后构建 DOM 树。整个过程需要一些时间,处理大量 HTML 时更是如此。
  2. 处理 CSS 标记,构建 CSSOM 树
    页面有CSS样式时,与 HTML 一样,我们需要将收到的 CSS 规则转换为浏览器可以理解、能够处理的东西。因此,我们再重复一次与处理 HTML 非常相似的过程。CSS字节会转换为字符,然后转换为符号和节点,最后生成CSSOM(CSS 对象模型)。
  3. 将 DOM 树和 CSSOM 树融合成渲染树
    DOM树和CSSOM树构建好后,浏览器开始将它们融合成渲染树,这样它既网罗页面上所有可见的DOM内容,又涵盖每个节点的CSSOM样式信息。
    浏览器大概做了如下工作:
    • 从 DOM 树的根节点开始,遍历每个可见的节点;
    • 给每个可见节点找到相应匹配的 CSSOM 规则,并应用这些规则;
    • 发射可见节点,连带其内容及计算的样式。
  4. 布局
    有了渲染树,可以进入布局阶段了,这阶段主要计算各个节点在设备窗口中的准确位置和尺寸。为了弄清每个对象的准确尺寸和位置,浏览器从渲染树的根节点开始遍历计算。布局过程会输出一个盒模型,它精确捕捉每个元素在窗口中的准确位置和尺寸,所有的相对度量单位都被转换为屏幕上的绝对像素位置。
  5. 在屏幕上绘制各个节点
    这个阶段把每个节点信息转换为屏幕像素,大功告成。

优化关键渲染路径

页面渲染都需要经过上述五步,为了尽快完成首次呈现,我们需要优化以下三个变量。

  • 尽量减少关键资源数量
  • 尽量减少关键字节数
  • 尽量缩短关键路径长度

谷歌的Ilya总结了优化关键呈现路径常规步骤:

  1. 分析和描述关键路径:资源数量、字节数、长度。
  2. 尽量减少关键资源数量:删除相应资源、延迟下载、标记为异步资源等等。
  3. 优化剩余关键资源的加载顺序:您需要尽早下载所有关键资源,以缩短关键路径长度。
  4. 尽量减少关键字节数,以缩短下载时间(往返次数)。

详情参见关键呈现路径系列文章

三、如何做到第一次请求最快呈现给用户?

网络请求全过程的各个过程已经讲完,那么问题来了,根据上面说的,如何才能做到第一次请求最快呈现给用户?
本文给的答案是:

  1. 页面大小控制在14K
  2. 采用内联样式
  3. 内联必要的JS

四、可利用的缓存

前文介绍的整个请求过程都没有涉及到缓存,实际上第二次请求时,有很多缓存的地方可供利用,有利于提升页面性能。
主要有五个方面的缓存:

  1. 浏览器缓存
  2. 本地缓存
  3. DNS缓存
  4. CDN缓存
  5. 服务器缓存

有必要在应用中充分利用缓存,善用浏览器缓存和本地缓存可以减少请求数和请求字节,极大提升页面性能;DNS缓存和CDN缓存可以帮助节省请求时间;服务器缓存在高并发场景对保护后端有重要价值。

五、小结

各个过程可关注的重点:

  1. 移动网络连接过程
    关注控制面延迟和节约用电。
  2. DNS查询
    关注DNS缓存、DNS预处理。
  3. IP寻址
    关注寻址过程,应用指令定位网络问题。MCA:traceroute,Windows:tracert。
  4. TCP连接
    关注三次握手、慢启动、拥塞预防、带宽延迟积。
  5. Request与Response
    重点内容,关注报文各个字段,性能优化。
  6. 服务器处理
    关注移动端直出。
  7. 页面渲染
    关注渲染过程,关键路径分析。

本文抛砖引玉对各个过程逐一进行分析,指出一些值得大家关注的方向,一般来说,这些已足够应用。实际各个过程都可以深入进去,有兴趣可以做相应研究。

你可能感兴趣的:(web优化)