浏览器的缓存机制

浏览器缓存是一种存储技术,用于保存用户已经下载过的网页资源,比如HTML页面、图片、JavaScript脚本和样式表等。浏览器缓存的目的在于提高网页的加载速度,并减少网络流量。

浏览器缓存的工作原理

当用户访问一个网页时,浏览器会检查是否有该网页的相关资源的副本存储在缓存中。如果有,浏览器会根据缓存的相关规则来判断这些资源是否还有效,进而决定是直接使用缓存资源,还是向服务器重新请求资源。

强缓存

强缓存是指浏览器在加载资源时,不会向服务器发送请求,直接从自己的缓存中读取资源。强缓存是通过HTTP响应头中的Cache-ControlExpires字段来控制的。

Cache-Control

Cache-Control头部是HTTP/1.1中引入的缓存控制机制,非常灵活,可以通过多种指令来定义缓存策略。

常用指令:

  • max-age=<seconds>: 设置客户端和代理服务器的缓存时间,表示缓存内容将在指定的秒数后失效。
  • public: 指示响应可被任何缓存区缓存(即客户端和代理服务器都可缓存)。
  • private: 只允许被客户端缓存(默认值)。
  • no-cache: 允许被客户端和代理服务器缓存,不管本地副本是否过期,使用资源副本前,一定要到源服务器进行副本有效性校验。
  • must-revalidate: 允许被客户端和代理服务器缓存,本地副本过期前,可以使用本地副本;本地副本一旦过期,必须去源服务器进行有效性校验。
  • no-store: 绝对禁止缓存,不保存任何副本。

示例:

Cache-Control: public, max-age=3600

这个示例告诉浏览器和所有的中间缓存(例如代理服务器或 CDN),这个响应在接下来的3600秒内是有效的,在此期间,浏览器在需要这个资源时,不会再去服务器请求新的副本。而是会直接从浏览器本地缓存中加载资源。

Expires

Expires头部是HTTP/1.0中存在的字段,指定了一个日期/时间,过了这个时间点,资源就被认为是过期的(过时不建议使用)。

示例:

Expires: Tue, 20 Apr 2023 04:00:00 GMT

这个示例为资源设置了一个过期时间,过了这个时间,资源将考虑为过期。

强缓存的优先级

如果同时指定了Cache-ControlExpires,那么Cache-Control将优先考虑,因为Expires是HTTP/1.0的遗留字段。

如何设置强缓存

强缓存的设置通常是在服务器配置中完成的。以下是在某些常见Web服务器上设置强缓存的方法:

Apache:

.htaccess文件中,你可以添加如下配置:

<FilesMatch ".(jpg|jpeg|png|gif|js|css)$">
  Header set Cache-Control "max-age=31536000, public"
</FilesMatch>

Nginx:

在Nginx的配置文件(通常是nginx.conf)中,你可以添加如下配置:

location ~* \.(jpg|jpeg|png|gif|js|css)$ {
  add_header Cache-Control "max-age=31536000, public";
}

强缓存的缺点

尽管强缓存可以显著提高网站性能,但是它也有缺点。强缓存可能导致用户在资源更新后依然看到旧版本的资源,这是因为缓存副本还在有效期内。因此,强缓存需要谨慎设置,并结合资源的更新策略进行。

协商缓存

协商缓存(Conditional Caching)是浏览器和服务器之间协商使用缓存内容的一种机制。它是当强缓存(如直接使用Cache-Controlmax-ageExpires)失效时使用的一种备选方案。

工作原理

协商缓存的核心是在服务器和浏览器之间进行有效性确认。浏览器会发送一个请求到服务器,带上上次加载资源时服务器提供的某些标记信息。服务器根据这些信息判断资源是否被修改过:

  • 如果资源没有变化,服务器返回一个304状态码,告诉浏览器可以安全地使用缓存的版本。
  • 如果资源发生了变化,服务器返回新的资源和200状态码,浏览器将更新缓存,并渲染新的资源。

协商缓存的关键字段:

  • Last-Modified / If-Modified-Since
  • ETag / If-None-Match

Last-Modified/If-Modified-Since

这是协商缓存中一对非常基本的头。服务器通过Last-Modified响应头告诉浏览器资源的最后修改时间。当浏览器在之后的请求中检查这一资源时,会在请求头中添加If-Modified-Since,其中包含之前Last-Modified中的时间戳。服务器收到这个请求后会比较资源当前的修改时间与请求头中的时间戳:

  • 如果资源自上次请求后未发生变化,则返回304 Not Modified,不包含资源内容。
  • 如果资源发生了变化,则返回200 OK和更新后的资源。

ETag/If-None-Match

ETag/If-None-Match是另一对用于实现协商缓存的HTTP头。ETag是资源的特定版本的唯一标识符,通常是一个标记资源内容的hash值。当浏览器请求资源时,服务器在响应头中返回ETag。在后续的请求中,浏览器将这个值放在If-None-Match请求头中发送给服务器,服务器根据ETag判断资源是否有变动:

  • 如果没有变化,则返回304 Not Modified
  • 如果有变化,则返回200 OK和新的ETag值。

ETag通常更精确,因为它能够检测到文件内容的实际改变,而Last-Modified可能因为文件的修改日期变化而导致不必要的资源传输。

设置协商缓存

协商缓存的设置通常由服务器来控制,可以通过配置服务器的相关文件或代码来实现。以下是一些常见的设置示例:

在Apache服务器中,可以修改.htaccess文件来设置缓存策略:

<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresDefault "access plus 1 week"
  Header set Cache-Control "must-revalidate"
</IfModule>

而在Nginx服务器中,可以在配置文件中添加相应指令:

location ~* \.(jpg|jpeg|gif|png|css|js)$ {
  expires 7d;
  add_header Cache-Control "public, must-revalidate";
}

在程序代码中设置协商缓存的例子(例如Node.js/Express应用):

app.get('/script.js', function(req, res) {
  res.setHeader('Cache-Control', 'must-revalidate');
  // 设置ETag或Last-Modified
  res.setHeader('ETag', '<your-etag-value>');
  // 或者
  res.setHeader('Last-Modified', '<your-last-modified-date>');
  res.sendFile('/path/to/script.js');
});

优点和注意事项

  • 减少不必要的数据传输:如果内容未变更,减少了数据传输量,节省了带宽,提升了性能。
  • 实时更新:如果服务器上的内容更新了,协商缓存确保用户能够接收到最新的资源。
  • 额外的请求开销:每次协商缓存都需要客户端和服务器之间的一次往返。虽然不下载资源内容,但是如果网页包含大量资源且经常变更,这无疑会造成许多304状态的请求,增加服务器负担。

协商缓存是优化应用性能的关键工具之一,但需要与其他类型的缓存和性能优化策略结合使用,才能取得最佳效果

> cd ..