一、什么是浏览器缓存
浏览器将请求过的资源(html、js、css、img)等,根据缓存机制,拷贝一份副本存储在浏览器的内存或者磁盘上。如果下一次请求的url相同时则根据缓存机制决定是读取内存或者磁盘上的数据还是去服务器请求资源文件
缓存通过url来判断,如果url不同则是新的资源。所以我们开发时前端会将一些js、css等文件在后面加hash值来避免资源更新时浏览器仍读取缓存文件,导致需要刷新才能获取新的资源的问题
这种做法虽然解决了js、css等文件的更新,这种加hash的就不在这篇文章说了,具体方法上网查即可,但是对于index.html这样的文件由于不能加hash值,则仍然会存在当资源更新了,但是仍然读取缓存文件的问题。
二、与缓存相关的状态码
|-------------------------|-----------------------------------------------------------------------|
| 200 ok | 从浏览器下载的最新资源 |
| 200 (from memory cache) | 不进行http请求,直接从浏览器内存中读取的资源,页面关闭,则资源释放,一般一些脚本、图片、文字等会存在内存中 |
| 200 (from disk cache) | 不进行http请求,直接从磁盘中读取的资源,页面关闭,资源仍然存在,除非清除缓存,一般一些非脚本文件会存在磁盘中,例如html、css文件 |
| 304 (not modified) | 请求了服务器,但是由于服务器资源没有更新,所以仍使用内存中的资源 |
三、缓存相关的http header介绍
|-------------------|-------------------------------------------------------------------------------------------|
| http header | 介绍 |
| cache-control | response header or request header;指定缓存机制,优先级最高 |
| expires | response header or request header;指定缓存的过期时间(现在浏览器一般设置cache-control,设置expires是为了兼容http1.0) |
| last-modified | response header;资源的最后修改时间 |
| etag | response header;资源的唯一标识符 |
| if-modified-since | request header;缓存的服务器资源的最后修改时间 |
| if-none-match | request header;缓存的服务器资源的唯一标识 |
- 强缓存
不会进行http请求,读取的是内存中的资源,直到缓存失效
涉及到的状态码:
- 200(from memory cache)
- 200(from disk cache)
涉及到的http header:
- cache-control
优先级最高,所有的缓存机制看到 cache-control 都要服从它
强缓存:设置max-age在这个时间内都不进行http请求,从缓存中读取
|---------------|---------------------------------|
| cache-control | 描述 |
| no-store | 请求和相应都不缓存 |
| no-cache | 协商缓存,相当于cache-control:max-age=0 |
| max-age | 指定多少秒后资源过期(强缓存) |
- expires
这个是为了兼容http1.0,由于客户端可以修改时间,所以,expires优先级低,缓存策略以cache-control为准
- 协商缓存
在第一次请求服务器时,服务器会返回资源,并且返回一个资源的缓存标识,一起存到浏览器的缓存数据库。当第二次请求资源时,浏览器会首先将缓存标识发送给服务器,服务器拿到标识后判断标识是否匹配,如果不匹配,表示资源有更新,服务器会将新数据和新的缓存标识一起返回到浏览器;如果缓存标识匹配,表示资源没有更新,并且返回 304 状态码,浏览器就读取本地缓存服务器中的数据。
涉及到的状态码:
- 304 not modified
- 200 ok
涉及到的请求头
- last-modified (响应头)/ if-modified-since(请求头)
当浏览器第一次请求时,服务端返回资源的同时,会在响应头中添加last-modified,表示资源的最后修改时间,浏览器在第二次请求这个url时会在请求头中带上if-modified-since请求头,值为上一次请求的last-modified,用来询问该文件是否被修改过。
但是last-modified时间只能精确到秒,且无法识别出内容没有修改过的文件,只要修改时间变了就算变动,因此有了etag
- etag(响应头) / ig-none-match(请求头)
etag解决了last-modified的问题,当etag和last-modified同时存在时则以etag为准
nginx的etag计算方式:计算页面文件的最后修改时间,将文件最后修改时间的秒级Unix时间戳转为16进制作为etag的第一部分 计算页面文件的大小,将大小字节数转为16进制作为etag的第二部分。
etag两种类型:
强etag:
不论实体发生多么细微的变化都会改变其值
强ETag表示形式:"22FAA065-2664-4197-9C5E-C92EA03D0A16"
。
弱etag:
弱 ETag 值只用于提示资源是否相同。只有资源发生了根本改变产 生差异时才会改变 ETag 。这时,会在字段值最开始处附加 W/
。
弱ETag表现形式:W/"22FAA065-2664-4197-9C5E-C92EA03D0A16"
。
浏览器第二次请求上次请求过的url时,浏览器会在HTTP请求头添加一个If-None-Match的标记,用来询问服务器该文件有没有被修改。
四、项目遇到的问题
- 项目上线后用户需手动刷新页面才能获取新的资源
产生原因:http请求没有设置缓存机制(cache-control或者expires),导致浏览器不知道以什么方式缓存。这种情况一般默认为强缓存,强缓存时间根据一定的计算方式获得,在这个时间段内不会进行网络请求,返回的状态码为200(from disk cache)或者200(from memory cache)
解决办法:
针对不能加hash值又想要随时获取最新资源的html文件,应该设置请求头 cache-control:no-cache,相当于cache-control:max-age=0
- 奇怪的问题与排查
现象:nginx并没有配置cache-control或者expires,但是有时却不需要刷新就可以返回最新的资源(200 ok),而有时则返回200 from disk cache。
排查:
查看http的header信息,发现差别在if-modified-sine这个请求头上,返回200 ok的请求头会携带if-modified-sine,而返回200 from disk cache的请起头中没有携带if-modified-sine。
查了一些资料终于找到原因,原因如下:
if-modified-sine的值是第一次报文中 last-modified 的值
为什么会有条件请求字段呢?是因为缓存过期了,所以浏览器会从缓存中查找是否有etag、last-modified字段(注意,缓存是缓存的整个报文,而不仅仅是body部分),有的话,就在请求中带上,向服务器发起协商缓存请求。如果服务器发现资源没有改变,就返回304响应,浏览器就知道,本地缓存中的这个数据资源可以继续使用。(304响应是没有body体的,只有头字段等元信息)也就是说,只有在缓存过期的情况下,请求报文中才会有条件请求的相关字段。什么情况下,缓存会过期呢?如下:
- cache-control: max-age=0
- cache-control: no-cache
- 响应报文根本就没有返回任何关于cache有效期的头字段: cache-control / expire / progma 。那么看是否返回了 last-modified ,如果有该header,浏览器可以使用 Heuristic 算法计算出一个通用的缓存时间 (Date - last-modified) * 10%
第三点原因就是项目中遇到的奇怪现象的解释。
文章借鉴:https://segmentfault.com/q/1010000007008829 浏览器缓存带来的前端项目更新问题及解决方法_浏览器缓存的数据,如果服务器的数据有变化怎么办-CSDN博客