浏览器缓存策略详解
关于 cache 的分类
按缓存位置分类 (memory cache, disk cache, Service Worker 等)
按失效策略分类 ( Cache-Control, ETag 等)
发网络请求时会按照这个原则来:
Service Worker
Memory Cache
Disk Cache
网络请求
浏览器的 tab 关闭后该浏览器 memory cache 便告失效 1.preloader 将请求的资源放入 memory cache 中,供之后的解析执行操作使用
2.preload <linkrel="preload">。这些显式指定的预加载资源
“请求 js/css - 解析执行 - 请求下一个 js/css - 解析执行下一个 js/css”
service worker 能够操作的缓存是有别于浏览器内部的 memory cache 或者 disk cache。缓存是永久性的,即便关闭了 tab。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。
请求网络之后会根据情况决定是否缓存
根据 Service Worker 中的 handler 决定是否存入 Cache Storage (额外的缓存位置)。
根据 HTTP 头部的相关字段( Cache-control, Pragma 等)决定是否存入 disk cache
memory cache 保存一份资源 的引用,以备下次使用。
memory cache 是浏览器控制的,不受开发者控制,也不受 http 协议头的约束
所以我们平时最为熟悉的其实是 disk cache,也叫 HTTP cache (因为不像 memory cache,它遵守 HTTP 协议头中的字段)。
平时所说的强制缓存,对比缓存,以及 Cache-Control 等,也都归于此类。
强缓存 强制缓存的含义是,当客户端请求后,会先访问缓存数据库看缓存是否存在。如果存在则直接返回;不存在则请求真的服务器,响应后再写入缓存数据库。
强制缓存直接减少请求数,是提升最大的缓存策略。它的优化覆盖了文章开头提到过的请求数据的全部三个步骤。如果考虑使用缓存来优化网页性能的话,强制缓存应该是首先被考虑的。
可以造成强制缓存的字段是 Cache-control 和 Expires
强缓存
分析对比 cache-control 和 expires
关于 Cache-Control: max-age=秒 和 Expires Expires = 时间,HTTP 1.0 版本,缓存的载止时间,允许客户端在这个时间之前不去检查(发请求) max-age = 秒,HTTP 1.1 版本,资源在本地缓存多少秒。
no-cache no-cache(无缓存)不意味着根本没有缓存,它只是告诉浏览器在使用缓存之前先验证服务器上的资源。
Expires 的一个缺点就是,返回的到期时间是服务器端的时间,这样存在一个问题,如果客户端的时间与服务器的时间相差很大,那么误差就很大,所以在 HTTP 1.1 版开始,使用 Cache-Control: max-age=秒替代。
考虑到 expires 的局限性,HTTP1.1 新增了 Cache-Control 字段来完成 expires 的任务。 expires 能做的事情,Cache-Control 都能做;expires 完成不了的事情,Cache-Control 也能做。因此,Cache-Control 可以视作是 expires 的完全替代方案。在当下的前端实践里,我们继续使用 expires 的唯一目的就是向下兼容。
协商缓存
ETag 和 last-modified
时间精度问题
Last-Modified 是基于资源的最后修改时间(通常精确到秒)来判断的。如果资源在同一秒内被多次修改(比如频繁写入的动态文件),Last-Modified 的时间戳无法反映这些变化,因为它只能精确到秒级。这样会导致客户端无法准确判断资源是否真正发生了变化。而 ETag 是基于资源内容的唯一标识(通常是内容的哈希值),即使修改发生在同一秒内,只要内容变了,ETag 就会不同。
2. 内容未变但时间戳变化的情况
有些情况下,文件的元数据(比如权限、所有者)被修改,或者文件被重新生成但实际内容没有变化,Last-Modified 时间会更新,但资源本身对客户端来说没有实质性差异。这时,仅仅依赖 Last-Modified,客户端可能会认为资源已改变而重新请求完整内容,浪费带宽。而 ETag 是基于内容的校验,只要内容没变,ETag 不变,服务器就可以返回 304 Not Modified,避免不必要的数据传输。
3. 分布式系统中的一致性问题
在分布式服务器(如 CDN)中,同一个资源可能存储在多个节点上,不同节点的时钟可能存在微小偏差,导致 Last-Modified 时间戳不一致。而 ETag 通常是基于内容生成的哈希值,不依赖服务器时间,因此在分布式环境中更可靠。
4. 更灵活的校验方式
ETag 可以由服务器自定义生成规则,不一定非要是内容的哈希值。比如,可以基于版本号、特定元数据或其他逻辑生成。这种灵活性让开发者能够根据业务需求优化缓存策略,而 Last-Modified 只能依赖时间戳,缺乏这种灵活性。
根据上次响应中的 ETag_value,自动往 request header 中添加 if-none-match 字段, 服务器收到请求后,拿 if-none-match 字段的值与资源的 etag 值进行比较,若相同,则命中协商缓存, 返回 304 响应
根据上次响应中的 last-modified—value,自动往 request header 中添加 if-modified-since 字段。服务器收到请求后,拿 if-modified-since 字段的值与资源的 last-modified 值进行比较,若相同,则命中协商缓存,则返回 304 响应
304 Not Modified:客户端有缓冲的文件并发出了一个条件性的请求(一般是提供 If-Modified-Since 头表示客户只想比指定日期更新的文档)。服务器告诉客户,原来缓存的文档还可以继续使用。
如果客户端在请求一个文件的时候,发现自己缓存的文件有 Last Modified ,那么在请求中会包含 If Modified Since ,这个时间就是缓存文件的 Last Modified 。因此,如果请求中包含 If Modified Since,就说明已经有缓存在客户端。只要判断这个时间和当前请求的文件的修改时间就可以确定是返回 304 还是 200 。对于静态文件,例如:CSS、图片,服务器会自动完成 Last Modified 和 If Modified Since 的比较,完成缓存或者更新。但是对于动态页面,就是动态产生的页面,往往没有包含 Last Modified 信息,这样浏览器、网关等都不会做缓存,也就是在每次请求的时候都完成一个 200 的请求。
一般的大的站点的图片服务器都有实现 HTTP 304 缓存功能。 这个 304 状态一般主要在用户刷新页面(F5 键)的时候触发,当用户在刷新页面的时候,因为原来的页面里的很多图片已经缓存过,客户端的浏览器已经记录了图片的最后更新时间(Last Modified),所以在用户刷新页面的时候,会向服务器提交一个字段:If-Modified-Since: Wed, 08 Sep 2010 21:35:06 GMT 这个时候,服务器端的程序先取得这个字段的值,然后与服务器上的图片最后修改时间对比,如果相同,就直接返回 304 Not Modified ,然后停止。这样就不用把图片重新传输到客户端,达到节省带宽的目的。
Cache-Control 除了 max-age 之外,还有好几个属性可以用来控制 cache 的行为,比如:
Cache-Control: public public 表示资源可以被缓存在任何可以被缓存的地方(CDN,中间代理服务器,浏览器等等),可以多用户共享
Cache-Control: private private 表示资源只可以被浏览器缓存,是私有缓存
Cache-Control: no-store no-store 表示浏览器需要每次都从服务器拿资源,真正的不缓存数据到本地。
Cache-Control: no-cache no-cache 并不是不缓存的意思,而是告诉浏览器要使用缓存文件,但是每次需要跟服务器确认是最新文件以后才能用,一般使用 Etag 或者 Last-Modified 字段来控制缓存,看 Etag/Last-Modified 是否跟服务器匹配,如果匹配就返回 304 告诉浏览器从缓存里取数据,不匹配就返回 200 并且重新返回数据。
两种刷新方式
f5 的刷新
可以看到 f5 可以被称为 soft refresh 其只是 reload page 而已。 即与回车地址相同,正常规则下的缓存还是会涉及到。
强制 f5 强制
刷新此时的刷新可以称为 hard refresh, 请求会加上一个 Cache-Control:no-cache 的标识来表明突破 cache-control 的限制, 需要服务端重新判断有效性,即不走强缓存。 另外请求 header 中去掉 If-None-Match,这样就不能使用协商缓存。拉到新的资源
get 和 post 的区别
get 请求 会被缓存 post 请求不会 首先要了解什么是缓存。
HTTP 缓存的基本目的就是使应用执行的更快,更易扩展,但是 HTTP 缓存通常只适用于 idempotent request(可以理解为查询请求,也就是不更新服务端数据的请求),这也就导致了在 HTTP 的世界里,一般都是对 Get 请求做缓存,Post 请求很少有缓存。
get 多用来直接获取数据,不修改数据,主要目的就是 DB 的 search 语句的感觉。用缓存(有个代理服务器的概念)的目的就是查 db 的速度变快。
post 则是发送数据到服务器端去存储。类似 db 里的 update delete 和 insert 语句的感觉。更新 db 的意思。数据必须放在数据库,所以一般都得去访问服务器端。
安全问题。
get 到服务器过程中数据都是在 url 中,也就是说要传送的数据是可以在链接里面看到,就有安全问题。因为是一个 url,所以就跟百度网址一样。
post 就不是在 url 里面所有还是比较安全的
浏览器把一些 css 文件和图片这些静态资源放在了 memory cache 和 disk cache,后续在访问这个页面的时候,这些静态资源就可以直接从缓存里拿,而不需要去服务器上下载,从而可以提高性能。
最后更新于
这有帮助吗?