前端性能优化指南——网络篇

网络,在我们开发的页面的访问过程中,是最开始的一个环节,同时,也是一个非常重要的环节。

当我们在提及网络优化的时候,我们都会说些什么呢。

事实上来讲,如果可以话的,我们对于 IDC,ISP,CDN,BGP,TDO 的优化应该作用是非常明显的。但是一般来讲,这些服务的优化很少可以被前端影响。所以这次就提一下,暂时不做详细的描述。当然了,对我们来说,及时监控不同服务商和各地节点的网络问题也是非常重要的。

那么接下来呢。说到网络性能优化,我们能做的事情主要是这两部分。

对协议本身进行优化

对传输的资源进行优化

我们先来谈谈如何从协议本身的角度进行优化。

协议优化

对于我们前端来说,最为熟悉的网络协议大概就是 HTTP 协议了。

而具体到协议本身,我们能优化的点主要是这么两个方向,一个是基于现有的协议内部的机制,让传输速度更快,而第二种,就是对协议本身进行修改或者替换。

http缓存

既然前面也提到了,对于我们前端来说,http协议是最为熟悉的协议了。所以我们这次就谈谈http缓存。

在我们的日常资源请求中,我们会发现这样一个现象,当一个资源成功请求的时候,有那么几种状态,200,304,200(from cache)。那么这些状态是如何被决定的呢?又各有什么优劣呢。接下来我们通过 http 的字段来一个个分析。

接下来我们先来讲一下 http1.0 有哪些头部可以控制缓存。

HTTP1.0Pragma

Pragma 这个字段可能大部分人不是很清楚,这个字段来源于 http1.0。可以表明一个资源是否需要缓存。

需要注意的是,这个字段没有明确规定http响应时的具体行为,所以并不适合使用。但是在 http1.0 中,缺乏较好的缓存控制策略,如果需要禁止对一个资源的缓存的时候,并且需要兼容到 http1.0 的时候,可以加入 来控制浏览器对资源不进行缓存。

不过如果这次传输支持http1.1,并且你添加了 cache-control 字段,那么 Pragma 字段会被忽略。

Expires

expires是一个很简单的字段,它的格式大概如下

例如看个简单的例子:Expires: Thu, 01 Dec 1994 16:00:00 GMT

这个字段代表了一个资源的过期时间,需要注意的是,expires会在设置了max-age的情况下被忽略。

Last-Modified

Last-Modified 包含源头服务器认定的资源做出修改的日期及时间。 它通常被用作一个验证器来判断接收到的或者存储的资源是否彼此一致。

它的格式如下:

同样,举一个简单的例子。

Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT

当你看到如下的返回头时,说明服务器认为这个资源的最新修改时间如上。我们可以发现,这个时间是精确到秒的。在这种情况下,下一次对资源的请求会带上 If-Modified-Since 这个头部,内容和上次的 Last-Modified 保持一致。如果服务器这边认定的最新修改不一致,就会返回新的资源。

我们发现一个问题,Last-Modified 是基于秒级来处理资源缓存的判断的。这并不靠谱。我们的文件完全可能有更频繁的修改情况。那么接下来我们会看到一些别的缓存策略。

http1.0 就提供了如上这几个头部作为缓存的处理。但是后来大家发现这并不能很好的满足我们的需求。所以在 http1.1 里又添加了如下这些头部。

http1.1cache-control

cache-control,是一个 http1.1 标准中提供用来控制缓存策略的字段。

它拥有很多字段,可以让我们定制不同的资源缓存策略。并且在客户端请求以及服务端响应时都会根据情况下进行一定设置。接下来我们看看都有些什么缓存指令。

下面这些是缓存请求指令,客户端可以在HTTP请求中使用的标准 Cache-Control 指令。

“no-cache” // 需要在请求之前向服务器端确认文件是否修改

“no-store” // 所有内容都不会被缓存到缓存或本地临时文件

“max-age” // 设置文件缓存的时间,单位为s,相对于请求的时间

“max-stale” // 表明客户端可以接受一个过期的资源,可以设置一个时间表明过期时间不超过多少,单位为s

“min-fresh” // 表明客户端希望在指定时间内获取最新的响应,单位为s

