构建高性能WEB之HTTP首部优化

2015/10/03 · HTML5,
JavaScript ·
HTTP

本文作者: 伯乐在线 –
十三号线上的蝼蚁
。未经作者许可,禁止转载!
欢迎加入伯乐在线 专栏作者。

在连续写了两篇关于「HTTP/2 与 WEB
性能优化」的文章后,今天来写这个系列的最后一篇。在正式开始之前,我们先来简单回顾下之前两篇文章:

在「HTTP/2 与 WEB 性能优化(一)」这篇博客中,我主要写了 HTTP/2 中的
Server Push 给 WEB 性能优化带来的便利,今天继续来聊一聊 HTTP/2
其他方面的改变。

今年二月份,Google 宣布将在 16 年初放弃对 SPDY 的支持,随后 Google
自家支持 SPDY 协议的服务都切到了 HTTP/2。今年 5 月 14 日,HTTP/2 以 RFC
7540 正式发布。目前,浏览器方面,Chrome 40+ 和 Firefox 36+ 都正式支持了
HTTP/2;服务器方面,著名的 Nginx 表示会在今年底正式支持 HTTP/2。

LEMP 自动化编译脚本下载:
(定期更新,欢迎多测试,找bug)
介绍参考:

0×00 前言

在讨论浏览器优化之前,首先我们先分析下从客户端发起一个HTTP请求到用户接收到响应之间,都发生了什么?知己知彼,才能百战不殆。这也是作为一个WEB开发者,为什么一定要深入学习TCP/IP等网络知识

「HTTP/2 与 WEB 性能优化(一)」的结论是:HTTP/2 的 Server Push
机制,可以让重要的 JS、CSS 等资源尽快加载,从而不再需要 HTTP/1
中「将重要资源内联在页面头部」的优化方案了。

我们知道,HTTP/2 并没有改动 HTTP/1
的语义部分,例如请求方法、响应状态码、URI
以及头部字段等核心概念依旧存在。HTTP/2
最大的变化是重新定义了格式化和传输数据的方式,这是通过在高层 HTTP API
和低层 TCP 连接中引入二进制分帧层来实现。这样改动的好处是原来的 WEB
应用完全不用修改,就能享受到协议升级带来的收益。

不得不说这几年 WEB 技术一直在突飞猛进,爆炸式发展。昨天还觉得 HTTP/2
很遥远,今天已经遍地都是了。对于新鲜事物,有些人不愿意接受,觉得好端端为什么又要折腾;有些人会盲目崇拜,认为它是能拯救一切的救世主。HTTP/2
究竟会给前端带来什么,什么都不是?还是像某些人说的「让前端那些优化小伎俩直接退休」?我打算通过写一系列文章来尝试回答这个问题,今天是第一篇。

作者:hamgua(哈密瓜)
)

0×01 到底发生什么了?

当用户发起一个HTTP请求时,首先客户端将与服务端之间建立TCP连接,成功建立连接后,服务端将对请求进行处理,并对客户端做出响应,响应内容一般包括响应主体。
(此处我们仅简单说明,但真实的一次请求其中发生的事情是相当复杂的,这里贴条连接,讲得比较详细)。
从输入 URL
到页面加载完成的过程中都发生了什么事情?

「HTTP/2 与 WEB 性能优化(二)」的结论是:HTTP/2 支持了多路复用,HTTP
连接变得十分廉价,之前为了节省连接数所采用的类似于「资源合并、资源内联」等优化手段不再需要了。多路复用可以在一个
TCP 连接上建立大量 HTTP 连接,也就不存在 HTTP 连接数限制了,HTTP/1
中常见的「静态域名」优化策略不但用不上了,还会带来负面影响,需要去掉。另外,HTTP/2
的头部压缩功能也能大幅减少 HTTP 协议头部带来的开销。

HTTP/2 的连接

提出问题


建立TCP连接

为了进行可靠的数据传输,TCP在进行发送数据之前,会进行TCP三次握手,以此确定接收方能够成功收取传输的数据,而建立连接的过程,必然是要耗费系统资源,以及时间资源的。

但 HTTP/2 并不是万能的,并不是说用了 HTTP/2
就不再需要性能优化了。我在本系统第二篇文章末尾写到:

HTTP/1
的请求和响应报文,都是由起始行、首部和实体正文(可选)组成,各部分之间以文本换行符分隔。而
HTTP/2
将请求和响应数据分割为更小的帧,并对它们采用二进制编码。下面这幅图中的
Binary Framing 就是新增的二进制分帧层:

我们知道,一个页面通常由一个 HTML
文档和多个资源组成。有一些很重要的资源,例如头部的 CSS、关键的
JS,如果迟迟没有加载完,会阻塞页面渲染或导致用户无法交互,体验很差。如何让重要的资源更快加载完是我本文要讨论的问题。

欢迎转载,转载时请务必以超链接形式标明文章出处和作者信息及本声明。

服务端处理并响应

当服务端接收到客户端发送来的请求之后,如果请求内容是静态资源,服务端会从硬盘中取出静态资源,然后将静态资源放在响应主体中,发送给客户端。如果是动态资源,服务端首先取出资源,并通过业务逻辑操作,动态生成最终的响应主体,然后发送给客户端。

据官方预测,HTTP/1 至少还需要 10 年才能彻底退出历史舞台,另外尽管 HTTP/2
协议允许脱离 TSL 部署,但 Chrome 和 Firefox 都表示不支持非 TLS 的
HTTP/2,之后很可能一个网站会同时提供 HTTP/1.1、HTTP/1.1 over TLS、HTTP/2
over TLS
三种服务。如何在每种情况下,都能给用户提供最好的体验,需要更加深入的优化研究和更加精细的优化策略。

 图片 1

HTTP/1

更新时间:2011-03-06

客户端渲染

客户端接受到服务端传输过来的网络资源,然后进行渲染,绘制等,最终展示给用户。

实际上,除了前两篇文章中提到的这些需要为 HTTP/2
做出调整的优化策略之外,其余大部分 HTTP/1 时期的优化策略依然有效。HTTP/1
的 WPO 并不是什么新鲜话题,大家早就熟门熟路了,本文只打算列举其中几个:

先来看看这几个概念:

分析

平台搭建环境:

0×02 优化点在哪里?

通过简单的了解,我们了解到TCP建立连接是有资源消耗,时间消耗的,那么如果我们无需每次简历TCP连接,那是否可以提高网站的性能呢?答案是肯定的。

  • 优化点1:减少TCP连接

我们知道,在获取资源的时候,以获取速度从慢到快是:网络资源->本地硬盘资源->本地内存资源。而网络资源也分硬盘资源以及内存资源。并且网络资源的传输,也是有相当大的时延的。

  • 优化点2:对数据进行缓存
  • 优化点3:减少数据传输量

启用压缩

帧(Frame):HTTP/2 数据通信的最小单位。帧用来承载特定类型的数据,如 HTTP
首部、负荷;或者用来实现特定功能,例如打开、关闭流。每个帧都包含帧首部,其中会标识出当前帧所属的流;

我们先来考虑资源外链的情况。通常,外链资源都会部署在 CDN
上,这样用户就可以从离自己最近的节点上获取数据。一般文本文件都会采用
gzip
压缩,实际传输大小是文件大小的几分之一。服务端托管静态资源的效率通常非常高,服务端处理时间几乎可以忽略。在忽略网络因素、传输大小以及服务端处理时间之后,用户何时能加载完外链资源,很大程度上取决于请求何时能发出去,这主要受下面三个因素影响:

CentOS5.5 x86_64

0×03 如何进行优化?

本篇文章主要说的优化点是与HTTP首部有关的优化,或者说是与浏览器端有关的优化,其它优化这里暂不赘述。

压缩的目的是让传输的数据变得更小。我们的线上代码(JS、CSS 和
HTML)都会做压缩,图片也会做压缩(PNGOUT、Pngcrush、JpegOptim、Gifsicle
等)。对于文本文件,在服务端发送响应之前进行 GZip
压缩也很重要,通常压缩后的文本大小会减小到原来的 1/4 –
1/3。对代码进行内容压缩已经有成熟的工具和标准流程了,而服务端的 GZip
更是标配,所以「压缩」是一项收益投入比很高的优化手段。

消息(Message):指 HTTP/2 中逻辑上的 HTTP
消息。例如请求和响应等,消息由一个或多个帧组成;

浏览器阻塞(Stalled):浏览器会因为一些原因阻塞请求。例如在 rfc2616
中规定浏览器对于一个域名,同时只能有 2 个连接(HTTP/1.1
的修订版中去掉了这个限制,详见
rfc7230,因为后来浏览器实际上都放宽了限制),超过浏览器最大连接数限制,后续请求就会被阻塞。再例如现代浏览器在加载同一域名多个
HTTPS 资源时,会有意等第一个 TLS 连接建立完成再请求其他资源;

目标:

持久连接:Keep-Alive

HTTP连接设计之初是请求-响应-关闭,也就是每建立一次HTTP连接,只能进行一次资源请求,当需要在同一目标服务器上获取多个资源的时候,就需要多次建立HTTP连接,而这个多次建立连接的过程,便降低了网站的性能。

于是,出现了Connection:Keep-Alive,人称持久连接。Keep-Alive避免了建立或者说重新建立连接的过程,减少了HTTP连接。

而与此配套的有Keep-Alive:timeout=120,max=5

其中,timeout=120 是指这个TCP通道保持120S,max=5 指这个TCP通道最多接收5个HTTP请求,之后便自动关闭该连接。

使用 HTTP 缓存

流(Stream):存在于连接中的一个虚拟通道。流可以承载双向消息,每个流都有一个唯一的整数
ID;

DNS 查询(DNS Lookup):浏览器需要知道目标服务器的 IP
才能建立连接。将域名解析为 IP 的这个系统就是 DNS。DNS
查询结果通常会被缓存一段时间,但第一次访问或者缓存失效时,还是可能耗费几十到几百毫秒;

构建高性能的LEMP环境提供WEB服务

修改时间:Last-Modified 和 If-Modified-Since

Last-Modified首部是服务端对客户端的HTTP响应所加的一个与缓存有关的HTTP首部,该首部标记了所请求资源在服务端的最后修改时间。类似:

Last-Modified : Fri , 12 May 2015 13:10:33 GMT

当客户端发现HTTP响应头中有Last-Modified,会对资源进行缓存,在下次请求资源时,在HTTP请求头中添加If-Modified-Since首部,首部中将会添加上次成功请求资源时响应头部的Last-Modified属性值,即:

If-Modified-Since : Fri , 12 May 2015 13:10:33 GMT

当服务端接收到的HTTP请求中,发现有If-Modified-Since头部时,会将该属性值与请求资源的最后修改时间进行比对,如果最后修改时间与该属性值一致时,服务端会返回一个304 Not Modified响应,该响应中不包括响应实体。浏览器收到304的响应后,会进行重定向,获取本地缓存资源。如果最后修改时间与该属性值不一致,则会从服务端重新获取资源,做出200响应。

任何一个 WEB 项目,要提高性能,各个环节的缓存必不可少。利用好 HTTP
协议的缓存机制,可以大幅减少传输数据,减少请求,这又是一项收益投入比超高的优化手段。这里把之前我写的
HTTP/1.1 缓存机制介绍翻出来:

连接(Connection):与 HTTP/1 相同,都是指对应的 TCP 连接;

建立连接(Initial connection):HTTP 是基于 TCP
协议的,浏览器最快也要在第三次握手时才能捎带 HTTP
请求报文。这个过程通常也要耗费几百毫秒;

一、系统安装

版本标记:ETag 和 If-None-Match

ETag其实与Last-Modified是差不多的方式,但是ETag并没有选择以时间作为标记,而是对所请求文件进行某些算法来生成一串唯一的字符串,作为对某一文件的标记。当收到客户端对某一资源的请求时,服务端在响应时,添加ETag首部,如下:

ETag:W/"a627ff1c9e65d2dede2efe0dd25efb8c"

