前端缓存
1. 前端缓存是什么?
浏览器与服务器通信的方式为应答模式,浏览器发起 HTTP 请求 – 服务器响应该请求。那么浏览器第一次向服务器发起该请求后拿到请求结果,会根据响应报文中 HTTP 头的缓存标识,决定是否缓存结果,是则将请求结果和缓存标识存入浏览器缓存中,简单的过程如下图: 浏览器每次发起请求,都会先在浏览器缓存中查找该请求的结果以及缓存标识, 浏览器每次拿到返回的请求结果都会将该结果和缓存标识存入浏览器缓存中。 浏览器缓存( Brower Caching )是浏览器对之前请求过的文件进行缓存,以便下一次访问时重复使用,节省带宽,提高访问速度,降低服务器压力 http缓存机制主要在 http 响应头中设定,响应头中相关字段为 Expires、Cache-Control、Last-Modified、Etag。
2. 分类
根据是否需要向服务器重新发起HTTP请求将缓存过程分为两个部分,分为强缓存和协商缓存。
2.1 强缓存:
强缓存主要使用 Expires、Cache-Control 两个头字段,两者同时存在 Cache-Control 优先级更高。当命中强缓存的时候,客户端不会再请求,直接从缓存中读取内容,并返回HTTP状态码200。
2.1.1 Expires
响应头,代表该资源的过期时间。是一个 GMT 格式的标准时间。 当客户端请求服务器的时候,服务器会返回资源的同时还会带上响应头 Expires,表示资源的过期具体时间,如果客户端在过期时间之前再次获取该资源,就不需要再请求我服务器了,可以直接在缓存里面拿。 使用 Expires 强缓存优点: 在过期时间以内,为用户省了很多流量。减少了服务器重复读取磁盘文件的压力。 使用 Expires 强缓存缺点: 缓存过期以后,服务器不管文件有没有变化会再次请求服务器。缓存过期时间是一个具体的时间,这个时间依赖于客户端的时间,如果时间不准确或者被改动缓存也会随之受到影响。
2.1.2 Cache-Control
请求/响应头,缓存控制字段,精确控制缓存策略。 为了让强缓存更精确,HTTP1.1增加了 Cache-Control 字段。Cache-Control 既能出现在请求头又能出现在响应头,其不同的值代表不同的意思 Cache-Control 服务端参数:
max-age: | 在多少秒内有效,是一个相对时间,这样比Expires具体的时间就更精确了。当为0的时候就是不使用强缓存,类似no-cache。 |
---|---|
s-maxage | 就是用于表示 cache 服务器上(比如 cache CDN,缓存代理服务器)的缓存的有效时间的,并只对 public 缓存有效。 |
no-cache | 不使用本地强缓存。需要使用缓存协商。 |
no-store | 不使用本地强缓存。需要使用缓存协商。 |
public | 可以被所有的用户缓存,包括终端用户和中间代理服务器。 |
private | 只能被终端用户的浏览器缓存,不允许中间缓存代理进行缓存。 |
Cache-Control 客户端参数:
max-stale | 5 表示客户端到代理服务器上拿缓存的时候,即使代理缓存过期了也不要紧,只要过期时间在 5 秒之内,还是可以从代理中获取的。 |
---|---|
min-fresh | 5 表示代理缓存需要一定的新鲜度,不要等到缓存刚好到期再拿,一定要在到期前 5 秒之前的时间拿,否则拿不到。 |
only-if-cached | 这个字段加上后表示客户端只会接受代理缓存,而不会接受源服务器的响应。如果代理缓存无效,则直接返回 504(Gateway Timeout)。 |
2.2 协商缓存
协商缓存主要有四个头字段,它们两两组合配合使用,If-Modified-Since 和 Last-Modified一组,Etag 和 If-None-Match 一组,当同时存在的时候会以Etag 和 If-None-Match 为主。当命中协商缓存的时候,服务器会返回HTTP 状态码 304,让客户端直接从本地缓存里面读取文件。
If-Modified-Since | 请求头,资源最近修改时间,由浏览器告诉服务器。其实就是第一次访问服务端返回的Last-Modified的值。 |
---|---|
Last-Modified | 响应头,资源最近修改时间,由服务器告诉浏览器。 |
Etag | 响应头,资源标识,由服务器告诉浏览器。 |
If-None-Match | 请求头,缓存资源标识,由浏览器告诉服务器。其实就是第一次访问服务端返回的Etag的值。 |
2.2.1 If-Modified-Since 和 Last-Modified:
当客户端第一次请求服务器的时候,服务端会返回一个 Last-Modified 响应头,该字段是一个标准时间。客户端请求服务器的时候会带上If-Modified-Since请求头字段,该字段的值就是服务器返回的 Last-Modified 的值。服务器接收到请求后会比较这两个值是否一样,一样就返回304,让客户端从缓存中读取,不一样就会返回新文件给客户端并更新 Last-Modified 响应头字段的值。
使用If-Modified-Since 和 Last-Modified的优点:
当缓存有效时服务器不会返回文件给客户端,而是直接返回304状态码,让客户端从缓存中获取文件。大大节省了流量和带宽以及服务器的压力。
使用If-Modified-Since 和 Last-Modified的缺点:
Last-Modified 过期时间只能精确到秒。如果在同一秒既修改了文件又获取文件,客户端是获取不到最新文件的。
2.2.2 Etag 和 If-None-Match
为了解决文件修改时间只能精确到秒带来的问题,我们引入 Etag 响应头。Etag 是由文件修改时间与文件大小计算而成,只有当文件文件内容或修改时间变了 Etag 的值才会发生变化。当客户端第一次请求服务器的时候,服务端会返回一个Etag响应头。客户端请求服务器的时候会带上 If-None-Match 请求头字段,该字段的值就是服务器返回的 Etag 的值。服务器接收到请求后会比较这两个值是否一样,一样就返回304,让客户端从缓存中读取,不一样就会返回新文件给客户端并更新Etag响应头字段的值。
使用Etag 和 If-None-Match的优点:
当缓存有效时服务器不会返回文件给客户端,而是直接返回 304 状态码,让客户端从缓存中获取文件。大大节省了流量和带宽以及服务器的压力。并且解决了一秒内修改并读取的问题。
3. 前端怎么做?
缓存是为了缓解请求的压力,但是我们前端开发遇到更新项目,用户的项目没有被更新,而是显示的是缓存的内容,我们如何做? 旧方案: 通过人工自己修改文件名或者在文件名后带上版本号、时间戳,这样客户端就会当新文件请求并使用,之前的强缓存就算在有效期内也会失效。
<script src="./static/vendor.dll.js?t=' + new Date().getTime()'&version=1.1.1"></script>
现有方案: 使用构建工具比如Wbpack,会根据文件名或文件内容自动计算hash值来给文件命名,当内容或文件名发生改变的时候,构建出来的文件名也一定会不一样,这样也解决了强缓存还在有效期内的问题。
4. 缓存具体存的位置在哪里?
按缓存位置分类我们可以分为 memory cache、disk cache、Service Worker 三种
Chrome 的开发者工具中,Network -> Size 一列看到一个请求最终的处理方式:如果是大小 (多少 K, 多少 M 等) 就表示是网络请求,否则会列出 from memory cache、from disk cache、from ServiceWorker 就表示命中了缓存。 memory cache 是内存中的缓存,(与之相对 disk cache 就是硬盘上的缓存)。按照操作系统的常理:先读内存,再读硬盘。 disk cache 也叫 HTTP cache,顾名思义是存储在硬盘上的缓存,因此它是持久存储的,是实际存在于文件系统中的。而且它允许相同的资源在跨会话,甚至跨站点的情况下使用,例如两个站点都使用了同一张图片。
上述的缓存策略以及缓存/读取/失效的动作都是由浏览器内部判断进行的,我们只能设置响应头的某些字段来告诉浏览器,而不能自己操作。
service work 给予了我们另外一种更加灵活,可以直接的操作方式。我们可以从 Chrome 的 Application找到Service Workers。这个缓存是永久性的,即关闭 TAB 或者浏览器,下次打开依然还在(而 memory cache 不是)。有两种情况会导致这个缓存中的资源被清除:手动调用 API cache.delete(resource) 或者容量超过限制,被浏览器全部清空。