“no-transform” // 不允许对资源进行转换,例如代理服务器可能会对图片格式做转换以便减少传输数据量,诸如此类,可能会对头部做修改,这个字段会让代理服务器不可以进行修改

“only-if-cached” // 表示如果存在缓存文件,就使用缓存文件,无论服务器端是否有更新

cache-extension // 自定义扩展字段,如果这个字段无法被理解,会被忽略

下面这些是缓存响应指令,服务器可以在响应中使用的标准 Cache-Control 指令。

“public” // 表明响应可以被任何对象(包括客户端,代理服务器)等缓存

“private” // 表明只能被客户端缓存,代理服务器之类的不可以缓存

“no-cache”

“no-store”

“no-transform”

“must-revalidate” // 缓存在使用之前必须确认服务器端的状态,并且不可以使用过期的资源

“proxy-revalidate” // 这个字段和上面的字段效果一致,但是只会对共享缓存(例如代理服务器缓存)生效。

“max-age”

“s-maxage” // 和max-age类似,但是仅仅只适用于共享缓存

cache-extension

这些头部里,我们最熟悉的可能就是 max-age。单位为秒。在 max-age 被设定过的情况下。我们的资源从第一次请求返回的时间开始,在对应的时间范围内不会再进行资源的请求,会直接从缓存(memory cache or disk cache)中读取。

Etag

Etag 是一个和 Last-Modified 有点类似的属性。但是上面我们也说过了,Last-Modified 基于秒来做缓存是存在一定问题的。Etag 就可以解决我们的问题。

Etag 是一个基于文件内容生成的值。当对应的文件内容修改时,这个值一定要被修改。

memory cache or disk cache

说到这里就顺便提一下上面的 memory cache or disk cache。

在新版本的 chrome 里,from cache 这种状态做了一些新的处理。分成了 memory cache 和 disk cache。顾名思义,memory cache 是从内存中读取资源,而 disk cache 则是从硬盘文件中读取资源。

两者的触发时机,可以查看如下的两篇文章来思考。

memoryCache和diskCache流程详解:

http://blog.csdn.net/m632587166/article/details/50732205?locationNum=14

由memoryCache和diskCache产生的浏览器缓存机制的思考:

https://segmentfault.com/a/1190000011286027

缓存策略的优先级

上面介绍了这么多种缓存手段,相信大家可能会有点疑惑。网络对于这些缓存策略的整理很多,找了一张图来说明一下。

分离优化

对于我们早期的服务,前后端都是完全在一起的。但是我们的静态资源其实非常需要被剥离出来。那么我们就需要做分离优化。使用最优的静态资源服务架构。

并发优化

浏览器有着统一域名下,并发请求数量的限制。限制如下图。

那么如果我们的静态资源数量过多,就需要考虑到是否需要将资源分离到不同域名下进行加载。或者对于资源进行合并或者做 combo 处理。

上面提了一些常见的在 HTTP1 的时代的优化方式,但是我们的互联网已经发展了很久了。HTTP1 也不再是唯一的选择。接下来我们再谈谈 HTTP2 的一些优化方式。

HTTP2首部压缩技术

众所周知的是,HTTP1 的头部比较庞大,如果我们的请求次数较多,头部的传输也是一笔不小的开销。

对于首部压缩的技术细节,在 RFC7541 中有比较详细的规定。并且后面还有范例教程来说明如何压缩。所以我们不会太过详细的描述。

但是可以简单的说明一下。主要是需要维护一份静态字典,动态字典。以及对于字典中不存在的数据进行哈夫曼编码。

其中静态字典是提前规定好的,会根据字典对于协议首部进行处理。

而动态字典则会在第一次传输后建立。

举个简单的例子,如果我们第一次传输了如下的协议内容。

前面的

都会被静态字典所处理

而后面的

则会被动态字典记录,当下次请求时,authority 部分也会被压缩。

多路复用

HTTP2 的多路复用技术,使得我们可以在同一个 TCP 连接里发起多个请求。这样可以提高我们的资源加载速度,也就不需要我们考虑分离域名来优化资源了。

资源优化

减少不必要的资源

说实话这是一个非常简单直接的方法,但是也经常被大家所忽略。随着我们工程复杂度的提高,代码结构的复杂化,经常会出现引入了很多不必要的资源,重复的资源的情况,这时候,直接减少加载的资源数量和大小是最为直接也有效的方法。

