为了提高Web服务器的性能,其中的一种可以提高Web服务器性能的方法就是采用缓存技术。
1.缓存
1.1.什么是缓存?
如果某个资源的计算耗时或耗资源,则执行一次并存储结果。当有人随后请求该资源时,返回存储的结果,而不是再次计算。
1.2.新鲜度和陈旧度
问题的关键在于“计算”不是数学计算。在数学中,计算的结果随时间而变化。在网络上,您昨天请求的资源可能与您今天请求的资源不同。因此就会有新鲜度和陈旧度两个关键概念。
A fresh response is one whose age has not yet exceeded its freshness lifetime. Conversely, a stale response is one where it has.
A response’s freshness lifetime is the length of time between its generation by the origin server and its expiration time. An explicit expiration time is the time at which the origin server intends that a stored response can no longer be used by a Cache without further validation, whereas a heuristic expiration time is assigned by a Cache when no explicit expiration time is available. A response’s age is the time that has passed since it was generated by, or successfully validated with, the origin server.
When a response is “fresh” in the cache, it can be used to satisfy subsequent requests without contacting the origin server, thereby improving efficiency.
— RFC 7234 - 4.2. Freshness
2.如何进行客户端Web资源缓存?
2.1.以前的方法
万维网刚开始时相对简单。客户端会发送请求,服务器会返回所请求的资源。当资源是页面时,它是静态页面还是服务器呈现的页面并不重要。因此,早期的客户端缓存相当简单。
2.2.现在的方法
2.2.1.Web资源缓存规范
Web 缓存的第一个规范于 2014 年在RFC 7234(又名HTTP/1.1 缓存)中定义。请注意,自 2022 年起,它已被RFC 9111取代。
2.2.2.具体的方法
2.2.2.1.Pragma HTTP 标头
已经弃用
2.2.2.2.Expire HTTP 响应标头
最直接的缓存管理是通过Expire响应标头。当服务器返回资源时,它会指定缓存在哪个时间戳之后过期。请求缓存资源时,浏览器有两个选项:
- 当前时间在到期时间戳之前:资源被视为新鲜,浏览器从本地缓存中提供该资源
- 或者是之后:资源被视为过时,并且浏览器需要从服务器获取资源,因为它未被缓存
2.2.2.2.1.优点
它完全是本地决策。它不需要向服务器发送请求
2.2.2.2.2.不足
-
是否使用本地缓存资源的决定是基于启发式的。尽管值Expiry是未来的,但资源可能已在服务器端发生变化,因此浏览器会提供过期的资源。相反,浏览器可能会因为时间已到而发送请求,但资源尚未发生变化。
-
Expire这非常基本。资源要么是新鲜的,要么是陈旧的;要么从中返回,Cache要么再次发送请求,控制的维度有限。
2.2.2.3.Cache-Control
Cache-Control的目标:
- 绝不缓存资源
- 在提供资源之前,验证是否应从缓存中提供资源
- 中间缓存(代理)可以缓存资源吗?
Cache-Control的基本实现流程:
由于Cache-Control和Expire一样都是客户端本地的,具体来说就是如果需要,浏览器会从其缓存中提供资源,而无需向服务器发出任何请求。
2.2.2.4.Last-Modified 和 ETag
为了避免提供过期资源的风险,浏览器必须向服务器发送请求。输入Last-Modified响应标头。 If-Modified-Since与请求Last-Modified标头配合使用:
The If-Modified-Since request HTTP header makes the request conditional: the server sends back the requested resource, with a 200 status, only if it has been last modified after the given date. If the resource has not been modified since, the response is a 304 without any body; the Last-Modified response header of a previous request contains the date of last modification. Unlike If-Unmodified-Since, If-Modified-Since can only be used with a GET or HEAD.
注意:
具有和其他非幂等方法If-Unmodified-Since的相反功能:它返回HTTP 错误,以避免覆盖已更改的资源。POST412 Precondition Failed。
分布式系统中时间戳的问题在于无法保证系统中的所有时钟都具有相同的时间。时钟以不同的速度漂移,需要定期同步Last-Modified到同一时间。因此,如果生成标头的服务器和接收标头的服务器If-Modified-Since不同,则结果可能会因它们的漂移而出乎意料。请注意,这也适用于标Expire头。
Etags 是时间戳的替代方案,可避免上述问题。服务器计算所提供资源的哈希值,并将ETag包含该值的标头与资源一起发送。当新请求包含If-None-Match哈希值时,服务器会将其与当前哈希值进行比较。如果它们匹配,则返回304如上所述的哈希值。
与仅提供时间戳相比,计算哈希值的开销较小,但现在被认为是一种很好的做法。
2.2.2.5.缓存 API
客户端最新的缓存方式是通过Cache API。它提供了一个通用的缓存接口:你可以将其视为浏览器提供的本地键值。
缓存 API提供以下方法:
- Cache.match(request, options)
返回Promise解析与对象中第一个匹配的请求相关的响应Cache。
- Cache.matchAll(request, options)
返回Promise解析为对象中所有匹配响应的数组Cache。
- Cache.add(request)
获取 URL,检索它并将生成的响应对象添加到给定的缓存中。这在功能上等同于调用fetch(),然后使用put()将结果添加到缓存中。
- Cache.addAll(requests)
获取 URL 数组,检索它们,并将生成的响应对象添加到给定的缓存中。
- Cache.put(request, response)
接受请求及其响应并将其添加到给定的缓存中。
- Cache.delete(request, options)
查找Cache以请求为键的条目,如果找到匹配的条目并将其删除,则返回Promise解析为的条目。如果未找到任何条目,则解析为。trueCacheCachePromisefalse
- Cache.keys(request, options)
返回Promise解析为键数组的Cache。
Cache API 与Service Workers配合使用。流程很简单:
首先在 URL 上注册一个 Service Worker,浏览器在 URL 获取调用之前调用该 worker,从 worker 中,你可以从缓存中返回资源,并避免向服务器发出任何请求,它允许我们在初始加载后将资源放入缓存中,以便客户端可以离线工作 ,而具体效果取决于用例。