1. 概述
1.1 是否有必要使用HTTP缓存
当下中国国内一般的家庭网络都可以达到百兆,千兆,下载速度很快,是否有必要使用http缓存?
下载速度只是一方面,对于网站运营者来说,还有带宽成本,如果用户量非常大,节省的带宽成本也会非常可观。
以下是腾讯云服务器套餐,流量和价格成正比:
另外,有的场景"更快"的体验感觉比"快"好很多,所以自然是能更快最好。
所以,使用HTTP缓存仍然非常必要。
1.2 本文介绍
相信不少读者对http缓存已经非常熟悉,为了帮助读者快速了解此文是否有帮助,先简单做个内容介绍。
● 知识点
除了代理缓存,中间缓存外,本文对http缓存知识点做了较为详细的梳理,并结合例子进行直观说明,除了基础知识外,还包含和解答如下内容:
1)是否要使用启发式缓存?
2)多端适配(手机端/PC)出现缓存问题时如何解决?
3)何时使用immutable指令?
4)最佳实践总结
5)缓存指令使用快速参考表
● 落地实践
本文基于nginx + spring + vue的技术架构分析要使用的缓存策略,并详细描述了实施指南,可作为落地参考。
2. http缓存知识点
2.1 强缓存
强缓存也可以称为显式缓存,它存储Web资源并直接从客户端(如浏览器)的缓存中检索,无需每次都向服务器发起请求。
强缓存依赖HTTP响应头中的特定字段,如Cache-Control和Expires,来指示资源可以被缓存多久,从而减少网络延迟和带宽使用,加快网页加载速度。
2.1.1 指令说明
指令 | 作用 | 例子 |
---|---|---|
public | 响应可以被任何缓存区缓存,包括客户端浏览器、代理服务器等。 | Cache-Control: public, max-age=31536000 资源可以被公开缓存,有效期为一年。 |
private | 响应为单个用户定制,只能被用户的浏览器缓存,不能被共享缓存缓存。 | Cache-Control: private, max-age=3600 资源私有,且最大缓存时间一小时。 |
no-cache | 强制客户端向服务器验证缓存的响应是否仍然有效,即使它仍在有效期内也要验证。 | Cache-Control: no-cache 要求每次请求时都必须向服务器验证资源的有效性。 |
no-store | 完全禁止缓存,每次请求都会下载完整的响应 | Cache-Control: no-store 不应存储任何关于客户端请求和服务器响应的任何部分。 |
max-age | 指定一个时间长度,资源在此时间内被认为是新鲜的,在此时间内客户端将使用缓存资源。 | Cache-Control: max-age=86400 资源可以在缓存中存储并被认为是新鲜的最长之间为24小时(86400秒)。 |
must-revalidate | 一旦资源过期(即max-age时间已过),在使用缓存之前,缓存必须向服务器验证这个响应的状态。 | Cache-Control: must-revalidate, max-age=3600 资源最多可以缓存一个小时,但一旦过期,必须重新验证。 |
s-maxage= | 类似于max-age,但仅适用于共享缓存(如代理服务器),并且其优先级高于max-age和Expires头。 | Cache-Control: s-maxage=7200, max-age=3600 共享缓存中的资源可以缓存7200秒,而私有缓存(如浏览器)中的资源可以缓存3600秒。 |
Expires | HTTP/1.0缓存控制头部,指定一个日期/时间,在这之后资源被认为是过期的。虽然Cache-Control更加灵活且优先级更高,但Expires可以作为一个后备选项用于兼容http/1.0。 | Expires: Sun, 26 Apr 2024 18:00:00 GMT 最大过期时间 2024/04/26 18:00:00 |
2.1.2 要点
● Expires指令
Expires指定的是绝对时间,当客户端和服务端的系统时钟不一致时,可能出现客户端使用失效缓存的问题,例如客户端比服务端晚8小时,那么服务端资源失效时,客户端缓存资源要晚8小时后才失效,在此期间,客户端访问的缓存资源是旧数据。
注意Cache-Control指令的优先级高于Expires指令,Expires是http1.0的指令。
● max-age指令
max-age指定了一个相对的资源新鲜度时间,并没有解决可能访问到失效缓存资源的问题,使用时要考虑访问失效缓存资源的容忍度,容忍度大则设置大一些,容忍度小则设置小一些,完全不能容忍则不能使用。
每个客户端发起请求的时间不同,因此下次获取到新鲜资源的时间也不同,示例如下:
访问次数 | 客户端A | 客户端B |
---|---|---|
首次访问 | 从服务端下载资源。 | 从服务端下载资源。 |
二次访问 | 距离上次从服务端下载资源未超过max-age指定的8小时,从缓存获取资源。 | 同客户端A。 |
三次访问 | 距离上次从服务端下载资源超过了max-age时间,从服务端下载资源,资源新鲜度时间重新从8:00开始计算。 | 同客户端A,区别是资源新鲜度时间重新从10:00开始计算。 |
四次访问 | 距离上次从服务端下载资源超过了max-age时间,从服务端下载资源,因资源已更新,获取到的是更新后资源。 | 距离上次从服务端下载资源未超过max-age时间,从缓存获取资源,缓存资源是旧资源。 |
五次访问 | 距离上次从服务端下载资源未超过max-age时间,从缓存获取资源。 | 距离上次从服务端下载资源超过了max-age时间,从服务端下载资源,因资源已更新,获取到的是更新后资源。 |
● no-cache指令
使用no-cache指令时,客户端每次访问资源时都会向服务端询问资源是否更新,没有更新则使用缓存资源,否则会下载新资源并使用。
no-cache与Expires和max-age的区别是no-cache每次都会向服务端发起请求,而在Expires指定的绝对时间之前,或在max-age指定的相对时间之前客户端不会向服务端发起请求而是直接使用缓存资源。比较如下:
no-cache的优点是保证每次访问都能获取最新资源,缺点是每次都要和服务端交互,不过当资源没有更新时只会返回http响应头,比起同时返回资源数据也能节省一些资源下载时间。
● max-age=0和must-revalidate的组合与no-cache具有相同含义
xml
Cache-Control: max-age=0, must-revalidate
Cache-Control: no-cache
max-age=0 意味着响应立即过时,而 must-revalidate 意味着一旦过时就不得在没有重新验证的情况下重用它(例如网络受限时),因此,结合起来后,语义与 no-cache 相同。
max-age=0 是为了解决 HTTP/1.1 之前的许多实现无法处理 no-cache 指令。
现在符合 HTTP/1.1 的服务器已经广泛部署,所以没有理由使用 max-age=0 和 must-revalidate 组合。
2.2 协商缓存
协商缓存,又称验证缓存,它让客户端与服务器之间协商内容的新鲜度,以确定是否需要传输新的资源版本。
它依赖特定的HTTP头部来实现,允许客户端存储一份资源副本,并在后续请求中询问服务器该资源是否有更新,如果资源未更新,服务器将回应一个状态码304,指示客户端可以安全地使用缓存资源,避免重新下载资源,从而节省带宽并加快加载时间。
2.2.1 指令说明
指令 | 作用 | 例子 |
---|---|---|
ETag | 服务端响应给客户端,值为服务端资源唯一标识。 | ETag: "abcd1234" |
If-None-Match | 客户端发送给服务端,值为服务端之前响应的ETag,服务端判断是否和资源ETag一致,一致返回状态码304,客户端使用缓存资源,不一致则返回状态码200,同时返回变化后的资源。 | If-None-Match: "abcd1234" |
Last-Modified | 服务端响应给客户端,值为服务端资源的最后修改日期。 | Last-Modified: Wed, 21 Oct 2015 07:28:00 GMT |
If-Modified-Since | 客户端发送给服务端,值为服务端之前响应的资源的Last-Modified,服务端判断是否和资源的最后修改日期一致,一致返回状态码304,客户端使用缓存资源,不一致则返回状态码200,同时返回变化后的资源。 | If-Modified-Since: Wed, 21 Oct 2015 07:28:00 GMT |
2.2.2 要点
2.2.2.1 ETag和Last-Modified比较
● 精确度
ETag比Last-Modified精确度更高,因为它基于资源的实际内容生成,即使文件在几秒内多次修改,ETag也能精确反映这些变化。
● 性能
生成和验证ETag可能比比较时间戳(使用Last-Modified)更耗费资源,尤其是对于大型文件或需要计算哈希值的场景。
● 应用场景
Last-Modified适合内容更新不是非常频繁,且以时间为主要修改标准的资源;而ETag适合内容动态生成或频繁更改的资源,以及在毫秒级别内可能发生变化的资源。
2.2.2.2 服务端判断资源更新方法
服务端可以通过ETag和Last-Modified指令来判断资源是否被更新,这是http协议支持的标准方式,除此之外还有其他方式也能实现,例如使用资源版本控制、资源内容hash值、自定义头部等等,但这些都是非标方式,需要定制实现。
2.3 启发式缓存
启发式缓存是一种缓存机制,使Web服务器和客户端(如Web浏览器)在没有明确缓存控制指令的情况下(如Cache-Control或Expires头部),可以推断资源的新鲜度或其有效期从而决定是否使用缓存。
2.3.1 例子
● Last-Modified
当服务器返回资源时,包含Last-Modified头部,指示资源最后一次被修改的时间。客户端(如浏览器)可以基于这个日期和当前日期的差异来推断资源的最大年龄。例如,如果资源在一周前最后修改,缓存可能决定将其保留一定时间(如几天),假设在此期间资源未发生变化。
下面是一个没有指定缓存策略的示例,:
首次访问请求:
第二次访问请求:
应答中"200 OK (from disk cache)"含义为:不访问服务器,直接从磁盘中读取缓存。
● Date
如果响应中包含Date头部但缺少Expires或Cache-Control指令,客户端可能使用响应的日期和当前时间的差异来估算缓存时间。例如,如果一个响应被生成并在几分钟前发送,缓存机制可能会推断这个响应可以被短暂缓存。
● Heuristic Expiry
某些客户端和代理服务器使用固定比例的启发式过期策略,如将资源的生命周期估计为最后修改时间和首次获取时间之间差的10%,这种方法虽不精确,但在缺乏其他指示时提供了一种缓存策略。
● ETag and If-None-Match
虽然ETag和If-None-Match头部通常用于验证缓存的资源是否仍然是最新的,但也可以在启发式缓存决策中发挥作用。
注意:它们不会触发启发式缓存,只是在启发式缓存被触发后发挥作用。
2.3.2 要点
● 不要依赖启发式缓存
启发式缓存可以在缺乏明确缓存指示的情况下优化资源的重新获取,减少带宽使用,提高加载速度,但是为了避免潜在的缓存过期问题(例如不同的浏览器处理逻辑差异,不同的web server默认响应的指令有差异),应尽可能明确缓存指示。
● Last-Modified
如果缺乏Cache-Control或Expires头,即没有明确的缓存过期指示时,缓存服务器可能会使用启发式缓存,此时使用Last-Modified可用于启发式缓存。
当HTTP响应包含Cache-Control头,并且指定了如max-age或must-revalidate等指令时,这明确了缓存的行为和有效期,在缓存过期后,Last-Modified可以用于协商缓存。
2.4 immutable指令
immutable指令告诉浏览器和其他缓存代理,一旦某个资源被缓存,则在其有效期内不会改变,即使用户执行了强制刷新操作(如按下Ctrl+F5)。
immutable指令减少了不必要的条件性请求,提高网站性能。
2.4.1 应用场景
immutable指令适用于不会改变的资源,如:
● 版本化的静态文件(如script.js?v=1.2)
● 长期不变的图像文件
● 构建后的CSS文件
使用immutable时要谨慎,确保只有真正不会在其max-age期限内改变的资源才使用此指令,如果错误地将immutable应用于可能会更新的资源上,可能会导致用户看到过时的内容,直到缓存过期或被手动清除。
2.4.2 使用示例
immutable需要和max-age一起使用:
xml
Cache-Control: max-age=31536000, immutable
这段指令告诉浏览器资源可以被缓存,且新鲜期是一年(31536000秒),在这一年内,即使用户尝试强制刷新页面,资源也不需要重新验证或下载。
2.5 Vary指令
2.5.1 业务场景
如果做过多端适配,当PC端和手机端访问的url相同,但实际页面视图不同(静态资源不同)时,先通过PC模拟手机端访问该url后再切回PC访问,就会发现界面展现的还是手机视图,即使F5刷新也没有用,这是因为模拟手机端访问时缓存了url对应的静态资源,切回PC时url没变,还会从缓存获取静态资源。
虽然Ctrl + F5可以强制刷新静态资源,但除了开发人员之外,一般的用户大多数并不知道可以如此操作。
对于这种场景,Vary指令就能发挥作用。
2.5.2 定义
指示响应是基于请求头中的一个或多个值而变化,这样可以帮助缓存服务器理解在什么情况下一个存储的响应可以被认为是另一个请求的有效响应。
2.5.3 作用
● 内容协商
Vary头部最常见的用途是实现内容协商,它允许服务器根据请求头(如Accept-Language、User-Agent、Accept-Encoding等)返回最适合用户需求的版本的资源。
例如,使用Vary: Accept-Encoding可以根据客户端是否支持压缩(如gzip)来提供不同的响应版本。
● 提升缓存效率
通过明确指示哪些请求头部影响响应内容,使得缓存服务器更精确地决定何时可以复用缓存的响应,减少不必要的服务器请求,提高网站加载速度和用户体验。
● 避免缓存污染
在没有正确使用Vary头部的情况下,缓存服务器可能会错误地将响应提供给不应该接收该响应的请求(例如,将压缩过的内容发送给不支持压缩的客户端),Vary指令可以避免这种缓存污染,确保所有用户都获得正确格式的内容。
● 支持多版本内容的缓存
对于同一URL提供的内容有多个版本的情况(比如不同的语言、不同的编码),Vary指令可以让缓存服务器针对每种情况存储和管理各自的缓存版本。
2.5.4 使用场景
● 语言变化
Vary: Accept-Language用于国际化网站,根据用户的语言偏好返回不同语言版本的内容。
● 客户端支持的编码
Vary: Accept-Encoding允许服务器基于客户端是否支持如gzip的编码方式来发送压缩或未压缩的内容。
● 设备类型
Vary: User-Agent可以根据用户的设备类型(如手机或桌面)提供优化的响应。
2.5.5 解决多端缓存问题实践
通过Vary: User-Agent,解决上面业务场景中提到的问题。
● 首先模拟手机端访问
● 查看手机User-Agent
● 切回PC访问
此时会访问手机端缓存的静态资源,F5刷新后没有用,看到的还是手机端视图。
● 添加Vary指令
nginx添加如下配置,http, server, location下都可以,这里添加到server下:
xml
add_header Vary User-Agent;
● 重新加载nginx配置
xml
nginx -s reload
● 重新按照前面步骤操作,最后切回pc访问
刷新后重新加载了pc的静态资源,不再使用手机端缓存,pc视图访问正常。
● 查看pc的User-Agent
和手机端不一样,说明Vary指令发挥了作用,静态资源缓存增加了User-Agent维度。
3. 落地实践
3.1 组网
组网如下:
● 通过Nginx做负载均衡,并以主从模式部署保证可用性,外部通过VIP访问,这样主从切换时不受影响。
● Web Server以集群方式部署,增加可靠性,提升性能。
● 静态资源部署在Web Server,并没有放到Nginx上,这将决定在哪里设置HTTP缓存响应头信息。
● 使用的核心开源软件:Vue + Spring + MyBatis + MySQL,不同的前端打包结果会影响静态资源的缓存处理策略,例如如果静态资源文件名带hash值,那么可以设置为永久不变,始终使用缓存资源。
● 支持多端不同视图(手机端和PC),但url相同,因此需要支持多端缓存。
3.2 HTTP缓存策略分析
3.2.1 资源缓存策略
● js
打包后文件名带hash值,可认为永久不变,使用immutable指令,max-value设置为10年。
● css
打包后文件名带hash值,可认为永久不变,使用immutable指令,max-value设置为10年。
● html
vue下只有index.html,可能变化,且如果不及时刷新会影响重新打包后生成的带hash值js、css、图片,因此需要及时更新,使用no-cache在每次访问资源时都做更新检查,同时使用Last-Modified指令提升访问性能。
● 图片
分为两类:
一类是应用使用的图片,此类在打包后文件名带hash值,可认为永久不变,使用immutable指令,max-value设置为10年。
另一类是业务数据使用的图片,此类文件名不会带hash值,需要根据实际情况决定,如果发布后就不会再更改,可认为永久不变,使用immutable指令;如果担心领导哪天突然要求调整一下,则要考虑变更是否要求在客户界面尽早体现,对于这种情况使用合适的max-value值,同时使用Last-Modified指令提升过期后访问性能。
这里认为可能会变,同时考虑变化后影响没有那么大,以及重复访问概率并不高,将max-value设置为1个月。
● 动态内容
通过Web Server查询DB返回,不做HTTP缓存。
这部分内容90%以上不会重复访问,因此也没有必要通过其他方案做客户端缓存,例如通过Ajax拦截做缓存。
3.2.2 资源缓存维度
需要考虑是否支持国际化,客户端支持的编码,以及是否区分PC和手机端。
这里不做国际化,客户端编码统一,仅区分PC和手机端,使用Vary指令实现。
3.3 缓存策略实施
3.3.1 公共部分
● 区分手机端和PC端
在nginx上配置,前面示例中已经提及,不再赘述。
● 使用强制缓存
不依赖启发式缓存,上面的分析中均会使用到no-cache或max-age,即使用Cache-Control明确缓存指示,使用强制缓存。
3.3.2 静态资源
hash值静态资源路径:
其他静态资源路径:
对应的缓存策略:
序号 | no-cache | max-value | immutable | Last-Modified |
---|---|---|---|---|
hash值js文件 | 不启用 | 10年 | 启用 | 启用 |
hash值css文件 | 不启用 | 10年 | 启用 | 启用 |
hash值应用图片 | 不启用 | 10年 | 启用 | 启用 |
业务图片 | 不启用 | 1个月 | 启用 | 启用 |
首页根目录资源 | 启用 | 不启用 | 不启用 | 启用 |
3.3.2.1 实践1-WebMvcConfigurer子类
通过实现WebMvcConfigurer类来实现,代码如下:
java
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// 带hash值的js、css、图片文件,资源有效期10年
registry.addResourceHandler("/assets/**")
.addResourceLocations("classpath:/static/assets/")
.setCacheControl(CacheControl.noCache().maxAge(3650, TimeUnit.DAYS));
// 业务图片,资源有效期30天
registry.addResourceHandler("/images/**")
.addResourceLocations("classpath:/static/images/")
.setCacheControl(CacheControl.noCache().maxAge(30, TimeUnit.DAYS));
// 根目录的首页等资源,资源有效期1天
registry.addResourceHandler("/**")
.addResourceLocations("classpath:/static/")
.setCacheControl(CacheControl.noCache().maxAge(1, TimeUnit.DAYS));
}
}
说明:
● Spring默认会启用Last-Modified,因此不需要显示设置
● 此方式不支持设置immutable指令,需要调整实现方式。
3.3.2.2 实践2-FilterRegistrationBean
通过FilterRegistrationBean来实现:
● 实现Filter
java
class StaticResourceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletResponse httpResponse = (HttpServletResponse) response;
httpResponse.setHeader("Cache-Control", "max-age=315360000, immutable");
chain.doFilter(request, response);
}
}
● 创建FilterRegistrationBean:
java
@Configuration
public class WebCacheConfigurer {
@Bean
public FilterRegistrationBean<StaticResourceFilter> imageResourceFilter() {
FilterRegistrationBean<StaticResourceFilter> registrationBean = new FilterRegistrationBean<>();
registrationBean.addUrlPatterns("/assets/*");
registrationBean.setFilter(new StaticResourceFilter());
return registrationBean;
}
}
至此,对于url路径/assets/*下的所有资源都设置了如下缓存策略:
xml
Cache-Control: max-age=315360000, immutable
由于各类资源的缓存策略不同,按照此方案就需要设置多个Filter以设置不同缓存策略。
但此方案下每个FilterRegistrationBean只能设置1个Filter,如果想创建多个Filter去匹配不同缓存策略,与之对应创建多个FilterRegistrationBean,则只会有一个生效,其余的不起作用。
3.3.2.3 实践3-优化FilterRegistrationBean
思路是FilterRegistrationBean通配"/*",即所有路径,然后在Filter中动态判断路径然后设置缓存指令。
● FilterRegistrationBean
匹配模式修改为如下:
java
registrationBean.addUrlPatterns("/*");
● StaticResourceFilter
java
class StaticResourceFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
String urlPath = httpRequest.getServletPath(); // 格式如: /images/04tech/性能优化/watermark/webp请求头.png
AntPathMatcher antPathMatcher = new AntPathMatcher();
String assetsPattern = "/assets/**"; // 带hash值的静态资源文件
String imagesPattern = "/images/**"; // 业务图片
String apiPattern = "/api/**"; // 后端api接口
HttpServletResponse httpResponse = (HttpServletResponse) response;
if (antPathMatcher.match(assetsPattern, urlPath)) {
httpResponse.setHeader("Cache-Control", "no-cache, max-age=315360000, immutable");
} else if (antPathMatcher.match(imagesPattern, urlPath)) {
httpResponse.setHeader("Cache-Control", "no-cache, max-age=2592000, immutable");
} else if (antPathMatcher.match(apiPattern, urlPath)) {
// 后端接口,防止意外,不设置任何缓存策略
} else {
// 默认是根路径下的静态资源
httpResponse.setHeader("Cache-Control", "no-cache, max-age=86400");
}
chain.doFilter(request, response);
}
}
4.3.3 实施前后响应头对比
● hash值js文件
实施前:
实施后:
● hash值css文件
实施前:
实施后:
● hash值应用图片
实施前:
实施后:
● 业务图片
实施前:
实施后:
● 首页根目录资源
实施前:
实施后:
4. 总结
4.1 最佳实践
4.1.1 使用Cache-Control
● 为可缓存资源设置适当的max-age
静态资源(如图片、CSS、JavaScript文件)应设置较长的max-age,以便在客户端长时间缓存。
也要考虑刚上线时图片是否能保持不变?例如领导突然发话说不好看,要调整一下。
● 对变化内容使用no-cache或no-store
对于频繁变动的内容,使用no-cache以确保内容总是最新的,或者no-store以完全禁止缓存。
除非每次访问资源都会变化,否则no-store应该很少使用。
● 利用immutable指令
对于一旦发布就不会更改的资源,使用Cache-Control: immutable,减少因用户强制刷新而造成的不必要请求。
懂得强制刷新并且会这么操作的用户能有多少?只能说能优化一点是一点,尽可能做到性能优化最大化。
4.1.2 利用ETag和Last-Modified验证
既能保持资源新鲜度,又能一定程度上提速降流(成本),能用则用。
● 配置ETag
利用ETag可以帮助浏览器判断资源是否已更改,如果未更改,可以避免重新下载资源,只需返回304状态码。
● 使用Last-Modified
Last-Modified标头可以用于验证资源的最后修改时间,如果资源自上次请求后未修改,只需返回304状态码。
4.1.3 设置Expires头部
尽管Cache-Control头部更灵活和强大,但为了向后兼容HTTP/1.0的客户端,也可以设置Expires头部为资源指定一个明确的过期时间。
http3都出来了,http2已经很常见,http/1.0还有多少用户?
4.1.4 利用Vary指令
当服务器根据请求头(如Accept-Encoding、User-Agent等)返回不同版本的资源时,使用Vary头部指示哪些请求头会影响到响应内容。这有助于正确的缓存代理行为,确保正确版本的内容被缓存和服务。
4.1.5 减少使用启发式缓存
启发式缓存的潜在问题
● 缓存时间可能不准确
启发式缓存有效期是估算的,可能不符合实际资源的更新频率,导致用户看到过时的内容,或者在资源尚未过时时过早地从缓存中清除资源。
● 难以控制
对于网站所有者而言,由于缓存策略是由缓存服务器通过启发式算法自动确定的,很难精确控制内容的缓存行为和有效期。
● 不一致的用户体验
不同的缓存服务器可能采用不同的启发式算法,导致同一资源在不同用户或不同缓存代理中的缓存时间不一致,从而影响用户体验。
4.1.6 版本化静态资源
对静态资源使用版本控制(如文件名哈希),以便在资源更新时,浏览器能自动请求新版本,而无需担心旧版本的缓存问题。
vue打包后的js,css文件就是一个例子。
4.1.7 监控和验证缓存策略
在项目和产品中对资源进行分类,定义好统一的缓存策略并实施。
定期审核和测试缓存策略的有效性,确保内容按预期被缓存并在必要时更新。
4.2 性能和资源新鲜度
使用http缓存时需要在性能和资源新鲜度之间进行取舍,两者不可兼得,各个指令和性能以及新鲜度的关系如下:
指令 | 说明 |
---|---|
no-store | 不使用缓存,每次都从服务端获取资源,因此总是能保持新鲜度,性能最差。 |
no-cache | 每次都要到服务端验证资源是否更新,因此也总是能保持新鲜度;当资源没有更新时,服务端只返回响应头,状态码304,不返回实际资源,此时性能高于no-store。 |
max-age和Expires | 客户端在指令指定的绝对时间和相对时间内使用缓存数据,超过时间后从服务端获取资源;使用缓存资源期间,新鲜度低,但减少了访问服务端频率,因此性能高。 |
max-age、Expires与ETag、Last-Modified组合使用 | 因为都是在超过指令指定的时间后到服务端获取新资源,因此新鲜度和上一条一样;但因为使用ETag、Last-Modified时服务端会判断资源是否更新,若未更新则只返回响应头,状态码304,不返回实际资源,此时性能会比上一条高一些。 |
must-revalidate | 该指令强制资源过期后(超出max-value或Expires),在使用缓存前,客户端必须向服务器验证资源有效性,哪怕是在网络连接受限的情况下,如果无法验证,客户端将无法使用过期的缓存内容,保证了用户不会接收到过期的数据。 它不会依赖客户端自主缓存策略,因此新鲜度比只使用max-value或Expires高一些。 |
注意:no-cache和must-revalidate需要结合ETag、Last-Modified指令一起使用,单独使用意义不大。
4.3 缓存指令使用快速参考
指令 | 说明 |
---|---|
public | 响应可以被任何缓存区缓存,包括客户端浏览器、代理服务器等。 |
private | 响应为单个用户定制,只能被用户的浏览器缓存,不能被共享缓存缓存。 |
no-store | 完全禁止缓存,每次请求都会下载完整的响应。 |
no-cache | 强制客户端每次访问都要向服务器验证缓存的资源是否更新,即使它仍在有效期内也要验证,未更新返回状态码304,不需要重新下载资源,否则返回状态码200并重新下载资源。 结合ETag和Last-Modified一起使用。 |
Last-Modified | 资源的最后更新时间,适合内容更新不是非常频繁,且以时间为主要修改标准的资源。 尽可能和Cache-Control或Expires一起使用,以避免使用启发式缓存。 |
ETag | 资源的唯一标识符,适合内容动态生成或频繁更改的资源,以及在毫秒级别内可能发生变化的资源,比Last-Modified提供更高的精确度,但同时也可能比使用Last-Modified更耗费资源。 |
max-age | 指定资源新鲜度相对时间,http/1.1及以上版本替代Expires。 |
must-revalidate | 缓存到期后强制判断资源是否更新(哪怕网络受限),未成功判断不得使用缓存,和max-age=0一起使用等同no-cache。 |
immutable | 资源被缓存,则在其有效期内不会改变,即使用户执行了强制刷新操作(如按下Ctrl+F5); 和max-age一起使用,适用于不会改变的资源; 使用immutable时要谨慎,确保只有不会在其max-age期限内改变的资源才使用此指令,如果错误地将immutable应用于可能会更新的资源上,可能会导致用户看到过时的内容,直到缓存过期或被手动清除。 |
Expires | 资源过期绝对时间,兼容http/1.0。 |
其他阅读:
掘金都使用webp图片提速降本,必须安排!
通过代码覆盖率减小JS代码体积实践
Spring AOP-AspectJ注解实现拦截
Spring AOP-编码实现拦截
Spring Cache架构、机制及使用
布隆过滤器适配Spring Cache及问题与解决策略
如何编写软件设计文档
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(三)ClassLoader实现热加载
JAVA基础(五)函数式接口-复用,解耦之利刃