当客户端发现ETag头部时,同样会对资源进行缓存,并在下次请求时,在请求头部添加If-None-Match,如:

If-None-Match:W/"a627ff1c9e65d2dede2efe0dd25efb8c"

当服务端收到请求中含有该头部时,会使用同样的ETag生成算法对文件ETag进行计算,并与If-None-Match属性值进行比对,如果一致,则返回一个304 Not Modified响应,基本与上一种方式是一致的。

首先,服务端可以通过响应头里的 Last-Modified(最后修改时间) 或者
ETag(内容特征) 标记实体。浏览器会存下这些标记,并在下次请求时带上
If-Modified-Since: 上次 Last-Modified 的内容 或 If-None-Match: 上次 ETag
的内容,询问服务端资源是否过期。如果服务端发现并没有过期,直接返回一个状态码为
304、正文为空的响应,告知浏览器使用本地缓存;如果资源有更新,服务端返回状态码
200、新的 Last-Modified、Etag 和正文。这个过程被称之为 HTTP
的协商缓存,通常也叫做弱缓存。

在 HTTP/2
中,同域名下所有通信都在单个连接上完成,这个连接可以承载任意数量的双向数据流。每个数据流都以消息的形式发送,而消息又由一个或多个帧组成。多个帧之间可以乱序发送,因为根据帧首部的流标识可以重新组装。下面有一幅图说明帧、消息、流和连接的关系:

当然我们一般都会给静态资源设置一个很长时间的缓存头。只要用户不清除浏览器缓存也不刷新,第二次访问我们网页时,静态资源会直接从本地缓存获取,并不产生网络请求;如果用户只是普通刷新而不是强刷,浏览器会在请求头带上协商字段
If-Modified-Since 或 If-None-Match,服务端对没有变化的资源会响应 304
状态码,告知浏览器从本地缓存获取资源。304 请求没有正文,非常小。

1.系统分区建议

缓存时间:Expires 和 Cache-Control

上述两种方式中,每次请求资源时,虽然在有缓存的情况下,选择缓存进行渲染绘制,但是在这之前还是发起了一次HTTP请求,虽然并没有真实的响应实体,但是依然会造成一些资源消耗。而Expires与上述两种方式使用了不同的思路。

当服务端希望客户端浏览器对某一资源进行缓存时,为了免去客户端每次都要询问自己:我上次的缓存现在还能用吗?所以,服务端选择了放权。只去告诉浏览器,我这次给你的资源你可以用多长时间,在这个时间段内,你可以一直使用它,无需每次咨询我。而服务端就是通过Expires属性来告诉客户端浏览器可以多长时间内不需要询问服务端。如下:
Expires:Thu, 19 Nov 2015 15:00:00 GMT

当客户端在响应首部中发现该属性值时,便会将该资源缓存起来,而缓存的过期时间即是Expires中的时间。在这个时间段内,浏览器完全自主。

但是,Expires有一个不足的地方是,如果服务端时间与客户端本地时间不统一时,可能服务端让客户端可以对该资源缓存一个小时,而客户端本地时间比服务端时间快了两个小时,那就意味着,所有缓存都将不会生效。

于是有了弥补该不足的一个属性,即:Cache-Control。如果服务端在响应首部添加该属性时,客户端将直接使用该属性值来生成本地时间的缓存过期时间,这样便解决了这个问题,如下:

Cache-Control:max-age=3600

如果客户端在2015年10月01日13时00分00秒收到该响应时,便会加上3600秒也就是2015年10月01日14时00分00秒作为缓存过期时间。如果响应头部既有ExpiresCache-Control,浏览器会首选Cache-Control

可以看到协商缓存并不会节省连接数,但是在缓存生效时,会大幅减小传输内容(304
响应没有正文,一般只有几百字节)。另外为什么有两个响应头都可以用来实现协商缓存呢?这是因为一开始用的
Last-Modified 有两个问题:1)只能精确到秒,1
秒内的多次变化反映不出来;2)在轮询的负载均衡算法中,如果各机器读到的文件修改时间不一致,有缓存无故失效和缓存不更新的风险。HTTP/1.1
并没有规定 ETag
的生成规则,而一般实现者都是对资源内容做摘要,能解决前面两个问题。

 图片 2

也就是说资源外链的特点是,第一次慢,第二次快。

/boot 100M(大约100左右)

0×04 结束

这里,基本上说的都是与HTTP首部有关的网站性能优化。本文主要是在对《构建高性能WEB站点.
郭欣著》中第六章浏览器缓存的学习总结笔记。这本书对于WEB站点的优化,从各个层面都做了很详尽的讲解,确实是一本很棒的书,也在这里感谢HQBOSS的推荐。

1 赞 1 收藏
评论

另外一种缓存机制是服务端通过响应头告诉浏览器,在什么时间之前(Expires)或在多长时间之内(Cache-Control:
Max-age=xxx),不要再请求服务器了。这个机制我们通常称之为 HTTP 的强缓存。

TCP
协议本身更适合用来长时间传输大数据,这样它的稳定和可靠性才能显露出来。HTTP/1
时代太多短而小的 TCP 连接,反而更多地将 TCP 的缺点给暴露出来了。

再来看看资源内联的情况。把 CSS、JS 文件内容直接内联在 HTML
中的方案,毫无疑问会在用户第一次访问时有速度优势。但通常我们很少缓存
HTML
页面,这种方案会导致内联的资源没办法利用浏览器缓存,后续每次访问都是一种浪费。

SWAP 物理内存的2倍(如果你的物理内存大于4G,分配4G即可)

关于作者:十三号线上的蝼蚁

图片 3

哈哈哈
个人主页 ·
我的文章 ·
3 ·
 

图片 4

一旦资源命中强缓存规则后,再次访问完全没有 HTTP 请求(Chrome 开发者工具的
Network 面板依然会显示请求,但是会注明 from cache;Firefox 的 firebug
也类似,会注明 BFCache),这会大幅提升性能。所以我们一般会对
CSS、JS、图片等资源使用强缓存,而入口文件(HTML)一般使用协商缓存或不缓存,这样可以通过修改入口文件中对强缓存资源的引入
URL 来达到即时更新的目的。