数据压缩

压缩算法是非常常用并且有效的办法。例如说我们一般会用 Gzip 来进行压缩。

Gzip 严格来讲是一种文本格式,其中使用了 DEFLATE 算法来进行压缩。而 DEFLATE 算法则是 LZ77 和哈夫曼编码的一个组合体。关于具体的内部组成,可以查看 RFC1952,个人认为写的还是比较通俗易懂。

Gzip由如下几部分组成。

10字节的头,包含幻数、版本号以及时间戳

可选的扩展头,如FEXTRA,FNAME,FCOMMENT,FHCRC等

文件体,包括DEFLATE压缩的数据

8字节的尾注,包括CRC-32校验和以及未压缩的原始数据长度

10字节的头部如下形式:

前面的两个 id 是两个固定的值。分别为 (0x1f, \037), (0x8b, \213) 一般来讲我们看到的就是 1f 8b。这种就是我们常说到的幻数,魔法数字。

CM 的意思是 Compression Method。就是选择的压缩方法,刚才说过了,Gzip 是一种格式而非算法,内部的压缩算法是可以自由选定的。这里我们一般会选用 8,意思是使用 DEFLATE 算法。而 0-7 目前是保留字。

FLG 的值也是 0-7 之间,表示了是否设置之前所说到的 FEXTRA,FNAME,FCOMMENT,FHCRC 等字段。

MTIME 则是一个 unix 时间戳。

XFL 这个字段在 CM 为 8 时有两个值可选。分别是 2 和 4。2 代表使用压缩比最大,时间最久的压缩方式。而 4 则代表着使用最快速的算法来压缩。

OS 顾名思义,代表了使用的操作系统。具体的值如下列表。

0 - FAT filesystem (MS-DOS, OS/2, NT/Win32)

1 - Amiga

2 - VMS (or OpenVMS)

3 - Unix

4 - VM/CMS

5 - Atari TOS

6 - HPFS filesystem (OS/2, NT)

7 - Macintosh

8 - Z-System

9 - CP/M

10 - TOPS-20

11 - NTFS filesystem (NT)

12 - QDOS

13 - Acorn RISCOS

255 - unknown

之后就是一些具体的使用 DEFLATE 算法压缩后的数据。对于 DEFLATE 算法具体实现,这里暂时不进行讨论。有兴趣的同学可以查阅 RFC1951。

Gzip 对基于文本的内容的压缩效果最好,在压缩较大文件时往往可实现高达 70-90% 的压缩率,而如果对已经通过替代算法压缩过的资产(例如,大多数图片格式)运行 Gzip,则效果甚微,甚至毫无效果。

另外在有的情况下,Gzip 有时会把文件压缩的更大。

事实上我们不止可以用 Gzip 来进行压缩。

在 RFC6713 中,有两种 Media Types 可以让我们选择压缩的模式。分别是 ‘application/zlib’ 和 ‘application/gzip’。不过我们一般还是用 Gzip 更多。

图片优化

关于图片的优化,我们有很多工作可以做,这里稍微多讲一些。

图片压缩

我们经常会对图片进行压缩,而压缩分为无损压缩和有损压缩。

在这里我们举例两种常见的图片格式,png 和 jpg。

png

png 在压缩时一般会采用无损压缩的方式。当然实际上来讲可能会存在一定的信息丢失。对于 png 我们知道,有 png8 和 png24 两种常见的格式。在压缩的过程中,我们一般从如下几个角度来进行压缩:

减少色值。例如说我们本身常用的 png24 转化成 png8。这样的话,图片的大小会有显著的减少。但是一般来讲,会有一定的颜色信息的丢失。对于如何更好的减少颜色丢失。pngquant 用了中位切分算法,pngnq 用神经网络算法。

去除图片中无用的一些数据区块。

Deflate 压缩。Deflate 算法我们刚才在 Gzip 中进行了介绍,事实上 png 压缩也有对于 Deflate 算法的应用。

jpg

jpg 在压缩时,我们则是更多的使用了有损压缩的技术。那么我们在压缩 jpg 图片时,运用有损压缩技术都要做些什么呢?

