HTTP缓存机制
为什么需要缓存
缓存可以减少重复的请求,优化整个请求链路,提高用户的体验,节省公司的资源开销
在链路请求的各个环节都可以根据实际收益引入缓存。
客户端 ----缓存----> 网关 —缓存----> 服务 —缓存—> 三方服务
客户端------HTTP缓存------> 服务
缓存分类:
1. 客户端缓存
2. CDN
3. 服务本地缓存:map、ehcache
4. 分布式缓存 redis
HTTP 协议的无状态性决定了它必须依靠客户端缓存来解决网络传输效率上的缺陷。
在 HTTP 协议设计之初,便确定了服务端与客户端之间“无状态”(Stateless)的交互原则。但无状态并不只有好的一面,由于每次请求都是独立的,服务端不保存此前请求的状态和资源,所以也不可避免地导致其携带有重复的数据,造成网络性能降低。HTTP 协议对此问题的解决方案便是客户端缓存,在 HTTP 从 1.0 到 1.1,再到 2.0 版本的每次演进中,逐步形成了现在被称为“状态缓存”、“强制缓存”(许多资料中简称为“强缓存”)和“协商缓存”的 HTTP 缓存机制。
强制缓存
根据约定,强制缓存在浏览器的地址输入、页面链接跳转、新开窗口、前进和后退中均可生效,但在用户主动刷新页面时应当自动失效。HTTP 协议中设有以下两类 Header 实现强制缓存:
1. expires
2. cache-control
expires
expires是HTTP/1.0开始提供给的,其语义是服务器承诺在这个时间前承诺资源不会发生变动,浏览器可以缓存该数据,不在重新发请求。
HTTP/1.1 200 OK
Expires: wed, 8 Apr 2023 07:28:00 GMT
但是这种方式会存在缺点:
1. 客户端和服务端的时钟不一定是一致的
2. 私有资源仅某些用户可见的会被CDN或代理服务器缓存器来,导致信息泄漏
cache-control
cache-control是HTTP/1.1开始提供的,其语义比较丰富根据设置的KEY-VALUE表达不同的意思。当其与expires同时存在是,并且语义存在冲突以cache-control为准。
HTTP/1.1 200 OK
Cache-Control: max-age=600
HTTP/1.1 200 OK
Cache-Control: public
其丰富的语义说明:
- max-age:后面跟随以秒为单位的数字,表明对于请求时间,资源多少秒内缓存是有效的
- s-maxage:中s是share的缩写,其后同样跟随数字,允许代理服务器和CDN缓存资源
- public和private: 表明资源是公共的还是私有的,公共的允许代理服务器和CDN缓存,private则只允许客户端缓存
- no-cache和no-store: no-cache客户端不要缓存,每一次都必须从服务端返回最新的资源,no-store禁止保存资源
- no-transform: 禁止资源被修改,如gzip
- min-fresh和only-if-cached: min-fresh建议服务器返回一个不少于该时间的缓存资源;only-if-cached表明客户端要求不必发送资源的具体内容,如果缓存没有命中直接返回503/service unavailable错误
- must-revalidate和proxy-revalidate,表示资源过期后,一定需要从服务器中获取,proxy-revalidate表示资源过期后,CDN等要过期资源
协商缓存
强制缓存是基于时效性的,但无论是人还是服务器,其实多数情况下都并没有什么把握去承诺某项资源多久不会发生变化。另外一种基于变化检测的缓存机制,在一致性上会有比强制缓存更好的表现,但需要一次变化检测的交互开销,性能上就会略差一些,这种基于检测的缓存机制,通常被称为“协商缓存”。
协商缓存有两种变动检查机制:
1.根据资源的修改时间 Last-Modified和If-Modified-Since
2.Etag和If-None-Match
如果文件文件内容没有变化,直接返回304,无须带响应消息体
Last-Modified
HTTP/1.1 304 Not Modified
Last-Mofified: Wed, 8 Apr 2023 15:31:30 GMT
缺点: Last-Modified只能到秒级,如果文件1秒内被修改多次,则不能及时得到最新资源。
Etag
Etag 是HTTP服务器的响应 Header,用于告诉客户端这个资源的唯一标识。HTTP 服务器可以根据自己的意愿来选择如何生成这个标识,第一次请求得到etag后,再次请求会在header里的if-none-match带上上次返回的etag,如果匹配则返回304,无须带响应消息体
资源未发生变化
HTTP/1.1 304 Not Modified
Etag: "28c3f612-ceb0-4ddc-ae35-791ca840c5fa"
资源发生变化
HTTP/1.1 200 OK
Etag: "28c3f612-ceb0-4ddc-ae35-791ca840c5f1"
缺点:由于etag需要计算生成,会消耗一定得资源和时间,没有Last-Modified直接查询文件时间性能好。
Etag交互服务端JAVA实现代码
String ifNoneMatch = request.getHeader("if-none-match");
String etag = fileStorageService.getFileEtagValue(npmPackageFile.getStorageReference());
res.setHeader("ETag", etag);
String etag = etagAndDownloadContent.getEtag();
if (StringUtil.isNotEmpty(ifNoneMatch) && ifNoneMatch.equals(etag)) {
// http_code = 304
response.setStatus(HttpStatus.NOT_MODIFIED.value());
return;
}
// 否则返回实际的内容
byte[] downloadContent = etagAndDownloadContent.getContent();
// 返回实际的文件内容
总结
HTTP有两种缓存机制:强制缓存和协商缓存,强制缓存cache-control比expires的语义丰富的多;协商缓存:Last-Modified比etag生成性能好,但是只能秒级。
技术方案没有最好,只有最适合当前场景的解决方案,技术也是一直发展的,我们也需要与时俱进,脑中存储各个场景在具体能拿到最大资源下的最合适的方案
参考
《凤凰架构构建可靠的大型分布系统》周志明