HTTP/1 的连接

解决

/ 分区15~20G

这里也解释下为什么有了 Expire,还要有
Cache-Control。也有两个原因:1)Cache-Control
功能更强大,对缓存的控制能力更强;2)Cache-Control 采用的 max-age
是相对时间,不受服务端 / 客户端时间不对的影响。

在 HTTP/1 中,每一个请求和响应都要占用一个 TCP 连接,尽管有 Keep-Alive
机制可以复用,但在每个连接上同时只能有一个请求 /
响应,这意味着完成响应之前,这个连接不能用于其他请求(怎么判断响应是否结束,可以看这里)。如果浏览器需要向同一个域名发送多个请求,需要在本地维护一个
FIFO
队列,完成一个再发送下一个。这样,从服务端完成请求开始回传,到收到下一个请求之间的这段时间,服务端处于空闲状态。

很早之前,就有网站开始针对第一次访问的用户将资源内联,并在页面加载完之后异步加载这些资源的外链版本,同时记录一个
Cookie
标记表示用户来过。用户再次访问这个页面时,服务端就可以输出只有外链版本的页面,减小体积。

/usr/local (用于安装软件)

另外关于浏览器的刷新(F5 / cmd + r)和强刷(Ctrl + F5 / shift + cmd
+r):普通刷新会使用协商缓存,忽略强缓存;强刷会忽略浏览器所有缓存(并且请求头会携带
Cache-Control:no-cache 和
Pragma:no-cache,用来通知所有中间节点忽略缓存)。只有从地址栏或收藏夹输入网址、点击链接等情况下,浏览器才会使用强缓存。

后来,人们提出了 HTTP 管道(HTTP Pipelining)的概念,试图把本地的 FIFO
队列挪到服务端。它的原理是这样的:浏览器一股脑把请求都发给服务端,然后等着就可以了。这样服务端就可以在处理完一个请求后,马上处理下一个,不会有空闲了。甚至服务端还可以利用多线程并行处理多个请求。可惜,因为
HTTP/1 不支持多路复用,这个方案有几个棘手的问题:

这个方案除了有点浪费流量之外(一份资源,内联外链加载了两次),基本上能达到更快加载重要资源的效果。但是在流量更加宝贵的移动端,我们需要继续改进这个方案。

/data 剩余所有空间

减少 DNS 查询

服务端收到多个管道请求后,需要按接收顺序逐个响应。如果恰好第一个请求特别慢,后续所有响应都会跟着被阻塞。这种情况通常被称之为「队首阻塞(Head-of-Line
Blocking)」;

考虑到移动端浏览器都支持
localStorage,可以将第一次内联引入的资源缓存起来后续使用。缓存更新机制可以通过在
Cookie 中存放版本号来实现。这样,服务端收到请求后,首先要检查 Cookie
头中的版本标记:

【注】具体分区请根据相关业务划分,具体安装本文不作介绍,安装过程中选择最少的包,采用文本模式安装请不要安装图形模式。 2.系统初始化 系统初始化脚本(根据具体需求关闭不需要的服务)**

我们知道,建立 TCP 连接需要知道目标
IP,而绝大部分时候给浏览器的是域名。浏览器需要先将域名解析为
IP,这个过程就是 DNS
查询,一般需要几毫秒到几百毫秒,移动环境下会更慢。DNS
解析完成之前,请求会被 Block。浏览器一般都会缓存 DNS
查询结果,页面使用的域名(包括子域名)越少,花费在 DNS
查询上的开销就越小。另外,合理使用浏览器的 DNS Prefetching
技术,也是很好的做法。

服务端为了保证按顺序回传,通常需要缓存多个响应,从而占用更多的服务端资源,也更容易被人攻击;

如果标记不存在或者版本不匹配,就将资源内联输出,并提供当前版本标记。页面执行时,会把内联资源存入
localStorage,并将资源版本标记存入 Cookie;

#sh network.sh //下载本文提供的lemp.zip包,即可找到此脚本。

减少重定向

浏览器连续发送多个请求后,等待响应这段时间内如果遇上网络异常导致连接被断开,无法得知服务端处理情况,如果全部重试可能会造成服务端重复处理;

如果标记匹配,就输出 JavaScript 片段,用来从 localStorage
读取并使用资源;

二、编译安装基本环境

无论是通过服务端响应头产生的重定向,还是通过 或者 JS
产生的重定向,都可能引入新的 DNS 查询、新的 TCP 连接以及新的 HTTP
请求,所以减少重定向也很重要。浏览器基本都会缓存通过 301 Moved
Permanently
指定的跳转,所以对于永久性跳转,可以考虑使用状态码301。对于启用了 HTTPS
的网站,配置 HSTS 策略,也可以减少从 HTTP 到 HTTPS 的重定向。

另外,服务端和浏览器之间的中间代理设备也不一定支持 HTTP
管道,这给管道技术的普及引入了更多复杂性;

由于 Cookie
内容需要尽可能的少,所以一般只存总的版本号。这会导致页面任何一处资源变动,都会改变总版本号,进而忽略客户端所有
localStorage 缓存。要解决这个问题可以继续改进我们的方案:Cookie
中只存放用户唯一标识,用户和资源对应关系存在服务端。服务端收到请求后根据用户标识,计算出哪些资源需要更新,从而输出更有针对性的
HTML 文档。

1.安装准备

WEB
性能优化是一个系统工程,不可能在这一篇文章里写完,我决定先就写到这儿。最后,推荐一个
Chrome 扩展:HTTP/2 and SPDY
indicator,它可以在地址栏显示当前网站是否启用了 SPDY 或者
HTTP/2,点击图标可以直接打开 Chrome 的 HTTP/2 的调试界面,十分方便。

基于这些原因,HTTP
管道技术无法大规模使用,我们需要寻找其他方案。实际上,在 HTTP/1
时代,连接数优化不外乎两个方面:开源和节流。

这套方案要投入实际使用,要处理一系列异常情况,例如 JS / Cookie /
localStorage 被禁用;localStorage 被写满;localStorage
内容损坏或丢失等等。考虑成本和实际收益,推荐只在移动项目中使用这种方案。

