【问题标题】:File Caching: Query string vs Last-Modified?文件缓存:查询字符串与上次修改?
【发布时间】:2014-06-29 11:03:45
【问题描述】:

我在玩弄缓存我网站资产的方法,并注意到大多数与我类似的网站都使用查询字符串来覆盖缓存(例如:/css/style.css?v=124942823)

之后,我注意到每当我保存我的 style.css 文件时,最后修改的标题都被“更新”了,使得查询字符串变得不必要了。

所以我想知道:

  • 为什么这么多网站都使用“查询字符串”方法,而不是让 last-modified 标头发挥作用?
  • 是否应该取消设置 Last-modified 标头而只使用查询字符串? (这有什么特别的优势吗?)

【问题讨论】:

  • Chrome 的缓存非常激进,我看到它拒绝抓取更新的 css/js 文件,即使选项卡已关闭、打开一个新选项卡,甚至几天后,跨多台计算机也是如此.我最近不得不回退到将版本放在我们 Azure 托管的 web 应用程序的查询字符串中。虽然服务器可能没有发送正确的标头日期,但我还没有验证它。
  • Chuck Norris 使用查询字符串。

标签: javascript html css apache caching


【解决方案1】:

TL;DR

为什么这么多网站都使用“查询字符串”方法,而不是让 last-modified 标头发挥作用?

更改查询字符串会更改 url,确保内容是“新鲜的”。

我是否应该取消设置 Last-modified 标头而只使用查询字符串?

没有。虽然这几乎是正确的答案。


网络上使用了三种基本的缓存策略:

  • 没有缓存,或者缓存被禁用
  • 使用验证/条件请求
  • 永久缓存

为了说明这三个方面,请考虑以下场景:

用户第一次访问一个网站,加载十个页面然后离开。每个页面加载相同的 css 文件。对于上述每种缓存策略,会发出多少请求?

无缓存:10 个请求

在这种情况下,应该清楚没有任何其他因素影响结果,对 css 文件的 10 次请求将导致它被发送到客户端(浏览器)10 次。

优势

  • 内容始终新鲜
  • 无需任何努力/管理

缺点

  • 效率最低,内容总是传输

验证请求:10 个请求

如果使用Last-ModifiedEtag,则也有 10 个请求。但是其中 9 个只是标题,没有正文被传输。客户端使用条件请求来避免重新下载它已经拥有的东西。以本网站的 css 文件为例。

第一次请求文件时,会发生以下情况:

$ curl -i http://cdn.sstatic.net/stackoverflow/all.css
HTTP/1.1 200 OK
Server: cloudflare-nginx
Date: Mon, 12 May 2014 07:38:31 GMT
Content-Type: text/css
Connection: keep-alive
Set-Cookie: __cfduid=d3fa9eddf76d614f83603a42f3e552f961399880311549; expires=Mon, 23-Dec-2019 23:50:00 GMT; path=/; domain=.sstatic.net; HttpOnly
Cache-Control: public, max-age=604800
Last-Modified: Wed, 30 Apr 2014 22:09:37 GMT
ETag: "8026e7dfc064cf1:0"
Vary: Accept-Encoding
CF-Cache-Status: HIT
Expires: Mon, 19 May 2014 07:38:31 GMT
CF-RAY: 1294f50b2d6b08de-CDG
.avatar-change:hover{backgro.....Some KB of content

对同一网址的后续请求如下所示:

$ curl -i -H "If-Modified-Since:Wed, 30 Apr 2014 22:09:37 GMT" http://cdn.sstatic.net/stackoverflow/all.css
HTTP/1.1 304 Not Modified
Server: cloudflare-nginx
Date: Mon, 12 May 2014 07:40:11 GMT
Content-Type: text/css
Connection: keep-alive
Set-Cookie: __cfduid=d0cc5afd385060dd8ba26265f0ebf40f81399880411024; expires=Mon, 23-Dec-2019 23:50:00 GMT; path=/; domain=.sstatic.net; HttpOnly
Cache-Control: public, max-age=604800
Last-Modified: Wed, 30 Apr 2014 22:09:37 GMT
ETag: "8026e7dfc064cf1:0"
Vary: Accept-Encoding
CF-Cache-Status: HIT
Expires: Mon, 19 May 2014 07:40:11 GMT
CF-RAY: 1294f778e75d04a3-CDG

注意没有正文,响应是304 Not Modified。这告诉客户端它已经拥有的(在本地缓存中)该 url 的内容仍然是新鲜的。

这并不是说这是最佳方案。使用诸如the network tab of chrome developer tools 之类的工具可以让您准确了解请求需要多长时间以及执行的操作:

由于响应没有正文,响应时间将大大缩短,因为要传输的数据更少。但仍有回应。 还有连接到远程服务器的所有开销。

优势

  • 内容始终新鲜
  • 仅发送了一个“完整”请求
  • 仅包含标头的九个请求要小得多
  • 更高效

缺点

  • 仍然发出最大请求数
  • 仍会引发 DNS 查找
  • 仍然需要与远程服务器建立连接
  • 不能离线工作
  • 可能需要服务器配置

永久缓存:1 个请求

如果没有 etags,没有最后修改的标头,并且只有一个过期标头设置在很远的将来 - 只有对 url 的第一次访问才会导致与远程服务器的任何通信。这是well-known? best practice for better frontend performance。如果是这种情况,对于后续请求,客户端将从自己的缓存中读取内容,根本不与远程服务器通信。

这具有明显的性能优势,这在延迟可能很明显(委婉地说)的移动设备上尤其显着。

优势

  • 最高效,内容只传输一次

缺点

  • 网址必须更改以防止现有访问者加载过时的缓存版本
  • 设置/管理的最大努力

不要使用查询字符串来破坏缓存

站点使用查询参数是为了规避客户端的缓存。当内容更改时(或者如果发布了新版本的站点),查询参数会被修改,因此当 url 发生更改时,将请求该文件的 new 版本。这比每次更改文件时重命名文件的工作量更少/更方便,但它并非没有问题,

Using query strings prevents proxy caching,在下面的引用中,作者证明来自浏览器代理缓存服务器网站的请求没有使用代理缓存:

加载 mylogo.gif?v=1.2 两次(清除其间的缓存)结果 在这些标题中:

>> GET http://stevesouders.com/mylogo.gif?v=1.2 HTTP/1.1
<< HTTP/1.0 200 OK
<< Date: Sat, 23 Aug 2008 00:19:34 GMT
<< Expires: Tue, 21 Aug 2018 00:19:34 GMT
<< X-Cache: MISS from someserver.com
<< X-Cache-Lookup: MISS from someserver.com

>> GET http://stevesouders.com/mylogo.gif?v=1.2 HTTP/1.1
<< HTTP/1.0 200 OK
<< Date: Sat, 23 Aug 2008 00:19:47 GMT
<< Expires: Tue, 21 Aug 2018 00:19:47 GMT
<< X-Cache: MISS from someserver.com
<< X-Cache-Lookup: MISS from someserver.com

很明显,代理没有提供第二个响应: 缓存响应标头说 MISS,Date 和 Expires 值发生变化, 并跟踪 stevesouders.com 访问日志显示两个命中。

不应掉以轻心 - 当访问物理上位于世界另一端的网站时,响应时间可能会非常慢。从位于路由沿线的代理服务器获得答案可能意味着网站是否可用之间的区别 - 在永久缓存资源的情况下,这意味着 url 的第一次加载很慢,在使用验证请求的情况下它意味着整个网站会很慢。

取而代之的是版本控制资产

“最佳”解决方案是对文件进行版本控制,这样每当内容更改时,url 也会随之更改。通常,这将作为构建过程的一部分自动执行。

然而,一个近乎妥协的是实现重写规则such as

# ------------------------------------------------------------------------------
# | Filename-based cache busting                                               |
# ------------------------------------------------------------------------------

# If you're not using a build process to manage your filename version revving,
# you might want to consider enabling the following directives to route all
# requests such as `/css/style.12345.css` to `/css/style.css`.

# To understand why this is important and a better idea than `*.css?v231`, read:
# http://stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring

<IfModule mod_rewrite.c>
   RewriteCond %{REQUEST_FILENAME} !-f
   RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpe?g|gif)$ $1.$3 [L]
