40 HTTP性能优化面面观(下)

今天我们继续上次的话题,看看 HTTP 性能优化有哪些行之有效的手段。

上一讲里我说到了,在整个 HTTP 系统里有三个可优化的环节,分别是服务器客户端传输链路(“第一公里”和“中间一公里”)。但因为我们是无法完全控制客户端的,所以实际上的优化工作通常是在服务器端。这里又可以细分为后端和前端,后端是指网站的后台服务,而前端就是 HTML、CSS、图片等展现在客户端的代码和数据。

知道了大致的方向,HTTP 性能优化具体应该怎么做呢?

总的来说,任何计算机系统的优化都可以分成这么几类:硬件软件、内部外部、花钱不花钱。

投资购买现成的硬件最简单的优化方式,比如换上更强的 CPU、更快的网卡、更大的带宽、更多的服务器,效果也会“立竿见影”,直接提升网站的服务能力,也就实现了 HTTP 优化。

另外,花钱购买外部的软件或者服务也是一种行之有效的优化方式,最“物有所值”的应该算是 CDN 了(参见[第 37 讲])。CDN 专注于网络内容交付,帮助网站解决“中间一公里”的问题,还有很多其他非常专业的优化功能。把网站交给 CDN 运营,就好像是“让网站坐上了喷气飞机”,能够直达用户,几乎不需要费什么力气就能够达成很好的优化效果。

不过这些“花钱”的手段实在是太没有“技术含量”了,属于“懒人”(无贬义)的做法,所以我就不再细说,接下来重点就讲讲在网站内部、“不花钱”的软件优化。

我把这方面的 HTTP 性能优化概括为三个关键词:开源节流缓存

开源

这个“开源”可不是 Open Source,而是指抓“源头”,开发网站服务器自身的潜力,在现有条件不变的情况下尽量挖掘出更多的服务能力。

首先,我们应该选用高性能的 Web 服务器,最佳选择当然就是 Nginx/OpenResty 了,尽量不要选择基于 Java、Python、Ruby 的其他服务器,它们用来做后面的业务逻辑服务器更好。利用 Nginx 强大的反向代理能力实现“动静分离”,动态页面交给 Tomcat、Django、Rails,图片、样式表等静态资源交给 Nginx。

Nginx 或者 OpenResty 自身也有很多配置参数可以用来进一步调优,举几个例子,比如说禁用负载均衡锁、增大连接池,绑定 CPU 等等,相关的资料有很多。

特别要说的是,对于 HTTP 协议一定要启用长连接。在[第 39 讲]里你也看到了,TCP 和 SSL 建立新连接的成本是非常高的,有可能会占到客户端总延迟的一半以上。长连接虽然不能优化连接握手,但可以把成本“均摊”到多次请求里,这样只有第一次请求会有延迟,之后的请求就不会有连接延迟,总体的延迟也就降低了。

另外,在现代操作系统上都已经支持 TCP 的新特性“TCP Fast Open”(Win10、iOS9、Linux 4.1),它的效果类似 TLS 的“False Start”,可以在初次握手的时候就传输数据,也就是 0-RTT,所以我们应该尽可能在操作系统和 Nginx 里开启这个特性,减少外网和内网里的握手延迟。

下面给出一个简短的 Nginx 配置示例,启用了长连接等优化参数,实现了动静分离:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
server {

listen 80 deferred reuseport backlog=4096 fastopen=1024;





keepalive_timeout 60;

keepalive_requests 10000;



location ~* \.(png)$ {

root /var/images/png/;

}



location ~* \.(php)$ {

proxy_pass http://php_back_end;

}

}

节流

“节流”是指减少客户端和服务器之间收发的数据量,在有限的带宽里传输更多的内容。

“节流”最基本的做法就是使用 HTTP 协议内置的“数据压缩”编码,不仅可以选择标准的 gzip,还可以积极尝试新的压缩算法 br,它有更好的压缩效果。

不过在数据压缩的时候应当注意选择适当的压缩率,不要追求最高压缩比,否则会耗费服务器的计算资源,增加响应时间,降低服务能力,反而会“得不偿失”。

gzip 和 br 是通用的压缩算法,对于 HTTP 协议传输的各种格式数据,我们还可以有针对性地采用特殊的压缩方式。

HTML/CSS/JS 属于纯文本,就可以采用特殊的“压缩”,去掉源码里多余的空格、换行、注释等元素。这样“压缩”之后的文本虽然看起来很混乱,对“人类”不友好,但计算机仍然能够毫无障碍地阅读,不影响浏览器上的运行效果。

图片在 HTTP 传输里占有非常高的比例,虽然它本身已经被压缩过了,不能被 gzip、br 处理,但仍然有优化的空间。比如说,去除图片里的拍摄时间、地点、机型等元数据,适当降低分辨率,缩小尺寸。图片的格式也很关键,尽量选择高压缩率的格式,有损格式应该用 JPEG,无损格式应该用 Webp 格式。

对于小文本或者小图片,还有一种叫做“资源合并”(Concatenation)的优化方式,就是把许多小资源合并成一个大资源,用一个请求全下载到客户端,然后客户端再用 JS、CSS 切分后使用,好处是节省了请求次数,但缺点是处理比较麻烦。

刚才说的几种数据压缩都是针对的 HTTP 报文里的 body,在 HTTP/1 里没有办法可以压缩 header,但我们也可以采取一些手段来减少 header 的大小,不必要的字段就尽量不发(例如 Server、X-Powered-By)。

网站经常会使用 Cookie 来记录用户的数据,浏览器访问网站时每次都会带上 Cookie,冗余度很高。所以应当少使用 Cookie,减少 Cookie 记录的数据量,总使用 domain 和 path 属性限定 Cookie 的作用域,尽可能减少 Cookie 的传输。如果客户端是现代浏览器,还可以使用 HTML5 里定义的 Web Local Storage,避免使用 Cookie。

压缩之外,“节流”还有两个优化点,就是域名重定向

DNS 解析域名会耗费不少的时间,如果网站拥有多个域名,那么域名解析获取 IP 地址就是一个不小的成本,所以应当适当“收缩”域名,限制在两三个左右,减少解析完整域名所需的时间,让客户端尽快从系统缓存里获取解析结果。

重定向引发的客户端延迟也很高,它不仅增加了一次请求往返,还有可能导致新域名的 DNS 解析,是 HTTP 前端性能优化的“大忌”。除非必要,应当尽量不使用重定向,或者使用 Web 服务器的“内部重定向”。

缓存

在[第 20 讲]里,我就说到了“缓存”,它不仅是 HTTP,也是任何计算机系统性能优化的“法宝”,把它和上面的“开源”“节流”搭配起来应用于传输链路,就能够让 HTTP 的性能再上一个台阶。

在“第零公里”,也就是网站系统内部,可以使用 Memcache、Redis、Varnish 等专门的缓存服务,把计算的中间结果和资源存储在内存或者硬盘里,Web 服务器首先检查缓存系统,如果有数据就立即返回给客户端,省去了访问后台服务的时间。

在“中间一公里”,缓存更是性能优化的重要手段,CDN 的网络加速功能就是建立在缓存的基础之上的,可以这么说,如果没有缓存,那就没有 CDN。

利用好缓存功能的关键是理解它的工作原理(参见[第 20 讲]和[第 22 讲]),为每个资源都添加 ETag 和 Last-modified 字段,再用 Cache-Control、Expires 设置好缓存控制属性。

其中最基本的是 max-age 有效期,标记资源可缓存的时间。对于图片、CSS 等静态资源可以设置较长的时间,比如一天或者一个月,对于动态资源,除非是实时性非常高,也可以设置一个较短的时间,比如 1 秒或者 5 秒。

这样一旦资源到达客户端,就会被缓存起来,在有效期内都不会再向服务器发送请求,也就是:“没有请求的请求,才是最快的请求。

HTTP/2

在“开源”“节流”和“缓存”这三大策略之外,HTTP 性能优化还有一个选择,那就是把协议由 HTTP/1 升级到 HTTP/2。

通过“飞翔篇”的学习,你已经知道了 HTTP/2 的很多优点,它消除了应用层的队头阻塞,拥有头部压缩、二进制帧、多路复用、流量控制、服务器推送等许多新特性,大幅度提升了 HTTP 的传输效率。

实际上这些特性也是在“开源”和“节流”这两点上做文章,但因为这些都已经内置在了协议内,所以只要换上 HTTP/2,网站就能够立刻获得显著的性能提升。

不过你要注意,一些在 HTTP/1 里的优化手段到了 HTTP/2 里会有“反效果”。

对于 HTTP/2 来说,一个域名使用一个 TCP 连接才能够获得最佳性能,如果开多个域名,就会浪费带宽和服务器资源,也会降低 HTTP/2 的效率,所以“域名收缩”在 HTTP/2 里是必须要做的。

“资源合并”在 HTTP/1 里减少了多次请求的成本,但在 HTTP/2 里因为有头部压缩和多路复用,传输小文件的成本很低,所以合并就失去了意义。而且“资源合并”还有一个缺点,就是降低了缓存的可用性,只要一个小文件更新,整个缓存就完全失效,必须重新下载。

所以在现在的大带宽和 CDN 应用场景下,应当尽量少用资源合并(JS、CSS 图片合并,数据内嵌),让资源的粒度尽可能地小,才能更好地发挥缓存的作用。

小结

  1. 花钱购买硬件、软件或者服务可以直接提升网站的服务能力,其中最有价值的是 CDN;
  2. 不花钱也可以优化 HTTP,三个关键词是“开源”“节流”和“缓存”;
  3. 后端应该选用高性能的 Web 服务器,开启长连接,提升 TCP 的传输效率;
  4. 前端应该启用 gzip、br 压缩,减小文本、图片的体积,尽量少传不必要的头字段;
  5. 缓存是无论何时都不能忘记的性能优化利器,应该总使用 Etag 或 Last-modified 字段标记资源;
  6. 升级到 HTTP/2 能够直接获得许多方面的性能提升,但要留意一些 HTTP/1 的“反模式”。

到这里,专栏的全部课程就学完了,在这三个月的时间里你是否有了很多的收获呢?

接下来,就请在广阔的网络世界里去实践这些知识吧,祝你成功!

unpreview

结束语 做兴趣使然的Hero

从今年年初与极客时间编辑的初次接触开始,到这个月底专栏的正式结束,经过了差不多 7 个月的时间。这段历程有痛苦也有欢乐,有迷惘也有清朗,有困惑也有顿悟,有挫折也有奋进,各种感受五味杂陈,一言难尽。

无论如何,首先要感谢的,就是你——感谢你对我和这个专栏的支持,Many thanks to you。

写文章都讲究“首尾呼应”,所以在这篇“结束语”里,我就顺着“开篇词”,和你聊些轻松的话题,唠唠家常、说说心里话。

我是怎么写专栏的

咱们都是吃计算机这碗饭的,应该知道通信协议这个东西很不好学,更是很难讲,因为它真的是太“虚”了。不像编程语言、算法、数据结构、开发框架、操作系统那样,有实实在在的代码,协议只是一个文本规范,是一个动态的过程,而不是在计算机系统里真实存在的东西,你不能用 GDB 直接去调试,内存里也看不见。

所以,讲协议的书本、课程实在是少之又少。

落到 HTTP 协议,就如同我在专栏一开始时所说的,它“既简单又不简单”,而且历史悠久,涉及的范围很广,关联的技能点很多、很杂。当我接下写专栏的任务时,甚至有点“懵”的感觉,千头万绪不知从何谈起。

好在我一直有写学习笔记的习惯,最早是用“原始”的 word 文档,近几年改用云端笔记工具,随时记录、整理散乱的知识碎片。

既然暂时“无从下手”,那就先读文章、记笔记好了。

于是,我开始大量地粗读、精读现有资料,在阅读的过程中慢慢搜集思维中闪现的“火花”,即使是一两个零星的词汇也不放过。一个多月的辛苦整理过后,这才逐渐理清了脉络,有了模糊的写作思路,全程有点“垃圾堆里筛金子”的感觉。

虽然我有写书的经验,但写专栏则是完全不同的体验,在正式动笔写作的时候(严格来说应该是“敲键盘码字”),我才意识到,它与传统的技术类书籍有很大的不同。

书籍的阅读场景通常会比较安静、放松,读者会有比较长的思考时间,可以翻来覆去地看,再时不时拿起铅笔画个重点、做个记号,一段时间的阅读下来可以关注很多的知识点,然后再慢慢思索,总结串联。

而专栏的阅读场景则更可能是在地铁、公交车里,周围人挤人、人挨人,拿着手机,或看或听,还要时刻当心别坐过站。在这种情况下,读者很难有足够的思考时间和精力,更希望能够高效率、便捷地在短短几分钟的碎片时间里吸取知识,如果有太多的知识点就难以接受,一两个略有深度的点会更好。

所以,这次的专栏写作我就改换了风格,开始“口语化写作”,不再像写书那样斟词酌句,为一两句话的用词反复思量、咬文嚼字,而是完全“放飞自我”,定下每篇文章的主旨、要点后就笔随心动,把头脑里的思绪完全“dump”出来。

这样虽然在一定程度上降低了文字的信息密度,却会让文章形式更流畅、更易理解,做为补充,文章的末尾我再用小结的形式集中强化一下要点,实现了“浅入深出”。

现在看来效果似乎还算不错,不知道你以为如何呢?

兴趣使然的 HERO

虽然风格定了,但专栏写作过程中的困难程度还是我当初没有预计到的,写书是一个“慢功夫”,可以慢慢思考,有想法了就写一点,没有灵感可能十天半个月都动不了笔。

而写专栏却有“硬性”的时间限制,和编辑确定了写作大纲后就开始了“奴隶”一样的日子:每周固定要交两、三篇,每篇三四千字,相当于毎天要产出至少一千的有效文字,这简直成了“夺命连环 call”,同时还有构思、画图、编码、试验、核查等其他工作,压力非常大,真是一次“触及灵魂”之旅。

记得有一句名言:“兴趣是最好的老师”,支撑着我把这个专栏按时交付下去的最大动力,可能就是对学习计算机知识的兴趣和探索欲了。每当 get 到一个以前没有注意的知识点,每当成功领会了协议背后的设计意图,我的心底都会产生由衷的喜悦,前面钻研过程中的苦恼和烦躁也就瞬间“烟消云散”了。

所以,只要发自内心地对一件事情产生兴趣和喜爱,那么即使有再多的困难,也会想办法去克服、去解决。

说到这里,我联想到了《一拳超人》里的主角埼玉,他可以算得上是典型的“兴趣使然的 Hero”,纯粹是因为自己的“兴趣”而走上了“打怪升级”的道路,不图名不图利,不在意排名,也不在意奖励。单纯而快乐的生活,也许正是我们很多人想要追求的目标。

当然,除了兴趣,更重要的是恒心、毅力和坚持。埼玉之所以成为“无敌的存在”,就是因为他每天坚持做 100 个俯卧撑、100 个仰卧起坐、100 个下蹲,天天如此,从不间断。

我在这几个月的专栏写作过程中,遇到的困难和烦恼是以前写书的好几倍,经常是坐在电脑前,脑子里有很多乱麻一样想法,却无法“落地”转化成合适的词语,有时候会就这么干坐上一两个小时,焦灼的心情可想而知。

幸运的是最终我在“兴趣”这个原动力的支撑下坚持到了最后,另外还有了一个意外的收获。每天夜里码字没有思路的时候,我会走出家门,在小区里慢跑两三圈,呼吸新鲜空气顺便“放空”大脑。到专栏结束的这个时间点,居然减掉了差不多 8 斤的体重。

所以你看,“兴趣”给我带来的好处还真是不少呢。

相濡以沫,不如相忘于江湖

“透视 HTTP”这个专栏马上就要结束了,但 HTTP 协议的学习还远没有结束。

这有点像是调查兵团历经磨难和牺牲,终于看到了大海,但在海的另一头,还会更多更大的挑战等待着他们(看过《进击的巨人》的朋友一定能领会这种情景吧)。

在这篇“结束语”的留言区里,希望大家都能“冒个泡”,看看当时定下的“小目标”有没有达成,一起分享一下在这个专栏中的收获和心路历程,还有将来的打算。也欢迎你访问专栏的 GitHub 主页,提 issue 和 PR,把 HTTP 的学习、实践继续下去。