1.1系统软件包安装规范

【编辑推荐】

开源

HTTP/2

系统约定

这里说的开源,当然不是「Open Source」那个开源。既然一个 TCP
连接同时只能处理一个 HTTP 消息,那多开几条 TCP
连接不就解决这个问题了。是的,浏览器确实是这么做的,HTTP/1.1
初始版本中允许浏览器针对同一个域名同时创建两个连接,在修订版(rfc7230)中更是去掉了这个限制。实际上,现代浏览器一般允许同域名并发
6~8
个连接。这个数字为什么不能更大呢?实际上这是出于公平性的考虑,每个连接对于服务端来说都会带来一定开销,如果浏览器不加以限制,一个性能好带宽足的终端就可能耗尽服务端所有资源,造成其他人无法使用。

对于 HTTP/2 来说,要解决前面这个问题简直就太容易了,开启「Server
Push」即可。HTTP/2
的多路复用特性,使得可以在一个连接上同时打开多个流,双向传输数据。Server
Push,意味着服务端可以在发送页面 HTML
时主动推送其它资源,而不用等到浏览器解析到相应位置,发起请求再响应。另外,服务端主动推送的资源不是被内联在页面里,它们有自己独立的
URL,可以被浏览器缓存,当然也可以给其他页面使用。

/usr/local/src 软件源代码包存放位置

但是,现在包含几十个
CSS、JSS,几百张图片的页面大有所在。为了进一步榨干浏览器,开更多的源,往往我们还会对静态资源做域名散列,将页面静态资源分散在多个子域下加载。多域名能提高并发连接数,也会带来很多问题,例如:

服务端可以主动推送,客户端也有权利选择接收与否。如果服务端推送的资源已经被浏览器缓存过,浏览器可以通过发送
RST_STREAM 帧来拒收。

/usr/local/softname_version 源码包编译安装位置(prefix)

如果同一资源在不同页面被散列到不同子域下,会导致无法利用之前的 HTTP
缓存;

可以看到,HTTP/2 的 Server Push
能够很好地解决「如何让重要资源尽快加载」这个问题,一旦普及开来,可以取代前面介绍过的
HTTP/1 时代优化方案。

/usr/local/sbin 脚本以及维护程序存放位置

每个域名的第一个连接都要经历 DNS
解析的过程,这在移动端可能需要耗费几百毫秒;

【编辑推荐】

/data/mysql/3306/data MySQL数据库位置(可按情况设置)

更多的并发连接 + Keep-Alive 机制,会显著增加服务端和客户端的负担;

/data/www/wwwroot 网站根目录(可按情况设置)

这里稍微吐槽下:本地 TCP 连接和本地端口也是一种资源,为了做 WEB
性能优化,开更多的域名让浏览器创建更多的并发连接,是很霸道和不公平的做法。

/data/logs 虚拟主机日志根目录(可按情况设置)

另外,HTTP/1
协议头部使用纯文本格式,没有任何压缩,且包含很多冗余信息(例如
Cookie、UserAgent
每次都会携带),所以一个页面的请求数越多,头部带来的额外开销就越大。我们一般会用短小且独立的域名来托管静态资源,就是为了减小这个开销(域名越短请求头起始行的
URI 就越短,独立域名不会共享主域的
Cookie,可以有效减小请求头大小,这个策略一般称之为 Cookie-Free Domain)。

www:www nginx运行账户

节流

mysql:mysql mysq运行账户

由于我们不能无限制开源,所以节流也很重要。除了砍掉页面内容,第二次访问时利用
HTTP
缓存之外,通常能做的就只有合并请求了。根据合并的内容不同,一般又分为以下几种:

install_softwartname_version.sh
存放编译参数脚本习惯,将所有编译脚本存放在

异步接口合并(Batch Ajax Request);

install_softwartname_version.sh中,便于升级和更新软件。

图片合并,雪碧图(CSS Sprite);

创建网站帐号及相关存放目录

CSS、JS 合并(Concatenation);

#sh add_nginx_user.sh

CSS、JS 内联(Inline);

  1. /usr/sbin/groupadd www

  2. /usr/sbin/useradd –g www www

  3. mkdir –p /data/{logs,www/wwwroot}

  4. chmod 755 –R /data/{logs,www/wwwroot}

  5. chown www:www –R /data/{logs,www/wwwroot}

图片、音频内联(Data URI);

复制代码
创建数据库帐号及存放目录

上面这份列表并不完整,我也没打算列全,这些就足以说明 HTTP/1
时代我们在性能上所做过的不懈努力了。可惜,他们并不完美,分别列举一下他们的缺点:

#sh add_mysql_user.sh

异步接口合并:批量接口返回的时间受木桶效应影响,最慢的那个接口拖累了其他接口。

  1. /usr/sbin/groupadd mysql -g 27

  2. /usr/sbin/useradd -u 27 -g mysql -c “MySQL Server” mysql -s
    /sbin/nologin

  3. mkdir –p /data/mysql/3306/{data,logs/{binlog,relaylog}}

  4. chown mysql:mysql –R /data/mysql

图片合并:首先,为了显示一张小图,而不得不加载合并后的整张大图,一是可能浪费流量;二是占用更多内存。其次,合并图片中任何一处修改,都会导致整张大图缓存失效。这些问题可以根据不同场景,选用
Data URI、Icon Font、SVG
等技术来改造。另外,雪碧图的生成和维护都比较繁琐,最好使用工具自动管理。

复制代码
1.2 系统环境部署及调整

CSS、JS
合并:合并后的资源需要整体加载完才开始解析、执行。原本加载完一个文件就可以解析并执行一个,将很多个文件合并成一个巨无霸,会整体推后可用时间。为此,Chrome
新版引入了 Script Streaming 技术,能边加载边解析 JS 文件。Gmail
为了解决这个问题,将多个 JS 文件合并为一个由多个 inline script
片段组成的 html,用 iframe
引入,以达到边加载变解析执行的效果。另外,与图片合并类似,CSS、JS
合并也会遇到「无论多小的改动,都会导致整个合并文件缓存失效」的问题。