</IfModule>

这样,foo.123.css 的请求被服务器处理为foo.css - 这具有使用查询参数进行缓存清除的所有优点,但没有禁用代理的问题缓存。

【讨论】:

  • 嘿,我正在实施这个,我想知道,如果很多人坚持认为这是一种不好的做法,为什么这个网站以及其他网站会在他们的资产上使用查询字符串?如果不是为了缓存破坏,是否还有其他理由将“版本”添加为查询字符串?谢谢!
  • 可能只是为了方便。如果您使用的是 cdn,则不使用代理缓存的相关性较低 - 尽管根据 cdn 它甚至可能会忽略查询字符串,因此更改它没有效果。这将是 meta.stackexchange.com 获得明确答案的问题。
  • “不要使用查询字符串来破坏缓存” - 那么this
  • “不要使用查询字符串来破坏缓存”是 2008 年过时的概念。 bizcoder.com/caching-resources-with-query-strings
  • @DavidLin 太真实了。现在even SO itself uses this method.
【解决方案2】:

Last-Modified 标头在不同浏览器中的应用不同,但通常浏览器会发出一个条件 GET 请求,如果需要更新缓存,服务器必须响应该请求。例如,在 Firefox 中...

“Last-Modified”响应标头可用作弱验证器。 它被认为是弱的,因为它只有 1 秒的分辨率。如果 响应中存在“Last-Modified”标头,然后客户端可以 发出“If-Modified-Since”请求标头来验证缓存 文件。

当发出验证请求时,服务器可以忽略 验证请求和响应正常 200 OK,或者它可以返回 304 Not Modified 指示浏览器使用其缓存副本。这 后一个响应还可以包括更新过期的标头 缓存文档的时间。

通过设置时间戳(或指纹),您可以明确地让浏览器知道它何时需要更新其缓存,然后您可以设置非常长的过期时间。

可能值得注意的是,rails 资产管道 (http://guides.rubyonrails.org/asset_pipeline.html) 上的文档引用了指纹识别优于查询字符串时间戳的 3 个优势:

  • 并非所有缓存都能可靠地缓存仅包含文件名的内容 因查询参数而异
  • 文件名可以在多服务器环境中的节点之间更改。
  • 缓存失效过多

有关缓存的更多详细信息和最佳实践: https://developers.google.com/speed/docs/best-practices/caching

【讨论】:

    猜你喜欢
    • 2014-02-06
    • 1970-01-01
    • 2011-08-24
    • 1970-01-01
    • 2021-04-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多