既然是有损,那么就需要我们界定什么信息是可以丢失的,什么信息是不可以丢失的。

为了寻找重要和不重要的信息。需要做如下步骤。

在进行 jpg 压缩时,我们会将图片切分为 8*8 的小块。然后对于 RGB 的颜色空间进行转换,使用 YCbCr 来表示。接着使用 DCT(离散余弦变换),恩,也就是傅里叶变换中的一种形式。之后我们会得到一个新的二维矩阵,接着,对于二维矩阵做量化处理。最后把量化后的二维矩阵转化为一维数组,接着对一维数组做哈夫曼编码,就得到了最终压缩后的结果。

在中间的量化处理的过程中,我们会处理掉一部分不重要的信息,就完成了压缩。

交错式图片

这里其实和我们的网络传输无关,但是和图片的优化本身有关。

我们平常的图片,在加载的时候,会从上到下,整体一点一点的加载出来。但是这样会引起不太友好的用户体验。

这时候我们就需要使用交错式图片。交错式图片和普通图片的区别就在于加载的时候是从模糊逐渐到清晰,而不是一点一点出现,给用户更好的体验。

图片的缩放,裁剪

对于我们来说,在合适的场景下,使用合适大小和尺寸的图片也是非常重要的。所幸我们现在基本都有内部的图片服务(或者第三方的图片服务),都是可以支持对于图片资源的自定义裁剪的。

WebP

WebP,是一种同时提供了有损压缩与无损压缩(可逆压缩)的图片文件格式,派生自视频编码格式VP8,被认为是WebM多媒体格式的姐妹项目,是由Google在购买On2 Technologies后发展出来,以BSD授权条款发布。

这是我们现在经常使用的一种图片格式,可以大幅减少图片的大小。只是需要考虑一定的兼容性。

结束语

今天的性能优化指南就到这里结束,如果有什么疑问和建议,欢迎提出。

文章中涉及到了非常多的 RFC 和规范标准,以及算法。有兴趣的同学可以之后自己进行更加深入的学习和思考。

本文来自企鹅号 - 前端配送站媒体

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏黑白安全

绕过CDN获取网站IP地址

基于masscan扫描IP端中开放的80端口,程序自动连接每个IP测试,筛选出符合条件的ip保存到result.txt 后续程序会提供”基于扫描子域名获取IP段...

1123
来自专栏机器之心

资源 | Parris:机器学习算法自动化训练工具

3509
来自专栏Pythonista

macos修改vmware Fusion的NAT网络

1.点击vmware Fusion > 偏好设置 > ( command + , )网络

872
来自专栏Spark学习技巧

基石 | Flink Checkpoint-轻量级分布式快照

前面两篇,一篇是spark的driver的Checkpoint细节及使用的时候注意事项。一篇是flink的Checkpoint的一些上层解释。本文主要是将fli...

2851
来自专栏xingoo, 一个梦想做发明家的程序员

Logstash为什么那么慢?—— json序列化

今天跟峡谷金桥聊天,询问起Logstash的性能,金桥提示说Logstash中json的序列化是浪费性能的一方面。于是便有了下面的测试: 第一步,造数据 ...

2239
来自专栏FreeBuf

爬虫采集去重优化浅谈

以前在做漏洞Fuzz爬虫时,曾做过URL去重相关的工作,当时是参考了seay法师的文章以及网上零碎的一些资料,感觉做的很简单。近来又遇到相关问题,于是乎有了再次...

3486
来自专栏机器学习算法原理与实践

scikit-learn 和pandas 基于windows单机机器学习环境的搭建

    很多朋友想学习机器学习,却苦于环境的搭建,这里给出windows上scikit-learn研究开发环境的搭建步骤。

822
来自专栏Java3y

操作系统第五篇【死锁】

3054
来自专栏linux驱动个人学习

Linux分页机制之概述--Linux内存管理(六)

在虚拟内存中,页表是个映射表的概念, 即从进程能理解的线性地址(linear address)映射到存储器上的物理地址(phisical address).

3801
来自专栏ericzli

Jetson TX1上安装Tensorflow Serving遇到的问题总结

本文的目的是分享在TX1上安装Tensorflow Serving时遇到的主要问题,避免重复踩坑。

3633

扫码关注云+社区

领取腾讯云代金券