检查系统是否正常

CSS、JS
内联:上篇文章我详细分析过内联的优点和弊端。主要两个问题:1)无法利用缓存;2)多页面无法共享。

#tail –n100 /var/log/message (检查有无系统级错误信息)

图片、音频内联:除了也有上面两个问题之外,二进制文件以 Data URI
方式内联,需要进行 Base64 编码,体积会变大 1/3。

#dmesg (检查硬件设备是否有错误信息)

结论

#ifconfig (检查网卡设置是否正确)

HTTP/1 时代,我们为了节省昂贵的 HTTP 连接(TCP
连接),采用了各种优化手段,这些方案或多或少会引入一些问题,但是相比收益来说还是值得做,也应该做。但是,有了
HTTP/2 的多路复用和头部压缩,HTTP
连接变得可以随心所欲了,本文提到的这些连接数优化手段确实可以退休了。

#ping bbs.linuxtong.org (检查网络是否正常)

哦对了,据官方预测,HTTP/1 至少还需要 10
年才能彻底退出历史舞台,另外尽管 HTTP/2 协议允许脱离 TSL 部署,但 Chrome
和 Firefox 都表示不支持非 TLS 的 HTTP/2,之后很可能一个网站会同时提供
HTTP/1.1、HTTP/1.1 over TLS、HTTP/2 over TLS
三种服务。如何在每种情况下,都能给用户提供最好的体验,需要更加深入的优化研究和更加精细的优化策略。由此可见,在很长一段时间内,WEB
性能优化非但不会落幕,反而会更加重要。

1.3 使用yum程序安装所需开发包(以下为标准的RPM包名称)

【编辑推荐】

添加国内镜像源,加速软件安装下载速度,请参照:

  1. 揭开HTTP网络协议神秘面纱系列(一)
  2. 揭开HTTP网络协议神秘面纱系列(二)
  3. 揭开HTTP网络协议神秘面纱系列(三)
  4. HTTP 协议中的
    Transfer-Encoding
  5. HTTP/2 与 WEB
    性能优化(一)

#sh yum.sh

【责任编辑:何妍 TEL:(010)68476606】

  1. yum -y install dialog ntp vim-enhanced vixie-cron gcc gcc-c++
    gcc-g77 flex bison autoconf automake glibc glibc-devel glib2
    glib2-devel bzip2 bzip2-devel ncurses ncurses-devel libtool*
    zlib-devel libxml2-devel libjpeg-devel libpng-devel libtiff-devel
    fontconfig-devel freetype-devel libXpm-devel gettext-devel curl
    curl-devel pam-devel e2fsprogs-devel krb5-devel libidn libidn-devel
    openssl openssl-devel openldap openldap-devel net-snmp
    net-snmp-devel nss_ldap openldap-clients openldap-servers libtidy
    libtidy-devel

原文:HTTP/2 与 WEB
性能优化(二)

返回网络频道首页

复制代码
1.4 定时校正服务器时钟,定时与中国国家时间中心服务器同步

#crontab –e

加入一行:

  1. 15 1 * * * /usr/sbin/ntpdate 210.72.145.44;/usr/sbin/hwclock
    -w > /dev/null 2>&1

复制代码
1.5 下载编译相关的源码包

#cd /usr/local/src

#vi list.txt //在list.txt文件里填入下载地址列表

  1. ftp://ftp.graphicsmagick.org/pub/GraphicsMagick/1.3/GraphicsMagick-1.3.12.tar.gz

复制代码

#wget –i list.txt

2.编译安装软件包源码编译安装所需包(Source)

2.1编译安装Nginx

  1. tar zvxf pcre-8.10.tar.gz

  2. cd pcre-8.10

  3. ./configure && make && make install

复制代码

  1. tar zvxf nginx-0.8.53.tar.gz

  2. cd nginx-0.8.53

  3. ./configure –user=www –group=www –prefix=/usr/local/nginx-0.8.53/
    –add-module=../ngx_cache_purge-1.2
    –with-http_stub_status_module –with-http_ssl_module
    –with-http_sub_module –with-md5=/usr/lib –with-sha1=/usr/lib
    –with-http_gzip_static_module

  4. make && make install

复制代码

  1. mkdir -p /data/cache/nginx_cache

  2. mkdir -p /data/cache/nginx_temp

复制代码
2.2编译安装MySQL 注:MySQL的5.5把本来用的autotools的换成CMake了。
mysql5.5融合了mysql数据库和innodb存储引擎的优点,能够提供高性能的数据库管理解决方案,包括:
innodb作为默认的数据库存储引擎
多核性能提升
复制功能(Replication)加强
增强表分区功能
提升了windows系统下的系统性能和可扩充性
改善性能和可扩充性,全面利用各平台现代多核构架的注意能力
提高实用性
提高易管理性和效率
提高可用性
改善检测与诊断性能



  1. tar zvxf cmake-2.8.4.tar.gz

  2. cd cmake-2.8.4

  3. ./configure && make && make install

复制代码

  1. tar zxvf mysql-5.5.8.tar.gz

  2. cd mysql-5.5.8

  3. cmake . -DCMAKE_INSTALL_PREFIX=/usr/local/mysql-5.5.8
    -DMYSQL_DATADIR=/data/mysql/3306/data -DSYSCONFDIR=/etc
    -DWITH_INNOBASE_STORAGE_ENGINE=1
    -DWITH_ARCHIVE_STORAGE_ENGINE=1
    -DWITH_BLACKHOLE_STORAGE_ENGINE=1
    -DWITH_FEDERATED_STORAGE_ENGINE=1
    -DWITH_PARTITION_STORAGE_ENGINE=1 -DMYSQL_TCP_PORT=3306
    -DENABLED_LOCAL_INFILE=1 -DWITH_SSL=yes -DEXTRA_CHARSETS=all
    -DDEFAULT_CHARSET=utf8 -DDEFAULT_COLLATION=utf8_general_ci
    -DWITH_READLINE=on

  4. gmake

  5. make install

复制代码 配置mysql配置文件

  1. cd /data/mysql/3306

  2. wget

  3. chown mysql:mysql /data/mysql/3306/my.cnf

  4. ln –s /data/mysql/3306/my.cnf /etc/

复制代码

(配置文件也可以上传附件中my.cnf)

初始化mysql数据

  1. mkdir -p /usr/local/mysql-5.5.8/include/mysql

  2. ln -s /usr/local/mysql-5.5.8/include/*
    /usr/local/mysql-5.5.8/include/mysql/

  3. /usr/local/mysql-5.5.8/scripts/mysql_install_db
    –basedir=/usr/local/mysql-5.5.8/ –datadir=/data/mysql/3306/data
    –user=mysql –defaults-file=/data/mysql/3306/my.cnf

  4. cp /usr/local/mysql-5.5.8/support-files/mysql.server
    /etc/rc.d/init.d/mysqld > /dev/null 2>&1

  5. chmod 755 /etc/rc.d/init.d/mysqld

  6. chkconfig –add mysqld

  7. /etc/rc.d/init.d/mysqld start > /dev/null 2>&1

  8. rm -rf /usr/bin/mysql* > /dev/null 2>&1

  9. cd /usr/local/mysql-5.5.8/bin/

  10. for i in *; do ln -s /usr/local/mysql-5.5.8/bin/$i /usr/bin/$i;
    done

  11. echo “/usr/local/mysql-5.5.8/lib/” >> /etc/ld.so.conf

  12. ldconfig

复制代码
2.3编译安装PHP相关的LIB

  1. tar zxvf libiconv-1.13.1.tar.gz

  2. cd libiconv-1.13.1

  3. ./configure –prefix=/usr/local && make && make install

复制代码

  1. tar zxvf libmcrypt-2.5.8.tar.gz

  2. cd libmcrypt-2.5.8/

  3. ./configure –prefix=/usr && make && make install

  4. cd libltdl/

  5. ./configure –enable-ltdl-install

  6. make && make install

复制代码

  1. tar zxvf mhash-0.9.9.9.tar.gz

  2. cd mhash-0.9.9.9

  3. ./configure –prefix=/usr && make && make install

  4. ldconfig

复制代码

  1. ln -s /usr/local/lib/libmcrypt.la /usr/lib/libmcrypt.la

  2. ln -s /usr/local/lib/libmcrypt.so /usr/lib/libmcrypt.so

  3. ln -s /usr/local/lib/libmcrypt.so.4 /usr/lib/libmcrypt.so.4

  4. ln -s /usr/local/lib/libmcrypt.so.4.4.8
    /usr/lib/libmcrypt.so.4.4.8

  5. ln -s /usr/local/lib/libmhash.a /usr/lib/libmhash.a

  6. ln -s /usr/local/lib/libmhash.la /usr/lib/libmhash.la

  7. ln -s /usr/local/lib/libmhash.so /usr/lib/libmhash.so

  8. ln -s /usr/local/lib/libmhash.so.2 /usr/lib/libmhash.so.2

  9. ln -s /usr/local/lib/libmhash.so.2.0.1 /usr/lib/libmhash.so.2.0.1

  10. /sbin/ldconfig

复制代码

  1. tar zxvf mcrypt-2.6.8.tar.gz

  2. cd mcrypt-2.6.8

  3. ./configure –prefix=/usr && make&& make install

复制代码

  1. echo “/usr/lib” >> /etc/ld.so.conf

  2. echo “/usr/local/lib” >> /etc/ld.so.conf

  3. ldconfig

复制代码
2.4编译安装PHP-FastCGI

  1. gzip -d suhosin-patch-5.3.4-0.9.10.patch.gz

  1. tar zxvf php-5.3.4.tar.gz

  2. cd php-5.3.4

  3. patch -p 1 -i ../suhosin-patch-5.3.4-0.9.10.patch

  4. ./configure –prefix=/usr/local/php-5.3.4
    –with-config-file-path=/usr/local/php-5.3.4/etc
    –with-mysql=/usr/local/mysql-5.5.8
    –with-mysqli=/usr/local/mysql-5.5.8/bin/mysql_config
    –with-pdo-mysql=/usr/local/mysql-5.5.8/ –with-iconv-dir=/usr/local
    –with-freetype-dir –with-jpeg-dir –with-png-dir –with-zlib
    –with-pdo-sqlite –enable-pdo –with-bz2 –with-gettext –with-snmp
    –with-libxml-dir –enable-xml –disable-rpath –disable-safe-mode
    –enable-bcmath –enable-shmop –enable-sysvsem
    –enable-inline-optimization –with-curl –with-curlwrappers
    –enable-mbregex –enable-fpm –enable-mbstring –with-mcrypt
    –with-gd –enable-gd-native-ttf –with-openssl –with-mhash
    –enable-pcntl –enable-sockets –with-ldap –with-ldap-sasl
    –with-xmlrpc –enable-zip –enable-soap –without-pear

  5. make ZEND_EXTRA_LIBS=’-liconv’

  6. make install

  7. cp php.ini-production /usr/local/php-5.3.4/etc/php.ini

复制代码

配置php-fcgi配置文件和启动脚本

  1. cd /usr/local/php-5.3.4/etc

  2. wget

  3. cd /etc/init.d

  4. wget

  5. chmod +x /etc/init.d/php-fpm

  6. mkdir -p /usr/local/php-5.3.4/logs

  7. chown www:www –R /usr/local/php-5.3.4/logs

复制代码(配置文件也可以上传附件中的php-fcgi配置文件php-fpm.conf和附件中的php-fpm脚本)

2.5编译安装PHP扩展

  1. tar zvxf libevent-1.4.13-stable.tar.gz

  2. cd libevent-1.4.13-stable

  3. ./configure && make && make install

复制代码

  1. tar zxvf memcached-1.4.5.tar.gz

  2. cd memcached-1.4.5

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –prefix=/usr/local/memcached-1.4.5

  5. make && make install

复制代码

  1. tar zxvf memcache-2.2.6.tgz

  2. cd memcache-2.2.6

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –with-php-config=/usr/local/php-5.3.4/bin/php-config

  5. make && make install

复制代码

  1. tar zvxf libmemcached-0.44.tar.gz

  2. cd libmemcached-0.44

  3. ./configure –prefix=/usr/local/libmemcached-0.44
    –with-memcached=/usr/local/memcached-1.4.5/bin/memcached

  4. make && make install

复制代码

  1. tar zvxf tidy-1.2.tgz

  2. cd tidy-1.2

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –with-php-config=/usr/local/php-5.3.4/bin/php-config

  5. make && make install

复制代码

  1. tar zxvf memcached-1.0.2.tgz

  2. cd memcached-1.0.2

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –with-php-config=/usr/local/php-5.3.4/bin/php-config
    –with-libmemcached-dir=/usr/local/libmemcached-0.44/

  5. make && make install

复制代码

  1. tar zxvf ImageMagick.tar.gz

  2. cd ImageMagick-6.5.1-2/

  3. ./configure –enable-shared –with-modules –without-x
    –with-gs-font-dir=default –with-perl=yes –with-xml=yes
    –with-zlib=yes –with-jpeg=yes

  4. Make && make install

复制代码

  1. tar zxvf imagick-3.0.1.tgz

  2. cd imagick-3.0.1

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –with-php-config=/usr/local/php-5.3.4/bin/php-config

  5. make && make install

复制代码

  1. tar zvxf GraphicsMagick-1.3.12.tar.gz

  2. ./configure –prefix=/usr/local/GraphicsMagick-1.3.12
    –enable-libtool-verbose –with-included-ltdl –enable-shared
    –disable-static –with-modules –with-frozenpaths –without-perl
    –without-magick-plus-plus –with-quantum-depth=8
    –enable-symbol-prefix

  3. make && make install

复制代码

  1. tar zvxf gmagick-1.0.8b1.tgz

  2. cd gmagick-1.0.8b1

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –with-php-config=/usr/local/php-5.3.4/bin/php-config
    –with-gmagick=/usr/local/GraphicsMagick-1.3.12/

  5. make && make install

复制代码

  1. tar jvxf eaccelerator-0.9.6.1.tar.bz2

  2. cd eaccelerator-0.9.6.1

  3. /usr/local/php-5.3.4/bin/phpize

  4. ./configure –enable-eaccelerator=shared
    –with-php-config=/usr/local/php-5.3.4/bin/php-config

  5. make && make install

复制代码
2.6配置php.ini

#vi conf_php.sh

  1. sed -i ‘s/error_reporting = E_ALL \& ~E_NOTICE/error_reporting
    = E_WARNING \& E_ERROR/g’ /usr/local/php-5.3.4/etc/php.ini

  2. sed -i ‘/display_errors/ {s/On/Off/g};/log_errors/ {s/Off/On/g}’
    /usr/local/php-5.3.4/etc/php.ini

  3. sed -i “s#;error_log = filename#error_log =
    /tmp/php-error.log#g” /usr/local/php-5.3.4/etc/php.ini

  4. sed -i “s#;always_populate_raw_post_data =
    On#always_populate_raw_post_data = On#g”
    /usr/local/php-5.3.4/etc/php.ini

  1. cat >>/usr/local/php-5.3.4/etc/php.ini<<EOF

  2. extension_dir =
    “/usr/local/php-5.3.4/lib/php/extensions/no-debug-non-zts-20090626/”

  3. extension = “memcache.so”

  4. extension = “memcached.so”

  5. extension = “imagick.so”

  6. extension = “tidy.so”

  7. extension = “gmagick.so”

  8. extension = “eaccelerator.so”

  1. eaccelerator.cache_dir=”/data/cache/eaccelerator_cache”

  2. eaccelerator.enable=”1″

  3. eaccelerator.optimizer=”1″

  4. eaccelerator.check_mtime=”1″

  5. eaccelerator.debug=”0″

  6. eaccelerator.filter=””

  7. eaccelerator.shm_max=”0″

  8. eaccelerator.shm_ttl=”3600″

  9. eaccelerator.shm_prune_period=”3600″

  10. eaccelerator.shm_only=”0″

  11. eaccelerator.compress=”1″

  12. eaccelerator.compress_level=”9″

  13. EOF

复制代码

#sh conf_php.sh

2.7启动php-fcgi

  1. /etc/init.d/php-fpm start

复制代码

2.8配置nginx/conf

上传附件中的lemp/conf/nginx下的所有东西到/usr/local/nginx-0.8.53/conf下面覆盖原来的配置文件

2.9验证服务环境

新建info.php到虚拟主机的目录/data/www/wwwroot下面

  1. <?php

  2. Phpinfo();

  3. ?>

复制代码

访问

即可查看当前配置环境,查看完成后出于安全考虑,请修改php.ini禁止如下参数

  1. sed –i ‘210 s#disable_functions = #disable_functions =
    popen,pentl_exec,passthru,exec,system,scandir,chgrp,chown,shell_exec,proc_open,proc_get_status,ini_alter,ini_alter,ini_restore,dl,pfsockopen,openlog,syslog,readlink,symlink,popepassthru,stream_socket_server,show_source,escapeshellcmd,escapeshellarg,curl_exec,curl_multi_exec,parse_ini_file,assert,phpinfo#g’
    /usr/local/php-5.3.4/etc/php.ini

复制代码

三、服务器简单优化

1.简单网络优化

上传附件中的 lemp/scripts/tuning/network.sh

  1. #sh network.sh

复制代码
2.简单内核优化

上传附件中的 /lemp/scripts/tuning/tuning.sh

  1. #sh tuning.sh

复制代码
3.其他优化根据具体情况还有硬件情况做调整

更多的优化请参考:

四、相关参考及介绍

1.Nginx Tips(Nginx应用技巧含优化)

2.LEMP构建高性能WEB服务器(第三版)

3.如果你想构建可用集群方案请参考

4.如果你想学习服务器监控请参考

五、相关脚本下载

文中所有脚本及配置范例

自动化编译脚本下载:
(定期更新,欢迎多测试,找bug)
介绍参考: 作者…

发表评论

电子邮件地址不会被公开。 必填项已用*标注

网站地图xml地图