Web缓存大致可以分为数据库缓存、服务器缓存(代理服务器、CDN等)、浏览器缓存。
浏览器缓存可以分为HTTP缓存、indexDB、cookie、WebStrorage(localStorage和sessionStorage)等等。这里只讨论http缓存相关内容。
再具体了解HTTP缓存之前先来明确几个属于:
缓存命中率:从缓存中得到数据的请求数与所有请求数的比率。理想状态是越高越好。过期内容:超过设置的有效时间,被标记为“陈旧”的内容。通常过期内容不能响应客户端的请求,必须重新向源服务器请求新的内容或者验证缓存的内容是否依然准备。验证:验证缓存中的过期内容是否仍然有效,验证通过的话刷新过期时间。失效:失效就是把内容从缓存中移除。当内容发生改变时就必须移除失效的内容。浏览器缓存主要是HTTP协议定义的缓存机制。 在HTML中的头部标签中的标签里,例如:
<meta HTTP-EQUIV='Pragma' CONTENT='no-store'>该标签的含义是让浏览器不缓存当前页面,代理服务器不解析HTML内容,一般应用广泛的是用HTTP头信息控制缓存。
浏览器缓存分类 浏览器缓存分为强缓存和协商缓存。
强缓存指的是浏览器请求的资源保存在浏览器缓存中且未过期,可以从浏览器中直接加载。协商缓存指的是浏览器请求的资源是保留在浏览器上的过期资源,需要向服务器发送请求,验证请求的资源是否需要重新获取(或刷新过期时间,从浏览器缓存中获取该资源)。浏览器加载一个页面的简单流程如下:
浏览器先根据这个资源的http头信息来判断是否命中强缓存。如果命中,则直接加载在缓存中的资源,并且不会发送该资源的请求到服务器。如果未命中强缓存,则浏览器会发送加载该资源的请求到服务器。服务器判断浏览器本地缓存中该资源是否失效,若可以使用,则服务器会刷新该资源的过期时间,并且不会返回该资源,浏览器继续从缓存中加载资源。如果未命中协商缓存,则服务器会将完整的资源返回给浏览器,浏览器加载该资源,并更新缓存。强缓存 命中强缓存时,浏览器并不会发送请求给服务器。在Chrome的开发者工具中看到http的返回码是200,但是在Size列会显示为(from disk cache) 强缓存是利用http的响应头中的Expires或者Cache-Control两个字段来控制的,用来表示资源的缓存时间。
Expires 缓存的过期时间,用来指定资源的过期时间,是服务端的资源的具体过期时间点(绝对时间)。也就是说,Expires=max-age+请求时间,需要和Last-modified结合使用。
Expires是Web服务器响应头的字段,在响应http请求时告诉浏览器在过期时间前浏览器可以直接从浏览器缓存中读取数据,且无需发送请求给服务器。
缺点:由于过期时间是一个绝对时间,所以当客户端本地时间被修改以后,服务器与客户端的时间偏差会相比于未修改客户端本地时间时发生变化,就会导致缓存混乱。于是发展出了Cache-Control。
Cache-Control Cache-Control是一个相对时间,例如Cache-Control:3600,代表的是资源相对于客户端上的本地时间的有效期是3600秒。由于是相对时间,并且都是与客户端的本地时间作比较,所以服务器与客户端的时间偏差出现错误也不会导致出现问题。
Cache-Control与Expires可以在服务端配置同时启用或者启用其中一个,同时启用的话Cache-Control的优先级会比Expires的高。
Cache-Control可以由多个字段组合而成,主要有以下几个取值:
max-age:制定一个时间长度,在这个时间段内缓存是有效的,单位是s。例如设置Cache-Control:max-age=31536000,也就是说该资源的缓存有效期为31536000/24/60/60=365天,第一次访问该资源时,服务器中返回的响应头中带有Expires字段,表示该资源的有效时间为1年。 在没有禁用缓存且没有超过有效时间的情况下,再次访问这个资源就命中了缓存,直接从浏览器缓存中读取资源,不会向服务器发送加载该资源的请求。s-maxage:同max-age,覆盖max-age、Expires,但仅适用于共享缓存(public),在私有缓存(private)中被忽略。public:表示响应可以被任何对象(发送请求的客户端、代理服务器等等缓存)。private:表示响应之内被单个用户(可能是操作系统用户、浏览器用户)缓存,非共享的,不能被代理服务器缓存。no-cache:强制所有缓存了该响应的用户,在使用已缓存的数据前,都需要发送带验证器的请求到服务器。不是字面上的不缓存。no-store:禁止缓存,每次请求都要向服务器重新获取数据。must-revalidate:指定如果页面是过期的,则去服务器进行获取。这个指令并不常用,就不做过多的讨论了。协商缓存 若未命中强缓存,则浏览器会将请求发送至服务器。服务器根据请求头中的Last-Modify/If-Modify-Since或Etag/If-None-Match来判断是否命中协商缓存。如果命中,则http返回状态码为304,过期资源的过期时间被刷新,浏览器从浏览器缓存中加载该资源。
Last-Modify/If-Modify-Since 浏览器第一次请求一个资源的时候,服务器返回的响应头中会加上Last-Modify字段,Last-Modify字段是一个时间,标示该请求的资源的最后修改时间。 当浏览器再次请求该资源的时候,发送的请求头中会包含If-Modify-Since,该值为缓存之前响应头返回的Last-Modify。服务器在收到If-Modify-Since字段后,根据服务器上该资源的最后修改时间与该字段的时间是否匹配判断是否命中该资源。 如果命中协商缓存,则返回状态码为304,且不会返回资源内容和Last-Modify。这时候浏览器会从浏览器缓存中读取到该资源。 由于对比的是服务器时间,所以客户端和服务端的时间差距不会导致问题。 注意 通过服务器上资源的最后修改时间来判断该资源是否被修改还是不大准确,因为Last-Modify存在着几个问题:
Last-Modify标注的资源的最后修改时间单位只能精确到秒级,如果某些文件在1秒钟之内被修改多次的话,它将不能准确标注文件的修改时间。如果某些文件被定期生成,但内容又没发生任何变化,此时该资源的Last-Modify改变了,导致浏览器没法使用该资源缓存。有时候存在服务器没有准确获取文件的修改时间,或者与代理服务器时间不一致等情形。于是在HTTP1.1中出现了Etag/If-None-Match。
Etag/If-None-Match Etag/If-None-Match返回的是一个校验码(Etag:entity tag)。 Etag可以保证每一个资源是唯一的,资源变化都会导致Etag变化(事件被修改但资源内容不修改则Etag不发生变化)。Etag值的变更则说明该资源的状态已经被修改。 服务器根据浏览器上发送的If-None-Match值来判断是否命中协商缓存。
浏览器在第一次请求一个资源的时候,响应头中会有一个Etag标示该资源的状态。
当浏览器再次请求该资源时,在请求头中If-None-Match字段的值为先前响应头中的Etag,If-None-Match与此次请求响应头中的Etag进行比较。
若匹配则可以直接从浏览器缓存中读取到该数据(此时状态码为304),不匹配则服务器会返回完整的资源给浏览器,此时状态码为(200))。
Etag是服务器自动生成或者由开发者生成的对应的请求资源在服务器端的唯一标识符,能够更加准确的控制缓存。
Last-Modify与Etag是可以一起使用的,服务器会优先验证Etag,一致的情况下,才会继续比对Last-Modify,最后才决定是否返回304。
也可以单纯使用Etag/If-None-Match,因为通过判断资源的唯一标识符是否匹配,就可以知道资源是否被更改了。
用户行为与浏览器缓存 浏览器缓存与用户的行为有关 F5刷新会导致浏览器重新发送加载资源的请求到服务器,直接跳过了强缓存,所以Expires/Cache-Control会不起作用。
而是否命中协商缓存是需要发送请求到服务器,有服务器的响应头来决定的,所以Last-Modify/Etag还是会起作用的。
总结: