如何通过 HTTP 缓存优化你的网站

我最近一直在折腾网站优化:如何 加速你的JavaScript, 用你的服务器gzip压缩文件,以及如何设置缓存。但原因很简单: 网站性能是一个特性。

对于网站而言,速度可能是第一大特性。 用户讨厌等待当我们遇到视频缓冲或页面因图片加载缓慢而跳闪时,会感到沮丧。这是一种糟糕的用户体验。投入时间优化网站是非常值得的,让我们深入了解吧。

什么是缓存(Caching)?

缓存是编程中无处不在的时间空间权衡的绝佳例子。你可以 节省时间 作者 利用空间 来存储结果。

对于网站而言,浏览器可以保存图片、样式表、JavaScript或整个页面的副本。下次用户需要该资源(例如每个页面都出现的脚本或logo)时,浏览器无需再次下载。 更少的下载意味着更快、更愉快的网站。

下面快速回顾一下网页浏览器如何从服务器获取页面:

HTTP request

  1. 浏览器:哟!你有index.html吗?
  2. 服务器:(正在查找……)
  3. Sever: Totally, dude! It’s right here!
  4. 浏览器:太棒了,我现在就下载并展示给用户。

(实际的HTTP协议可能存在细微差异。)

缓存的丑陋秘密:它会过期失效

缓存看起来简单又有趣。浏览器保存文件副本(如logo图片),并在每个需要logo的页面上使用缓存副本。这样就不必再下载图片了,完美,对吧?

错了。当公司logo改变时会发生什么?Amazon.com变成Nile.com?Google变成Quadrillion?

我们遇到了问题。闪亮的新logo需要配合闪亮的新网站,缓存去死吧。

因此,即使浏览器有logo,它也不知道该图片是否可用。毕竟,服务器上的文件可能已经更改,可能有更新版本。

那么,如果我们无法确定文件是否可用,为什么还要费心缓存呢?幸运的是,有几种方法可以解决这个问题。

缓存方法 1:Last-Modified

一种解决办法是让服务器告诉浏览器 哪个版本 它正在发送的文件信息。服务器可以返回 Last-modified 一个日期连同文件(我们称它为logo.png),如下所示:

Last-modified: Fri, 16 Mar 2007 04:00:25 GMT
File Contents (could be an image, HTML, CSS, Javascript...)`

现在浏览器知道它获取的文件(logo.png)创建于2007年3月16日。下次浏览器需要logo.png时,它可以向服务器进行特殊检查:

HTTP caching last modified

  1. 浏览器:嘿,给我logo.png,但只有当它在2007年3月16日之后被修改过。
  2. 服务器:(检查修改日期)
  3. 服务器:嘿,你运气真好!它已经 未修改 自那个日期以来没被修改过。你拥有最新版本。
  4. 浏览器:太好了!我将向用户展示缓存版本。

发送简短的“未修改”消息是一种 快很多 相比需要重新下载文件,特别是对于巨大的JavaScript或图片文件,缓存节省了带宽。

缓存方法 2:ETag

通过修改时间比较版本通常有效,但可能会导致问题。如果服务器的时钟最初有误然后被修正了怎么办?如果夏令时提前到来而服务器未更新怎么办?缓存可能会不准确。

ETag来救援。ETag是赋予每个文件的唯一标识符。它类似于哈希或指纹:每个文件都有唯一的指纹,如果你更改文件(哪怕是一个字节),指纹也会改变。

服务器可以返回ETag(指纹),而不是返回修改时间:

ETag: ead145f
File Contents (could be an image, HTML, CSS, Javascript...)

ETag可以是任何唯一标识文件的字符串。下次浏览器需要logo.png时,它可以进行如下对话:

HTTP caching if none match

  1. 浏览器:如果没有任何内容匹配标签“ead145f”,能给我logo.png吗?
  2. 服务器:(检查logo.png的指纹)
  3. 服务器:你真走运!这个版本是“ead145f”。它是 未修改.
  4. 浏览器:太棒了!我向用户展示我的缓存版本。

就像last-modified一样, ETags解决了比较文件版本的问题只不过“if-none-match”比“if-modified-since”更难融入句子。但这是我的问题,不是你的。ETag非常好用。

缓存方法 3:Expires

缓存文件并与服务器检查很好,除了一件事: 我们仍在向服务器确认。 这就像每次做麦片时都要分析你的牛奶是否安全可饮用。当然,这比每次买一加仑要好,但也不是特别好。

那我们如何处理这种牛奶情况?通过一个 过期日期!

如果我们知道牛奶(logo.png)何时过期,我们一直使用到那个日期(如果你是个大学生,可能还会再用几天)。一旦过期,我们就联系服务器获取新的副本,并带有新的过期日期。头部看起来像这样:

Expires: Tue, 20 Mar 2007 04:00:25 GMT
File Contents (could be an image, HTML, CSS, Javascript...)

在此期间,如果我们处于过期期限内,我们甚至避免与服务器通信:

HTTP caching expires

这里没有对话;浏览器进行独白。

  1. 浏览器:自己,现在是否在2007年3月20日的过期日期之前?(假设是)。
  2. 浏览器:诚然,我将向用户展示缓存版本。

就是这样。Web服务器无需做任何事情。用户立即看到文件。

缓存方法 4:Max-Age

哦,我们还没完。Expires很好,但需要为每个日期计算。还有一个 max-age header 让我们可以这样说“此文件从今天起一周后过期”,这比设置一个明确的日期更简单。

Max-Age 以秒为单位。下面是一些快速的秒数换算:

  • 1天对应的秒数 = 86400
  • 1周对应的秒数 = 604800
  • 1个月对应的秒数 = 2629000
  • 1年对应的秒数 = 31536000(在互联网时间上实际上无限)

附加头部:Public 和 Private

缓存头的内容层出不穷。有时服务器需要控制某些资源何时被缓存。

  • Cache-control: public 意味着缓存版本可以被代理服务器和其他中间服务器保存,所有人都能看到它。
  • Cache-control: private 意味着对于不同用户,文件是不同的(例如他们的个人主页)。用户的私有浏览器可以缓存它,但公共代理不能。
  • Cache-control: no-cache 意味着该文件不应被缓存。这适用于URL看起来相同但内容可能变化的搜索结果之类的情况。

然而,要注意某些缓存指令仅适用于更新的 HTTP 1.1 浏览器。如果你正在对已认证页面进行特殊缓存,那么 了解更多关于缓存的内容.

好的,我说服了:启用缓存

首先,确保 Apache 启用了 mod_headers 和 mod_expires:

... list your current modules...
apachectl -t -D DUMP_MODULES

... enable headers and expires if not in the list above...
a2enmod headers
a2enmod expires

设置头信息的通用格式是

  • 要匹配的文件类型
  • 要设置的标头/过期时间

一个通用建议:资源变化越少(如图片、PDF 等),缓存时间应越长。如果它从不变化(每个版本都有不同的 URL),那么尽可能长时间地缓存它(例如一年)!

一种技巧:有一个加载文件(index.html)不被缓存,但它知道永久缓存项目的存储位置。用户始终会获取加载文件,但可能已经缓存了它所指向的资源。

以下配置设置基于 AskApache.

秒数计算器

所有时间都以秒为单位(A0 = 访问 + 0 秒)。

使用Expires首部

ExpiresActive On
ExpiresDefault A0

# 1 YEAR - doesn't change often
<FilesMatch "\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav)$">
ExpiresDefault A31536000
</FilesMatch>

# 1 WEEK - possible to be changed, unlikely
<FilesMatch "\.(jpg|jpeg|png|gif|swf)$">
ExpiresDefault A604800
</FilesMatch>

# 3 HOUR - core content, changes quickly
<FilesMatch "\.(txt|xml|js|css)$">
ExpiresDefault A10800
</FilesMatch>

同样,如果你知道某些内容(如 JavaScript)不会频繁更改,那么让“js”文件在一周后过期。

使用max-age首部:

# 1 YEAR
<FilesMatch "\.(flv|ico|pdf|avi|mov|ppt|doc|mp3|wmv|wav)$">
Header set Cache-Control "max-age=31536000, public"
</FilesMatch>

# 1 WEEK
<FilesMatch "\.(jpg|jpeg|png|gif|swf)$">
Header set Cache-Control "max-age=604800, public"
</FilesMatch>

# 3 HOUR
<FilesMatch "\.(txt|xml|js|css)$">
Header set Cache-Control "max-age=10800"
</FilesMatch>

# NEVER CACHE - notice the extra directives
<FilesMatch "\.(html|htm|php|cgi|pl)$">
Header set Cache-Control "max-age=0, private, no-store, no-cache, must-revalidate"
</FilesMatch>

最后一步:检查你的缓存

要查看你的文件是否被缓存,请执行以下操作:

  • 在线:在以下网站检查您的网站: Redbot (您会看到返回的标头,以及侧面的缓存摘要)
  • 在Firefox中:使用 FireBug或Live HTTP Headers 查看HTTP响应(304 Not Modified、Cache-Control等)。特别是,我会加载一个页面并使用Live HTTP Headers确保没有数据包被发送来加载图像、logo和其他缓存文件。如果按ctrl+刷新,浏览器将强制重新加载所有文件。
  • 在Chrome中:打开开发者工具 > 网络选项卡。在大小列中,您会看到“内存缓存”或“磁盘缓存”,而不是下载大小。(确保“禁用缓存”未启用!)

了解更多关于 缓存,或者 HTTP头字段。缓存不会帮助初始下载(那是由 gzip 负责的),但它会使未来的网站访问速度更快。

记住:创建唯一URL是通往缓存天堂的最简单方法。 祝你优化网站顺利!

本系列其他文章

  1. 如何通过 HTTP 缓存优化你的网站
  2. 如何通过 GZIP 压缩(Compression)优化你的网站
  3. 如何用 Firefox 调试 Web 应用
  4. 加快你的 Javascript 加载时间
  5. 加快你的 Javascript,第 2 部分:可下载示例!

加入 45 万月度读者

喜欢这篇文章?还有更多内容能帮你建立持久、直观的数学理解。加入通讯以获取额外内容和最新更新。